package logger

import (
	"fmt"
	"os"
	"strings"

	"github.com/ian-kent/go-log/appenders"
	"github.com/ian-kent/go-log/layout"
	"github.com/ian-kent/go-log/levels"
)

// Logger represents a logger
type Logger interface {
	Level() levels.LogLevel
	Name() string
	FullName() string
	Enabled() map[levels.LogLevel]bool
	Appender() Appender
	Children() []Logger
	Parent() Logger
	GetLogger(string) Logger
	SetLevel(levels.LogLevel)
	Log(levels.LogLevel, ...interface{})
	SetAppender(appender Appender)

	Debug(params ...interface{})
	Info(params ...interface{})
	Warn(params ...interface{})
	Error(params ...interface{})
	Trace(params ...interface{})
	Printf(params ...interface{})
	Println(params ...interface{})
	Fatal(params ...interface{})
	Fatalf(params ...interface{})
}

type logger struct {
	level       levels.LogLevel
	name        string
	enabled     map[levels.LogLevel]bool
	appender    Appender
	children    []Logger
	parent      Logger
	ExitOnFatal bool
}

// Appender represents a log appender
type Appender interface {
	Write(level levels.LogLevel, message string, args ...interface{})
	SetLayout(layout layout.Layout)
	Layout() layout.Layout
}

// New returns a new Logger
func New(name string) Logger {
	l := Logger(&logger{
		level:       levels.DEBUG,
		name:        name,
		enabled:     make(map[levels.LogLevel]bool),
		appender:    appenders.Console(),
		children:    make([]Logger, 0),
		parent:      nil,
		ExitOnFatal: true,
	})
	l.SetLevel(levels.DEBUG)
	return l
}

func unwrap(args ...interface{}) []interface{} {
	head := args[0]
	switch head.(type) {
	case func() (string, []interface{}):
		msg, args := head.(func() (string, []interface{}))()
		args = unwrap(args...)
		return append([]interface{}{msg}, args...)
	case func() []interface{}:
		args = unwrap(head.(func() []interface{})()...)
	case func(...interface{}) []interface{}:
		args = unwrap(head.(func(...interface{}) []interface{})(args[1:]...)...)
	}
	return args
}

func (l *logger) New(name string) Logger {
	lg := Logger(&logger{
		level:    levels.INHERIT,
		name:     name,
		enabled:  make(map[levels.LogLevel]bool),
		appender: nil,
		children: make([]Logger, 0),
		parent:   l,
	})
	l.children = append(l.children, lg)
	return lg
}

func (l *logger) GetLogger(name string) Logger {
	bits := strings.Split(name, ".")

	if l.name == bits[0] {
		if len(bits) == 1 {
			return l
		}

		child := bits[1]
		n := strings.Join(bits[1:], ".")
		for _, c := range l.children {
			if c.Name() == child {
				return c.GetLogger(n)
			}
		}

		lg := l.New(child)
		return lg.GetLogger(n)
	}
	lg := l.New(bits[0])
	return lg.GetLogger(name)
}

type stringer interface {
	String() string
}

func (l *logger) write(level levels.LogLevel, params ...interface{}) {
	a := l.Appender()
	if a != nil {
		if s, ok := params[0].(string); ok {
			a.Write(level, s, params[1:]...)
		} else if s, ok := params[0].(stringer); ok {
			a.Write(level, s.String(), params[1:]...)
		} else {
			a.Write(level, fmt.Sprintf("%s", params[0]), params[1:]...)
		}
	}
}

func (l *logger) Appender() Appender {
	if a := l.appender; a != nil {
		return a
	}
	if l.parent != nil {
		if a := l.parent.Appender(); a != nil {
			return a
		}
	}
	return nil
}

func (l *logger) Log(level levels.LogLevel, params ...interface{}) {
	if !l.Enabled()[level] {
		return
	}
	l.write(level, unwrap(params...)...)

	if l.ExitOnFatal && level == levels.FATAL {
		os.Exit(1)
	}
}

func (l *logger) Level() levels.LogLevel {
	if l.level == levels.INHERIT {
		return l.parent.Level()
	}
	return l.level
}

func (l *logger) Enabled() map[levels.LogLevel]bool {
	if l.level == levels.INHERIT {
		return l.parent.Enabled()
	}
	return l.enabled
}

func (l *logger) Name() string {
	return l.name
}

func (l *logger) FullName() string {
	n := l.name
	if l.parent != nil {
		p := l.parent.FullName()
		if len(p) > 0 {
			n = l.parent.FullName() + "." + n
		}
	}
	return n
}

func (l *logger) Children() []Logger {
	return l.children
}

func (l *logger) Parent() Logger {
	return l.parent
}

func (l *logger) SetLevel(level levels.LogLevel) {
	l.level = level
	for k := range levels.LogLevelsToString {
		if k <= level {
			l.enabled[k] = true
		} else {
			l.enabled[k] = false
		}
	}
}

func (l *logger) SetAppender(appender Appender) {
	l.appender = appender
}

func (l *logger) Debug(params ...interface{})   { l.Log(levels.DEBUG, params...) }
func (l *logger) Info(params ...interface{})    { l.Log(levels.INFO, params...) }
func (l *logger) Warn(params ...interface{})    { l.Log(levels.WARN, params...) }
func (l *logger) Error(params ...interface{})   { l.Log(levels.ERROR, params...) }
func (l *logger) Trace(params ...interface{})   { l.Log(levels.TRACE, params...) }
func (l *logger) Printf(params ...interface{})  { l.Log(levels.INFO, params...) }
func (l *logger) Println(params ...interface{}) { l.Log(levels.INFO, params...) }
func (l *logger) Fatal(params ...interface{})   { l.Log(levels.FATAL, params...) }
func (l *logger) Fatalf(params ...interface{})  { l.Log(levels.FATAL, params...) }