package log

import (
	"fmt"
	golog "log"
	"os"
	"runtime"
	"strings"
	"sync"
)

// Level indicates the level a log should be classified at
type Level int

// The possible levels a log can have
const (
	LevelDebug Level = iota + 1
	LevelInfo
	LevelWarn
	LevelError
)

/**** Color code from https://gist.github.com/ik5/d8ecde700972d4378d87 ****/
var (
	Debug = Teal
	Info = Green
	Warn = Yellow
	Error = Red
)

var (
	Black   = Color("\033[1;30m%s\033[0m")
	Red     = Color("\033[1;31m%s\033[0m")
	Green   = Color("\033[1;32m%s\033[0m")
	Yellow  = Color("\033[1;33m%s\033[0m")
	Purple  = Color("\033[1;34m%s\033[0m")
	Magenta = Color("\033[1;35m%s\033[0m")
	Teal    = Color("\033[1;36m%s\033[0m")
	White   = Color("\033[1;37m%s\033[0m")
)

func Color(colorString string) func(...interface{}) string {
	sprint := func(args ...interface{}) string {
		return fmt.Sprintf(colorString,
			fmt.Sprint(args...))
	}
	return sprint
}

/**** End color gist ****/

var levelString = map[Level]string{LevelDebug: "[DBUG]", LevelInfo: "[INFO]", LevelWarn: "[WARN]", LevelError: "[ERR ]"}
var levelColor = map[Level]func(...interface{})string {LevelDebug: Debug, LevelInfo: Info, LevelWarn: Warn, LevelError: Error}


// Logger is a go Logger wrapper that adds log levels and pattern filtering
// It allows high level 'log level' filtering broadly
// It allows allows two addition levels of filtering:
//   everythingFrom which allows inclusion of packages/patterns along with general logging
//   nothingExcept which allows focusing on just listed areas
//   privacyFilters by default apply all functions to printf args and add warnings to them for matches
//   privacyFilterReplace obscures any matches from the privacy filter functions
type Logger struct {
	lock				   sync.Mutex // ensures atomic writes; protects the following fields
	logger                 *golog.Logger
	level                  Level
	useColor			   bool
	nothingExceptPatterns  []string
	everythingFromPatterns []string
	excludeFromPatterns    []string
	privacyFilers		   []func(string)bool
	privacyFilterReplace   bool
}

// New returns a new Logger with a filter set to the supplied level
func New(level Level) *Logger {
	return &Logger{logger: golog.New(os.Stderr, "", golog.Ldate|golog.Ltime), level: level, useColor: true, everythingFromPatterns: make([]string, 0), nothingExceptPatterns: make([]string, 0), privacyFilers: make([]func(string)bool, 0), privacyFilterReplace: false}
}

// NewFile returns a new Logger that logs to the supplied file with a filter set to the supplied level
func NewFile(level Level, filename string) (*Logger, error) {
	logfile, err := os.OpenFile(filename, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600)
	if err != nil {
		return nil, err
	}

	return &Logger{logger: golog.New(logfile, "", golog.Ldate|golog.Ltime), level: level, useColor: false, everythingFromPatterns: make([]string, 0), nothingExceptPatterns: make([]string, 0), privacyFilers: make([]func(string)bool, 0), privacyFilterReplace: false}, nil
}

var stdLock sync.Mutex
var std = New(LevelWarn)

// SetStd sets the default logger all other functions use
func SetStd(logger *Logger) {
	stdLock.Lock()
	defer stdLock.Unlock()
	std = logger
}

// filter
func (l *Logger) filter(level Level) bool {

	_, file, _, ok := runtime.Caller(3)
	if !ok {
		file = "???"
	}

	for _, pattern := range l.excludeFromPatterns {
		if strings.Contains(file, pattern) {
			return false
		}
	}

	for _, pattern := range l.everythingFromPatterns {
		if strings.Contains(file, pattern) {
			return true
		}
	}

	for _, pattern := range l.nothingExceptPatterns {
		if strings.Contains(file, pattern) {
			return true
		}
	}

	if len(l.nothingExceptPatterns) > 0 {
		return false
	}

	return l.aboveLevel(level)
}

func (l *Logger) aboveLevel(level Level) bool {
	return level >= l.level
}

// SetLevel adjusts the output filter by logging level
func (l *Logger) SetLevel(level Level) {
	l.lock.Lock()
	defer l.lock.Unlock()
	l.level = level
}

// SetUseColor toggles weather color output is used
func (l *Logger) SetUseColor(useColor bool) {
	l.lock.Lock()
	defer l.lock.Unlock()
	l.useColor = useColor
}

// AddNothingExceptFilter enables strong filtering showing logs only for things on the approved list, adding this pattern to the list
func (l *Logger) AddNothingExceptFilter(pattern string) {
	l.lock.Lock()
	defer l.lock.Unlock()
	l.nothingExceptPatterns = append(l.nothingExceptPatterns, pattern)
}

// AddEverythingFromPattern adds a pattern to skip log level filtering, guaranteeing all logs matching the pattern are seen
func (l *Logger) AddEverythingFromPattern(pattern string) {
	l.lock.Lock()
	defer l.lock.Unlock()
	l.everythingFromPatterns = append(l.everythingFromPatterns, pattern)
}

