package splog

import (
	"fmt"
	"io"
	"os"
	"reflect"
	"runtime"
	"sort"
	"strings"
	"time"
)

type LogRecord struct {
	Level int
	Err   error
	Msg   string
	Args  []interface{}
	Pc    uintptr
	File  string
	Line  int
	Time  time.Time
}

/*
Format a log message from a record
Only called if rec.level >= logLevel
*/
type LogFormatter func(logLevel int, rec *LogRecord) string

var (
	SECCRITICAL = SetLevelName(51, "SECCRITICAL")
	CRITICAL    = SetLevelName(50, "CRITICAL")
	DENIED      = SetLevelName(42, "DENIED")
	SECERROR    = SetLevelName(41, "SECERROR")
	ERROR       = SetLevelName(40, "ERROR")
	SECWARN     = SetLevelName(31, "SECWARN")
	WARN        = SetLevelName(30, "WARN")
	ERROUT      = SetLevelName(29, "ERROUT")
	SERV        = SetLevelName(25, "SERV")
	OUT         = SetLevelName(22, "OUT")
	SECINFO     = SetLevelName(21, "SECINFO")
	INFO        = SetLevelName(20, "INFO")
	SECVERBOSE  = SetLevelName(16, "SECVERBOSE")
	VERBOSE     = SetLevelName(15, "VERBOSE")
	SECDEBUG    = SetLevelName(11, "SECDEBUG")
	DEBUG       = SetLevelName(10, "DEBUG")
)

var (
	levelNums                 = []int{0}
	levelNames                = []string{"NOTSET"}
	logLevel                  = INFO
	logErr       io.Writer    = os.Stderr
	logOut       io.Writer    = os.Stdout
	logFormatter LogFormatter = DefaultFormatter
)

func LevelNameEff(level int) (int, int, string) {
	n := len(levelNums)
	idx := sort.SearchInts(levelNums, level)

	if idx >= n {
		idx = n - 1
	}

	eff_level := levelNums[idx]
	eff_name := levelNames[idx]

	return idx, eff_level, eff_name
}

func LevelName(level int) string {
	_, _, name := LevelNameEff(level)
	return name
}

func SetLevelName(level int, name string) int {
	n := len(levelNums)
	idx := sort.SearchInts(levelNums, level)

	if idx < n && levelNums[idx] == level {
		levelNames[idx] = name
	} else {

		levelNums = append(levelNums, level)
		levelNames = append(levelNames, name)

		if idx < n {
			copy(levelNums[idx+1:], levelNums[idx:])
			copy(levelNames[idx+1:], levelNames[idx:])

			levelNums[idx] = level
			levelNames[idx] = name
		}
	}

	return level
}

func DefaultFormatter(logLevel int, rec *LogRecord) string {
	message := fmt.Sprintf(rec.Msg, rec.Args...)

	if !strings.HasSuffix(message, "\n") {
		// ensure written messages are separated by at least one newline
		message += "\n"
	}

	if rec.Err != nil {
		if logLevel < VERBOSE {
			// when debugging errors, add file and line number, and any stack trace
			message += fmt.Sprintf("%s:%d\n%+v\n", rec.File, rec.Line, rec.Err)
		} else {
			message += fmt.Sprintf("%v\n", rec.Err)
		}
	}

	if (rec.Level == INFO || rec.Level == OUT) && logLevel == INFO {
		// NOTE: this is a bit strange, but for user-friendliness it makes sense
		// to not pollute the messages unless something bad happens (by default).
		// if logLevel > INFO, then level == INFO messages should not be printed anyway,
		// and if logLevel < INFO, then it seems like all messages should get prefixed
		return message
	}

	name := LevelName(rec.Level)

	if len(name) <= 7 {
		return fmt.Sprintf("%-7s: %s", name, message)
	}

	return fmt.Sprintf("%-11s: %s", name, message)
}

func EnabledForLevel(level int) bool {
	return level >= logLevel
}

/*
Set the central log level. Uneven values are security related
lof entries.
*/
func SetLogLevel(level int) {
	logLevel = level

	Debug("Set log level to: %d, %s", logLevel, LevelName(logLevel))
}

func GetLogLevel() int {
	return logLevel
}

