//
// Copyright 2019 AT&T Intellectual Property
// Copyright 2019 Nokia
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//  This source code is part of the near-RT RIC (RAN Intelligent Controller)
//  platform project (RICP).


package logger

import (
	"fmt"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"strings"
	"time"
)

type Logger struct {
	Logger     *zap.Logger
}

// Copied from zap logger
//
// A Level is a logging priority. Higher levels are more important.
type LogLevel int8

const (
	// DebugLevel logs are typically voluminous, and are usually disabled in
	// production.
	DebugLevel LogLevel = iota - 1
	// InfoLevel is the default logging priority.
	InfoLevel
	// WarnLevel logs are more important than Info, but don't need individual
	// human review.
	WarnLevel
	// ErrorLevel logs are high-priority. If an application is running smoothly,
	// it shouldn't generate any error-level logs.
	ErrorLevel
	// DPanicLevel logs are particularly important errors. In development the
	// logger panics after writing the message.
	DPanicLevel
	// PanicLevel logs a message, then panics.
	PanicLevel
	// FatalLevel logs a message, then calls os.Exit(1).
	FatalLevel

	_minLevel = DebugLevel
	_maxLevel = FatalLevel
)

var logLevelTokenToLevel = map[string] LogLevel {
	"debug" : DebugLevel,
	"info": InfoLevel,
	"warn": WarnLevel,
	"error": ErrorLevel,
	"dpanic": DPanicLevel,
	"panic": PanicLevel,
	"fatal": FatalLevel,
}

func LogLevelTokenToLevel(level string) (LogLevel, bool) {
	if level, ok := logLevelTokenToLevel[strings.TrimSpace(strings.ToLower(level))];ok {
		return level, true
	}
	return _maxLevel+1, false
}

func InitLogger(requested LogLevel) (*Logger, error) {
	var logger *zap.Logger
	var err error
	switch requested {
	case DebugLevel:
		logger, err = initLoggerByLevel(zapcore.DebugLevel)
	case InfoLevel:
		logger, err = initLoggerByLevel(zapcore.InfoLevel)
	case WarnLevel:
		logger, err = initLoggerByLevel(zapcore.WarnLevel)
	case ErrorLevel:
		logger, err = initLoggerByLevel(zapcore.ErrorLevel)
	case DPanicLevel:
		logger, err = initLoggerByLevel(zapcore.DPanicLevel)
	case PanicLevel:
		logger, err = initLoggerByLevel(zapcore.PanicLevel)
	case FatalLevel:
		logger, err = initLoggerByLevel(zapcore.FatalLevel)
	default:
		err = fmt.Errorf("Invalid logging Level :%d",requested)
	}
	if err != nil {
		return nil, err
	}
	return &Logger{Logger:logger}, nil

}
func(l *Logger)Sync() error {
	l.Debugf("#logger.Sync - Going to flush buffered log")
	return l.Logger.Sync()
}

func (l *Logger)Infof(formatMsg string, a ...interface{})  {
	if l.InfoEnabled() {
		msg := fmt.Sprintf(formatMsg, a...)
		l.Logger.Info(msg, zap.Any("mdc", l.getTimeStampMdc()))
	}
}

func (l *Logger)Debugf(formatMsg string, a ...interface{})  {
	if l.DebugEnabled(){
		msg := fmt.Sprintf(formatMsg, a...)
		l.Logger.Debug(msg, zap.Any("mdc", l.getTimeStampMdc()))
	}
}

func (l *Logger)Errorf(formatMsg string, a ...interface{})  {
	msg := fmt.Sprintf(formatMsg, a...)
	l.Logger.Error(msg, zap.Any("mdc", l.getTimeStampMdc()))
}

func (l *Logger)Warnf(formatMsg string, a ...interface{})  {
	msg := fmt.Sprintf(formatMsg, a...)
	l.Logger.Warn(msg, zap.Any("mdc", l.getTimeStampMdc()))
}

func (l *Logger) getTimeStampMdc() map[string]string {
	timeStr := time.Now().Format("2006-01-02 15:04:05.000")
	mdc := map[string]string{"time": timeStr}
	return mdc
}

func (l *Logger)InfoEnabled()bool{
	return l.Logger.Core().Enabled(zap.InfoLevel)
}

func (l *Logger)DebugEnabled()bool{
	return l.Logger.Core().Enabled(zap.DebugLevel)
}

func (l *Logger)DPanicf(formatMsg string, a ...interface{})  {
	msg := fmt.Sprintf(formatMsg, a...)
	l.Logger.DPanic(msg, zap.Any("mdc", l.getTimeStampMdc()))
}

func initLoggerByLevel(l zapcore.Level) (*zap.Logger, error) {
	cfg := zap.Config{
		Encoding:         "json",
		Level:            zap.NewAtomicLevelAt(l),
		OutputPaths:      []string{"stdout"},
		ErrorOutputPaths: []string{"stderr"},
		EncoderConfig: zapcore.EncoderConfig{
			MessageKey: "msg",

			LevelKey:    "crit",
			EncodeLevel: zapcore.CapitalLevelEncoder,

			TimeKey:    "ts",
			EncodeTime: epochMillisIntegerTimeEncoder,

			CallerKey: "id",
			EncodeCaller: e2ManagerCallerEncoder,
		},
	}
	return cfg.Build()
}

func e2ManagerCallerEncoder(caller zapcore.EntryCaller, enc zapcore.PrimitiveArrayEncoder) {
	enc.AppendString("E2Manager")
}

func epochMillisIntegerTimeEncoder(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
	nanos := t.UnixNano()
	millis := int64(nanos) / int64(time.Millisecond)
	enc.AppendInt64(millis)
}

