package log

import (
	"encoding/json"
	"fmt"
	"os"
	"runtime"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/siddontang/go-log/loggers"
)

const (
	timeFormat     = "2006/01/02 15:04:05"
	maxBufPoolSize = 16
)

// Logger flag
const (
	Ltime  = 1 << iota // time format "2006/01/02 15:04:05"
	Lfile              // file.go:123
	Llevel             // [Trace|Debug|Info...]
)

// Level type
type Level int

// Log level, from low to high, more high means more serious
const (
	LevelTrace Level = iota
	LevelDebug
	LevelInfo
	LevelWarn
	LevelError
	LevelFatal
)

// String returns level String
func (l Level) String() string {
	switch l {
	case LevelTrace:
		return "trace"
	case LevelDebug:
		return "debug"
	case LevelInfo:
		return "info"
	case LevelWarn:
		return "warn"
	case LevelError:
		return "error"
	case LevelFatal:
		return "fatal"
	}
	// return default info
	return "info"
}

// Logger is the logger to record log
type Logger struct {
	// TODO: support logger.Contextual
	loggers.Advanced

	level Level
	flag  int

	hLock   sync.Mutex
	handler Handler

	bufs sync.Pool
}

// New creates a logger with specified handler and flag
func New(handler Handler, flag int) *Logger {
	var l = new(Logger)

	l.level = LevelInfo
	l.handler = handler

	l.flag = flag

	l.bufs = sync.Pool{
		New: func() interface{} {
			return make([]byte, 0, 1024)
		},
	}

	return l
}

// NewDefault creates default logger with specified handler and flag: Ltime|Lfile|Llevel
func NewDefault(handler Handler) *Logger {
	return New(handler, Ltime|Lfile|Llevel)
}

func newStdHandler() *StreamHandler {
	h, _ := NewStreamHandler(os.Stdout)
	return h
}

// Close closes the logger
func (l *Logger) Close() {
	l.hLock.Lock()
	defer l.hLock.Unlock()
	l.handler.Close()
}

// SetLevel sets log level, any log level less than it will not log
func (l *Logger) SetLevel(level Level) {
	l.level = level
}

// SetLevelByName sets log level by name
func (l *Logger) SetLevelByName(name string) {
	level := LevelInfo
	switch strings.ToLower(name) {
	case "trace":
		level = LevelTrace
	case "debug":
		level = LevelDebug
	case "warn", "warning":
		level = LevelWarn
	case "error":
		level = LevelError
	case "fatal":
		level = LevelFatal
	default:
		level = LevelInfo
	}

	l.SetLevel(level)
}

// Output records the log with special callstack depth and log level.
func (l *Logger) Output(callDepth int, level Level, msg string) {
	if l.level > level {
		return
	}

	buf := l.bufs.Get().([]byte)
	buf = buf[0:0]
	defer l.bufs.Put(buf)

	if l.flag&Ltime > 0 {
		now := time.Now().Format(timeFormat)
		buf = append(buf, '[')
		buf = append(buf, now...)
		buf = append(buf, "] "...)
	}

	if l.flag&Llevel > 0 {
		buf = append(buf, '[')
		buf = append(buf, level.String()...)
		buf = append(buf, "] "...)
	}

	if l.flag&Lfile > 0 {
		_, file, line, ok := runtime.Caller(callDepth)
		if !ok {
			file = "???"
			line = 0
		} else {
			for i := len(file) - 1; i > 0; i-- {
				if file[i] == '/' {
					file = file[i+1:]
					break
				}
			}
		}

		buf = append(buf, file...)
		buf = append(buf, ':')

		buf = strconv.AppendInt(buf, int64(line), 10)
		buf = append(buf, ' ')
	}

	buf = append(buf, msg...)
	if len(msg) == 0 || msg[len(msg)-1] != '\n' {
		buf = append(buf, '\n')
	}

	l.hLock.Lock()
	l.handler.Write(buf)
	l.hLock.Unlock()
}

