package log

import (
	"errors"
	"fmt"
	"log"
	"os"
	"path"
	"runtime"
	"strings"
	"sync"
	"time"
)

// levels
const (
	debugLevel   = 0
	releaseLevel = 1
	errorLevel   = 2
	fatalLevel   = 3
	warnLevel    = 4
	DEBUG_LEVEL  = 0
)

const (
	printDebugLevel   = "[debug  ] "
	printReleaseLevel = "[release] "
	printErrorLevel   = "[error  ] "
	printFatalLevel   = "[fatal  ] "
	printWarnLevel    = "[warn   ] "
)

type Logger struct {
	level      int
	baseLogger *log.Logger
	baseFile   *os.File
}

var gloggerLock sync.Mutex
var gLogger, _ = New("debug", "", log.LstdFlags)

func New(strLevel string, pathname string, flag int) (*Logger, error) {
	gloggerLock.Lock()
	defer gloggerLock.Unlock()
	// level
	var level int
	switch strings.ToLower(strLevel) {
	case "debug":
		level = debugLevel
	case "release":
		level = releaseLevel
	case "error":
		level = errorLevel
	case "fatal":
		level = fatalLevel
	case "warn":
		level = warnLevel
	default:
		return nil, errors.New("unknown level: " + strLevel)
	}

	// logger
	var baseLogger *log.Logger
	var baseFile *os.File
	if pathname != "" {
		now := time.Now()
		filename := fmt.Sprintf("%d%02d%02d.log",
			now.Year(),
			now.Month(),
			now.Day(),
		)

		file, err := os.Create(path.Join(pathname, filename))
		if err != nil {
			return nil, err
		}
		baseLogger = log.New(file, "", flag)
		baseFile = file
	} else {
		baseLogger = log.New(os.Stdout, "", flag)
	}

	// new
	logger := new(Logger)
	logger.level = level
	logger.baseLogger = baseLogger
	logger.baseFile = baseFile

	return logger, nil
}

func NewDailyLog(now time.Time, Level int, pathname string, flag int) error {
	gloggerLock.Lock()
	defer gloggerLock.Unlock()
	// level
	var level int
	switch Level {
	case debugLevel:
		level = debugLevel
	case releaseLevel:
		level = releaseLevel
	case errorLevel:
		level = errorLevel
	case fatalLevel:
		level = fatalLevel
	case warnLevel:
		level = warnLevel
	default:
		return fmt.Errorf("unknown level: %d", Level)
	}

	// logger
	var baseLogger *log.Logger
	var baseFile *os.File
	if pathname != "" {
		filename := fmt.Sprintf("%d%02d%02d.log",
			now.Year(),
			now.Month(),
			now.Day())

		file, err := os.OpenFile(path.Join(pathname, filename), os.O_RDWR|os.O_APPEND|os.O_CREATE, 0664)
		if err != nil {
			return err
		}

		baseLogger = log.New(file, "", flag)
		baseFile = file
	} else {
		baseLogger = log.New(os.Stdout, "", flag)
	}

	// new
	gLogger.level = level
	gLogger.baseLogger = baseLogger
	gLogger.baseFile = baseFile

	return nil
}

func BindLoggerToFile(strLevel string, pathName string, flag int) (*Logger, error) {
	gloggerLock.Lock()
	defer gloggerLock.Unlock()
	var level int
	switch strings.ToLower(strLevel) {
	case "debug":
		level = debugLevel
	case "release":
		level = releaseLevel
	case "error":
		level = errorLevel
	case "fatal":
		level = fatalLevel
	case "warn":
		level = warnLevel
	default:
		return nil, errors.New("unknown level: " + strLevel)
	}

	var baseLogger *log.Logger
	var baseFile *os.File

	file, err := os.OpenFile(pathName, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0664)
	if err != nil {
		return nil, err
	}
	baseLogger = log.New(file, "", flag)
	baseFile = file

	gLogger.level = level
	gLogger.baseLogger = baseLogger
	gLogger.baseFile = baseFile

	return gLogger, nil
}

// It's dangerous to call the method on logging
func (logger *Logger) Close() {
	if logger.baseFile != nil {
		logger.baseFile.Close()
	}

	logger.baseLogger = nil
	logger.baseFile = nil
}

func (logger *Logger) doPrintf(level int, printLevel string, format string, a ...interface{}) {
	if level < logger.level {
		return
	}
	if logger.baseLogger == nil {
		return
	}

	format = printLevel + format
	logger.baseLogger.Output(3, fmt.Sprintf(format, a...))

	if level == fatalLevel {
		os.Exit(1)
	}
}

func (logger *Logger) Debug(format string, a ...interface{}) {
	logger.doPrintf(debugLevel, printDebugLevel, format, a...)
}

func (logger *Logger) Release(format string, a ...interface{}) {
	logger.doPrintf(releaseLevel, printReleaseLevel, format, a...)
}

func (logger *Logger) Error(format string, a ...interface{}) {
	logger.doPrintf(errorLevel, printErrorLevel, format, a...)
}

func (logger *Logger) Fatal(format string, a ...interface{}) {
	logger.doPrintf(fatalLevel, printFatalLevel, format, a...)
}
func (logger *Logger) Warn(format string, a ...interface{}) {
	logger.doPrintf(warnLevel, printWarnLevel, format, a...)
}

// It's dangerous to call the method on logging
func Export(logger *Logger) {
	if logger != nil {
		gloggerLock.Lock()
		defer gloggerLock.Unlock()
		gLogger = logger
	}
}

func Debug(format string, a ...interface{}) {
	gloggerLock.Lock()
	defer gloggerLock.Unlock()
	if gLogger == nil {
		return
	}
	gLogger.doPrintf(debugLevel, printDebugLevel, format, a...)
}

func Release(format string, a ...interface{}) {
	gloggerLock.Lock()
	defer gloggerLock.Unlock()
	if gLogger == nil {
		return
	}
	gLogger.doPrintf(releaseLevel, printReleaseLevel, format, a...)
}

func Warn(format string, a ...interface{}) {
	gloggerLock.Lock()
	defer gloggerLock.Unlock()
	if gLogger == nil {
		return
	}
	gLogger.doPrintf(warnLevel, printWarnLevel, format, a...)
}

func Error(format string, a ...interface{}) {
	gloggerLock.Lock()
	defer gloggerLock.Unlock()
	if gLogger == nil {
		return
	}
	// Capture the stack trace
	stack := make([]byte, 1024)
	length := runtime.Stack(stack, false)
	format = fmt.Sprintf("%s\nStack trace:\n%s", format, stack[:length])

	gLogger.doPrintf(errorLevel, printErrorLevel, format, a...)
}

func Fatal(format string, a ...interface{}) {
	gloggerLock.Lock()
	defer gloggerLock.Unlock()
	if gLogger == nil {
		return
	}
	gLogger.doPrintf(fatalLevel, printFatalLevel, format, a...)
}

func Close() {
	gloggerLock.Lock()
	defer gloggerLock.Unlock()
	gLogger.Close()
}
