// Copyright (c) 2016 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

// Package zapgrpc provides a logger that is compatible with grpclog.
package zapgrpc // import "go.uber.org/zap/zapgrpc"

import (
	"fmt"

	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
)

// See https://github.com/grpc/grpc-go/blob/v1.35.0/grpclog/loggerv2.go#L77-L86
const (
	grpcLvlInfo int = iota
	grpcLvlWarn
	grpcLvlError
	grpcLvlFatal
)

var (
	// _grpcToZapLevel maps gRPC log levels to zap log levels.
	// See https://pkg.go.dev/go.uber.org/zap@v1.16.0/zapcore#Level
	_grpcToZapLevel = map[int]zapcore.Level{
		grpcLvlInfo:  zapcore.InfoLevel,
		grpcLvlWarn:  zapcore.WarnLevel,
		grpcLvlError: zapcore.ErrorLevel,
		grpcLvlFatal: zapcore.FatalLevel,
	}
)

// An Option overrides a Logger's default configuration.
type Option interface {
	apply(*Logger)
}

type optionFunc func(*Logger)

func (f optionFunc) apply(log *Logger) {
	f(log)
}

// WithDebug configures a Logger to print at zap's DebugLevel instead of
// InfoLevel.
// It only affects the Printf, Println and Print methods, which are only used in the gRPC v1 grpclog.Logger API.
//
// Deprecated: use grpclog.SetLoggerV2() for v2 API.
func WithDebug() Option {
	return optionFunc(func(logger *Logger) {
		logger.print = &printer{
			enab:   logger.levelEnabler,
			level:  zapcore.DebugLevel,
			print:  logger.delegate.Debug,
			printf: logger.delegate.Debugf,
		}
	})
}

// withWarn redirects the fatal level to the warn level, which makes testing
// easier. This is intentionally unexported.
func withWarn() Option {
	return optionFunc(func(logger *Logger) {
		logger.fatal = &printer{
			enab:   logger.levelEnabler,
			level:  zapcore.WarnLevel,
			print:  logger.delegate.Warn,
			printf: logger.delegate.Warnf,
		}
	})
}

// NewLogger returns a new Logger.
func NewLogger(l *zap.Logger, options ...Option) *Logger {
	logger := &Logger{
		delegate:     l.Sugar(),
		levelEnabler: l.Core(),
	}
	logger.print = &printer{
		enab:   logger.levelEnabler,
		level:  zapcore.InfoLevel,
		print:  logger.delegate.Info,
		printf: logger.delegate.Infof,
	}
	logger.fatal = &printer{
		enab:   logger.levelEnabler,
		level:  zapcore.FatalLevel,
		print:  logger.delegate.Fatal,
		printf: logger.delegate.Fatalf,
	}
	for _, option := range options {
		option.apply(logger)
	}
	return logger
}

// printer implements Print, Printf, and Println operations for a Zap level.
//
// We use it to customize Debug vs Info, and Warn vs Fatal for Print and Fatal
// respectively.
type printer struct {
	enab   zapcore.LevelEnabler
	level  zapcore.Level
	print  func(...interface{})
	printf func(string, ...interface{})
}

func (v *printer) Print(args ...interface{}) {
	v.print(args...)
}

func (v *printer) Printf(format string, args ...interface{}) {
	v.printf(format, args...)
}

func (v *printer) Println(args ...interface{}) {
	if v.enab.Enabled(v.level) {
		v.print(sprintln(args))
	}
}

// Logger adapts zap's Logger to be compatible with grpclog.LoggerV2 and the deprecated grpclog.Logger.
type Logger struct {
	delegate     *zap.SugaredLogger
	levelEnabler zapcore.LevelEnabler
	print        *printer
	fatal        *printer
	// printToDebug bool
	// fatalToWarn  bool
}

// Print implements grpclog.Logger.
//
// Deprecated: use [Logger.Info].
func (l *Logger) Print(args ...interface{}) {
	l.print.Print(args...)
}

// Printf implements grpclog.Logger.
//
// Deprecated: use [Logger.Infof].
func (l *Logger) Printf(format string, args ...interface{}) {
	l.print.Printf(format, args...)
}

// Println implements grpclog.Logger.
//
// Deprecated: use [Logger.Info].
func (l *Logger) Println(args ...interface{}) {
	l.print.Println(args...)
}

// Info implements grpclog.LoggerV2.
func (l *Logger) Info(args ...interface{}) {
	l.delegate.Info(args...)
}

// Infoln implements grpclog.LoggerV2.
func (l *Logger) Infoln(args ...interface{}) {
	if l.levelEnabler.Enabled(zapcore.InfoLevel) {
		l.delegate.Info(sprintln(args))
	}
}

// Infof implements grpclog.LoggerV2.
func (l *Logger) Infof(format string, args ...interface{}) {
	l.delegate.Infof(format, args...)
}

// Warning implements grpclog.LoggerV2.
func (l *Logger) Warning(args ...interface{}) {
	l.delegate.Warn(args...)
}

// Warningln implements grpclog.LoggerV2.
func (l *Logger) Warningln(args ...interface{}) {
	if l.levelEnabler.Enabled(zapcore.WarnLevel) {
		l.delegate.Warn(sprintln(args))
	}
}

// Warningf implements grpclog.LoggerV2.
func (l *Logger) Warningf(format string, args ...interface{}) {
	l.delegate.Warnf(format, args...)
}

// Error implements grpclog.LoggerV2.
func (l *Logger) Error(args ...interface{}) {
	l.delegate.Error(args...)
}

// Errorln implements grpclog.LoggerV2.
func (l *Logger) Errorln(args ...interface{}) {
	if l.levelEnabler.Enabled(zapcore.ErrorLevel) {
		l.delegate.Error(sprintln(args))
	}
}

// Errorf implements grpclog.LoggerV2.
func (l *Logger) Errorf(format string, args ...interface{}) {
	l.delegate.Errorf(format, args...)
}

// Fatal implements grpclog.LoggerV2.
func (l *Logger) Fatal(args ...interface{}) {
	l.fatal.Print(args...)
}

// Fatalln implements grpclog.LoggerV2.
func (l *Logger) Fatalln(args ...interface{}) {
	l.fatal.Println(args...)
}

// Fatalf implements grpclog.LoggerV2.
func (l *Logger) Fatalf(format string, args ...interface{}) {
	l.fatal.Printf(format, args...)
}

// V implements grpclog.LoggerV2.
func (l *Logger) V(level int) bool {
	return l.levelEnabler.Enabled(_grpcToZapLevel[level])
}

func sprintln(args []interface{}) string {
	s := fmt.Sprintln(args...)
	// Drop the new line character added by Sprintln
	return s[:len(s)-1]
}