/*
Set the log output writer
By default they are set to output writer
*/
func SetLogWriter(newOut io.Writer) {
	logErr = newOut
	logOut = newOut
}

/*
Set the log ofr info only
*/
func SetLogWriterInfo(newOut io.Writer) {
	logOut = newOut
}

/*
Set the log writer for errors
*/
func SetLogWriterErr(newOut io.Writer) {
	logErr = newOut
}

/*
Set the log record formatter
By default this is set to DefaultFormatter
*/
func SetLogFormatter(formatter LogFormatter) {
	logFormatter = formatter
	Debug("Set log formatter: %s", runtime.FuncForPC(reflect.ValueOf(formatter).Pointer()).Name())
}

/*
Internal method to create a log record
*/
func LogCaller(level int, skip int, err error, message string, a ...interface{}) {
	if EnabledForLevel(level) {
		pc, file, line, ok := runtime.Caller(skip + 1)
		if !ok {
			file = "[unknown]"
		}

		rec := LogRecord{
			Level: level,
			Err:   err,
			Msg:   message,
			Args:  a,
			Pc:    pc,
			File:  file,
			Line:  line,
			Time:  time.Now(),
		}

		message = logFormatter(logLevel, &rec)
		if level == INFO || level == OUT {
			_, _ = fmt.Fprint(logOut, message)
		} else {
			_, _ = fmt.Fprint(logErr, message)

		}
	}
}

/*
******************************************************************************
Named log level functions
*/
func Log(level int, message string, a ...interface{}) {
	LogCaller(level, 1, nil, message, a...)
}

func LogExc(level int, err error, message string, a ...interface{}) {
	LogCaller(level, 1, err, message, a...)
}

func Debug(message string, a ...interface{}) {
	LogCaller(DEBUG, 1, nil, message, a...)
}

func DebugExc(err error, message string, a ...interface{}) {
	LogCaller(DEBUG, 1, err, message, a...)
}

func SecDebug(message string, a ...interface{}) {
	LogCaller(SECDEBUG, 1, nil, message, a...)
}

func Verbose(message string, a ...interface{}) {
	LogCaller(VERBOSE, 1, nil, message, a...)
}

func VerboseExc(err error, message string, a ...interface{}) {
	LogCaller(VERBOSE, 1, err, message, a...)
}

func SecVerbose(message string, a ...interface{}) {
	LogCaller(SECVERBOSE, 1, nil, message, a...)
}

func Info(message string, a ...interface{}) {
	LogCaller(INFO, 1, nil, message, a...)
}

func Output(message string, a ...interface{}) {
	LogCaller(OUT, 1, nil, message, a...)
}

func InfoExc(err error, message string, a ...interface{}) {
	LogCaller(INFO, 1, err, message, a...)
}

func SecInfo(message string, a ...interface{}) {
	LogCaller(SECINFO, 1, nil, message, a...)
}

func Serv(message string, a ...interface{}) {
	LogCaller(SERV, 1, nil, message, a...)
}

func Warn(message string, a ...interface{}) {
	LogCaller(WARN, 1, nil, message, a...)
}

func WarnExc(err error, message string, a ...interface{}) {
	LogCaller(WARN, 1, err, message, a...)
}

func SecWarn(message string, a ...interface{}) {
	LogCaller(SECWARN, 1, nil, message, a...)
}

func Error(message string, a ...interface{}) {
	LogCaller(ERROR, 1, nil, message, a...)
}

func ErrorExc(err error, message string, a ...interface{}) {
	LogCaller(ERROR, 1, err, message, a...)
}

func SecError(message string, a ...interface{}) {
	LogCaller(SECERROR, 1, nil, message, a...)
}

func Denied(message string, a ...interface{}) {
	LogCaller(DENIED, 1, nil, message, a...)
}

func Critical(message string, a ...interface{}) {
	LogCaller(CRITICAL, 1, nil, message, a...)
}

func CriticalExc(err error, message string, a ...interface{}) {
	LogCaller(CRITICAL, 1, err, message, a...)
}

func SecCritical(message string, a ...interface{}) {
	LogCaller(SECCRITICAL, 1, nil, message, a...)
}