// Output json format records the log with special callstack depth and log level.
func (l *Logger) OutputJson(callDepth int, level Level, body interface{}) {
	if l.level > level {
		return
	}

	buf := l.bufs.Get().([]byte)
	buf = buf[0:0]
	defer l.bufs.Put(buf)

	type JsonLog struct {
		Time string `json:"log_time"`
		Level string `json:"log_level"`
		File string `json:"log_file"`
		Line string `json:"log_line"`
		Body interface{} `json:"log_body"`
	}

	var jsonlog JsonLog
	if l.flag&Ltime > 0 {
		now := time.Now().Format(timeFormat)
		jsonlog.Time = now
	}

	if l.flag&Llevel > 0 {
		jsonlog.Level = level.String()
	}

	if l.flag&Lfile > 0 {
		_, file, line, ok := runtime.Caller(callDepth)
		if !ok {
			file = "???"
			line = 0
		} else {
			for i := len(file) - 1; i > 0; i-- {
				if file[i] == '/' {
					file = file[i+1:]
					break
				}
			}
		}

		jsonlog.File = file
		jsonlog.Line = string(strconv.AppendInt(buf, int64(line), 10))
	}

	jsonlog.Body = body

	msg, _ := json.Marshal(jsonlog)
	msg = append(msg, '\n')

	l.hLock.Lock()
	l.handler.Write(msg)
	l.hLock.Unlock()
}

// Fatal records the log with fatal level and exits
func (l *Logger) Fatal(args ...interface{}) {
	l.Output(2, LevelFatal, fmt.Sprint(args...))
	os.Exit(1)
}

// Fatalf records the log with fatal level and exits
func (l *Logger) Fatalf(format string, args ...interface{}) {
	l.Output(2, LevelFatal, fmt.Sprintf(format, args...))
	os.Exit(1)
}

// Fatalln records the log with fatal level and exits
func (l *Logger) Fatalln(args ...interface{}) {
	l.Output(2, LevelFatal, fmt.Sprintln(args...))
	os.Exit(1)
}

// Panic records the log with fatal level and panics
func (l *Logger) Panic(args ...interface{}) {
	msg := fmt.Sprint(args...)
	l.Output(2, LevelError, msg)
	panic(msg)
}

// Panicf records the log with fatal level and panics
func (l *Logger) Panicf(format string, args ...interface{}) {
	msg := fmt.Sprintf(format, args...)
	l.Output(2, LevelError, msg)
	panic(msg)
}

// Panicln records the log with fatal level and panics
func (l *Logger) Panicln(args ...interface{}) {
	msg := fmt.Sprintln(args...)
	l.Output(2, LevelError, msg)
	panic(msg)
}

// Print records the log with trace level
func (l *Logger) Print(args ...interface{}) {
	l.Output(2, LevelTrace, fmt.Sprint(args...))
}

// Printf records the log with trace level
func (l *Logger) Printf(format string, args ...interface{}) {
	l.Output(2, LevelTrace, fmt.Sprintf(format, args...))
}

// Println records the log with trace level
func (l *Logger) Println(args ...interface{}) {
	l.Output(2, LevelTrace, fmt.Sprintln(args...))
}

// Debug records the log with debug level
func (l *Logger) Debug(args ...interface{}) {
	l.Output(2, LevelDebug, fmt.Sprint(args...))
}

// Debugf records the log with debug level
func (l *Logger) Debugf(format string, args ...interface{}) {
	l.Output(2, LevelDebug, fmt.Sprintf(format, args...))
}

// Debugln records the log with debug level
func (l *Logger) Debugln(args ...interface{}) {
	l.Output(2, LevelDebug, fmt.Sprintln(args...))
}

// Error records the log with error level
func (l *Logger) Error(args ...interface{}) {
	l.Output(2, LevelError, fmt.Sprint(args...))
}

// Errorf records the log with error level
func (l *Logger) Errorf(format string, args ...interface{}) {
	l.Output(2, LevelError, fmt.Sprintf(format, args...))
}

// Errorln records the log with error level
func (l *Logger) Errorln(args ...interface{}) {
	l.Output(2, LevelError, fmt.Sprintln(args...))
}

// Info records the log with info level
func (l *Logger) Info(args ...interface{}) {
	l.Output(2, LevelInfo, fmt.Sprint(args...))
}

// Infof records the log with info level
func (l *Logger) Infof(format string, args ...interface{}) {
	l.Output(2, LevelInfo, fmt.Sprintf(format, args...))
}

// Infoln records the log with info level
func (l *Logger) Infoln(args ...interface{}) {
	l.Output(2, LevelInfo, fmt.Sprintln(args...))
}

// Warn records the log with warn level
func (l *Logger) Warn(args ...interface{}) {
	l.Output(2, LevelWarn, fmt.Sprint(args...))
}

// Warnf records the log with warn level
func (l *Logger) Warnf(format string, args ...interface{}) {
	l.Output(2, LevelWarn, fmt.Sprintf(format, args...))
}

// Warnln records the log with warn level
func (l *Logger) Warnln(args ...interface{}) {
	l.Output(2, LevelWarn, fmt.Sprintln(args...))
}