// ExcludeFromPattern adds a pattern to exclude logs from
func (l *Logger) ExcludeFromPattern(pattern string) {
	l.lock.Lock()
	defer l.lock.Unlock()
	l.excludeFromPatterns = append(l.excludeFromPatterns, pattern)
}

func (l *Logger) AddPrivacyFilter(fn func(string)bool) {
	l.lock.Lock()
	defer l.lock.Unlock()
	l.privacyFilers = append(l.privacyFilers, fn)
}

func (l *Logger) SetPrivacyFilterReplace(b bool) {
	l.lock.Lock()
	defer l.lock.Unlock()
	l.privacyFilterReplace = b
}

func (l *Logger) header(level Level) string {
	_, file, _, ok := runtime.Caller(3)
	if !ok {
		file = "???"
	} else {
		short := file
		count := 0
		for i := len(file) - 1; i > 0; i-- {
			if file[i] == '/' {
				short = file[i+1:]
				count++
				if count == 2 {
					break
				}
			}
		}
		file = short
	}
	if l.useColor {
		return file + " " + levelColor[level](levelString[level]) + " "
	} else {
		return file + " " + levelString[level] + " "
	}
}

func (l *Logger) privacyFilter(v ...interface{}) []interface{} {
	if len(l.privacyFilers) > 0 {
		for i, val := range v {
			for _, fn := range l.privacyFilers {
				switch cval := val.(type) {
				case string:
					if fn(cval) {
						if l.privacyFilterReplace {
							cval = strings.Repeat("*", len(cval))
						}

						cval = "PRIVACY-FILTER-FLAGGED[" + cval + "]"

						if l.useColor {
							cval = Magenta(cval)
						}

						v[i] = cval
					}
				}
			}
		}
	}
	return v
}

// Printf outputs the format and variables, assuming it passes the filter levels
func (l *Logger) Printf(level Level, format string, v ...interface{}) {
	l.lock.Lock()
	defer l.lock.Unlock()
	if l.filter(level) {
		l.logger.Output(3, l.header(level)+fmt.Sprintf(format, l.privacyFilter(v...)...))
	}
}

// Println outputs the variables assuming the filter levels are passed
func (l *Logger) Println(level Level, v ...interface{}) {
	l.lock.Lock()
	defer l.lock.Unlock()
	if l.filter(level) {
		l.logger.Output(3, l.header(level)+fmt.Sprintln(v...))
	}
}

// SetLevel changes the filtering level of the system logger
func SetLevel(level Level) {
	std.SetLevel(level)
}

// SetUseColor toggles weather color output is used
func SetUseColor(useColor bool) {
	std.useColor = useColor
}

// AddNothingExceptFilter enables strong filtering showing logs only for things on the approved list, adding this pattern to the list
func AddNothingExceptFilter(pattern string) {
	std.AddNothingExceptFilter(pattern)
}

// AddEverythingFromPattern adds a pattern to skip log level filtering, guaranteeing all logs matching the pattern are seen
func AddEverythingFromPattern(pattern string) {
	std.AddEverythingFromPattern(pattern)
}

// ExcludeFromPattern adds a pattern to exclude logs from
func ExcludeFromPattern(pattern string) {
	std.ExcludeFromPattern(pattern)
}

// AddPrivacyFilter adds a function applied to printf argument strings that can flag them as sensitive data
func AddPrivacyFilter(fn func(string)bool) {
	std.AddPrivacyFilter(fn)
}

// SetPrivacyFilterReplace replaces the string argument to printf that a Privacy Filter identified with ****s
func SetPrivacyFilterReplace(b bool) {
	std.SetPrivacyFilterReplace(b)
}

// Printf outputs the format with variables assuming it passes the filter level
func Printf(level Level, format string, v ...interface{}) {
	std.Printf(level, format, v...)
}

// Println outputs the varibles assuming they pass the filter level
func Println(level Level, v ...interface{}) {
	std.Println(level, v...)
}

// Debugf outputs the format and variables at the Debug level
func Debugf(format string, v ...interface{}) {
	std.Printf(LevelDebug, format, v...)
}

// Infof outputs the format and variables at the Info level
func Infof(format string, v ...interface{}) {
	std.Printf(LevelInfo, format, v...)
}

// Warnf outputs the format and variables at the Warning level
func Warnf(format string, v ...interface{}) {
	std.Printf(LevelWarn, format, v...)
}

// Errorf outputs the format and variables at the Error level
func Errorf(format string, v ...interface{}) {
	std.Printf(LevelError, format, v...)
}

// Debugln outputs the variables at the Debug level
func Debugln(v ...interface{}) {
	std.Println(LevelDebug, v...)
}

// Infoln outputs the variables at the Info level
func Infoln(v ...interface{}) {
	std.Println(LevelInfo, v...)
}

// Warnln outputs the variables at the Warn level
func Warnln(v ...interface{}) {
	std.Println(LevelWarn, v...)
}

// Errorln outputs the variables at the Error level
func Errorln(v ...interface{}) {
	std.Println(LevelError, v...)
}