package logger

import (
	"fmt"
	"log/syslog"
	"os"
	"sync"

	"github.com/davecgh/go-spew/spew"
)

type PackageLog interface {
	Printf(level LogLevel, format string, args ...interface{})
	Print(level LogLevel, args ...interface{})
	Debugf(format string, args ...interface{})
	Debug(args ...interface{})
	Infof(format string, args ...interface{})
	Info(args ...interface{})
	Notifyf(format string, args ...interface{})
	Notify(args ...interface{})
	Warningf(format string, args ...interface{})
	Warnf(format string, args ...interface{})
	Warning(args ...interface{})
	Warn(args ...interface{})
	Errorf(format string, args ...interface{})
	Error(args ...interface{})
	Panicf(format string, args ...interface{})
	Panic(args ...interface{})
	Fatalf(format string, args ...interface{})
	Fatal(args ...interface{})
}

type Package struct {
	sync.RWMutex
	parent      *Logger
	packageName string
	level       LogLevel
	syslog      *syslog.Writer
}

// Static cast to verify that object implement interface.
var _ PackageLog = &Package{}

func (v *Package) Close() error {
	v.Lock()
	defer v.Unlock()
	if v.syslog != nil {
		err := v.syslog.Close()
		v.syslog = nil
		if err != nil {
			return err
		}
	}
	return nil
}

func (v *Package) SetLogLevel(level LogLevel) {
	v.Lock()
	defer v.Unlock()
	v.level = level
}

func (v *Package) GetLogLevel() LogLevel {
	v.RLock()
	defer v.RUnlock()
	return v.level
}

func (v *Package) getSyslog(level LogLevel, options FormatOptions,
	appName string) (*syslog.Writer, error) {
	v.Lock()
	defer v.Unlock()
	if v.syslog == nil {
		tag := metaFmtStr(false, level, options, appName,
			v.packageName, "", "%[2]s-%[3]s")
		sl, err := syslog.New(syslog.LOG_DEBUG, tag)
		if err != nil {
			err = spew.Errorf("Failed to connect to syslog: %v\n", err)
			return nil, err
		}
		v.syslog = sl
	}
	return v.syslog, nil
}

func (v *Package) writeToSyslog(options FormatOptions,
	level LogLevel, appName string, msg string) error {

	sl, err := v.getSyslog(level, options, appName)
	if err != nil {
		return err
	}
	switch level {
	case DebugLevel:
		return sl.Debug(msg)
	case InfoLevel:
		return sl.Info(msg)
	case NotifyLevel:
		return sl.Notice(msg)
	case WarnLevel:
		return sl.Warning(msg)
	case ErrorLevel:
		return sl.Err(msg)
	case PanicLevel:
		return sl.Crit(msg)
	case FatalLevel:
		return sl.Emerg(msg)
	default:
		return sl.Debug(msg)
	}
}

type printLog func(log *Log, msg interface{})
type getMessage func(colored bool) interface{}

func printLogs(logs []*Log, level LogLevel, prnt printLog, getMsg getMessage) {
	// Console and custom logs output
	for _, log := range logs {
		if log.level >= level {
			prnt(log, getMsg(log.colored))
		}
	}
}

func (v *Package) print(level LogLevel, msg string) {
	lvl := v.GetLogLevel()
	if lvl >= level {
		appName := getApplicationName()
		logs := v.parent.getLogs()
		options := v.parent.GetFormatOptions()
		out1 := FormatMessage(options, level, v.packageName, msg, false)
		// File output
		if lf := v.parent.GetLogFileInfo(); lf != nil {
			rotateMaxSize := v.parent.GetRotateMaxSize()
			rotateMaxCount := v.parent.GetRotateMaxCount()
			if err := lf.writeToFile(out1, rotateMaxSize, rotateMaxCount); err != nil {
				err = spew.Errorf("Failed to report syslog message %q: %v\n", out1, err)
				printLogs(logs, FatalLevel,
					func(log *Log, msg interface{}) {
						log.log.Fatal(msg)
					},
					func(colored bool) interface{} {
						return err
					})
			}
		}
		// Syslog output
		if v.parent.GetSyslogEnabled() {
			if err := v.writeToSyslog(options, level, appName, msg); err != nil {
				err = spew.Errorf("Failed to report syslog message %q: %v\n", msg, err)
				printLogs(logs, FatalLevel,
					func(log *Log, msg interface{}) {
						log.log.Fatal(msg)
					},
					func(colored bool) interface{} {
						return err
					})
			}
		}
		// Console and custom logs output
		outColored1 := FormatMessage(options, level, v.packageName, msg, true)
		printLogs(logs, level,
			func(log *Log, msg interface{}) {
				log.log.Print(msg)
			},
			func(colored bool) interface{} {
				if colored {
					return outColored1 + fmt.Sprintln()
				} else {
					return out1 + fmt.Sprintln()
				}
			})
		// Check critical events
		if level == PanicLevel {
			panic(out1)
		} else if level == FatalLevel {
			os.Exit(1)
		}
	}
}

func (v *Package) Printf(level LogLevel, format string, args ...interface{}) {
	lvl := v.GetLogLevel()
	if lvl >= level {
		msg := spew.Sprintf(format, args...)
		v.print(level, msg)
	}
}

func (v *Package) Print(level LogLevel, args ...interface{}) {
	lvl := v.GetLogLevel()
	if lvl >= level {
		msg := fmt.Sprint(args...)
		v.print(level, msg)
	}
}

func (v *Package) Debugf(format string, args ...interface{}) {
	v.Printf(DebugLevel, format, args...)
}

func (v *Package) Debug(args ...interface{}) {
	v.Print(DebugLevel, args...)
}

func (v *Package) Infof(format string, args ...interface{}) {
	v.Printf(InfoLevel, format, args...)
}

func (v *Package) Info(args ...interface{}) {
	v.Print(InfoLevel, args...)
}

func (v *Package) Notifyf(format string, args ...interface{}) {
	v.Printf(NotifyLevel, format, args...)
}

func (v *Package) Notify(args ...interface{}) {
	v.Print(NotifyLevel, args...)
}

func (v *Package) Warningf(format string, args ...interface{}) {
	v.Printf(WarnLevel, format, args...)
}

func (v *Package) Warnf(format string, args ...interface{}) {
	v.Printf(WarnLevel, format, args...)
}

func (v *Package) Warning(args ...interface{}) {
	v.Print(WarnLevel, args...)
}

func (v *Package) Warn(args ...interface{}) {
	v.Print(WarnLevel, args...)
}

func (v *Package) Errorf(format string, args ...interface{}) {
	v.Printf(ErrorLevel, format, args...)
}

func (v *Package) Error(args ...interface{}) {
	v.Print(ErrorLevel, args...)
}

func (v *Package) Panicf(format string, args ...interface{}) {
	v.Printf(PanicLevel, format, args...)
}

func (v *Package) Panic(args ...interface{}) {
	v.Print(PanicLevel, args...)
}

func (v *Package) Fatalf(format string, args ...interface{}) {
	v.Printf(FatalLevel, format, args...)
}

func (v *Package) Fatal(args ...interface{}) {
	v.Print(FatalLevel, args...)
}
