package log

import (
	"encoding/json"
	"fmt"
	"io"
	"os"
	"sync"
	"time"

	"go.uber.org/zap"
	"golang.org/x/term"
)

var mu sync.Mutex
var wr io.Writer
var tty bool
var LogJSON = false
var logger *zap.SugaredLogger

// Level is the log level
// 0: silent  - do not log
// 1: normal  - show everything except debug and warn
// 2: verbose - show everything except debug
// 3: very verbose - show everything
var Level = 1

// SetOutput sets the output of the logger
func SetOutput(w io.Writer) {
	f, ok := w.(*os.File)
	tty = ok && term.IsTerminal(int(f.Fd()))
	wr = w
}

// Build a zap logger from default or custom config
func Build(c string) error {
	if c == "" {
		zcfg := zap.NewProductionConfig()

		// to be able to filter with Tile38 levels
		zcfg.Level.SetLevel(zap.DebugLevel)
		// disable caller because caller is always log.go
		zcfg.DisableCaller = true

		core, err := zcfg.Build()
		if err != nil {
			return err
		}
		defer core.Sync()
		logger = core.Sugar()
	} else {
		var zcfg zap.Config
		err := json.Unmarshal([]byte(c), &zcfg)
		if err != nil {
			return err
		}

		// to be able to filter with Tile38 levels
		zcfg.Level.SetLevel(zap.DebugLevel)
		// disable caller because caller is always log.go
		zcfg.DisableCaller = true

		core, err := zcfg.Build()
		if err != nil {
			return err
		}
		defer core.Sync()
		logger = core.Sugar()
	}
	return nil
}

// Set a zap logger
func Set(sl *zap.SugaredLogger) {
	logger = sl
}

// Get a zap logger
func Get() *zap.SugaredLogger {
	return logger
}

func init() {
	SetOutput(os.Stderr)
}

// Output retuns the output writer
func Output() io.Writer {
	return wr
}

func log(level int, tag, color string, formatted bool, format string, args ...interface{}) {
	if Level < level {
		return
	}
	var msg string
	if formatted {
		msg = fmt.Sprintf(format, args...)
	} else {
		msg = fmt.Sprint(args...)
	}
	if LogJSON {
		switch tag {
		case "ERRO":
			logger.Error(msg)
		case "FATA":
			logger.Fatal(msg)
		case "WARN":
			logger.Warn(msg)
		case "DEBU":
			logger.Debug(msg)
		default:
			logger.Info(msg)
		}
		return
	}
	s := []byte(time.Now().Format("2006/01/02 15:04:05"))
	s = append(s, ' ')
	if tty {
		s = append(s, color...)
	}
	s = append(s, '[')
	s = append(s, tag...)
	s = append(s, ']')
	if tty {
		s = append(s, "\x1b[0m"...)
	}
	s = append(s, ' ')
	s = append(s, msg...)
	if s[len(s)-1] != '\n' {
		s = append(s, '\n')
	}
	mu.Lock()
	wr.Write(s)
	mu.Unlock()
}

var emptyFormat string

// Infof ...
func Infof(format string, args ...interface{}) {
	if Level >= 1 {
		log(1, "INFO", "\x1b[36m", true, format, args...)
	}
}

// Info ...
func Info(args ...interface{}) {
	if Level >= 1 {
		log(1, "INFO", "\x1b[36m", false, emptyFormat, args...)
	}
}

// HTTPf ...
func HTTPf(format string, args ...interface{}) {
	if Level >= 1 {
		log(1, "HTTP", "\x1b[1m\x1b[30m", true, format, args...)
	}
}

// HTTP ...
func HTTP(args ...interface{}) {
	if Level >= 1 {
		log(1, "HTTP", "\x1b[1m\x1b[30m", false, emptyFormat, args...)
	}
}

// Errorf ...
func Errorf(format string, args ...interface{}) {
	if Level >= 1 {
		log(1, "ERRO", "\x1b[1m\x1b[31m", true, format, args...)
	}
}

// Error ..
func Error(args ...interface{}) {
	if Level >= 1 {
		log(1, "ERRO", "\x1b[1m\x1b[31m", false, emptyFormat, args...)
	}
}

// Warnf ...
func Warnf(format string, args ...interface{}) {
	if Level >= 1 {
		log(2, "WARN", "\x1b[33m", true, format, args...)
	}
}

// Warn ...
func Warn(args ...interface{}) {
	if Level >= 1 {
		log(2, "WARN", "\x1b[33m", false, emptyFormat, args...)
	}
}

// Debugf ...
func Debugf(format string, args ...interface{}) {
	if Level >= 3 {
		log(3, "DEBU", "\x1b[35m", true, format, args...)
	}
}

// Debug ...
func Debug(args ...interface{}) {
	if Level >= 3 {
		log(3, "DEBU", "\x1b[35m", false, emptyFormat, args...)
	}
}

// Printf ...
func Printf(format string, args ...interface{}) {
	Infof(format, args...)
}

// Print ...
func Print(args ...interface{}) {
	Info(args...)
}

// Fatalf ...
func Fatalf(format string, args ...interface{}) {
	log(1, "FATA", "\x1b[31m", true, format, args...)
	os.Exit(1)
}

// Fatal ...
func Fatal(args ...interface{}) {
	log(1, "FATA", "\x1b[31m", false, emptyFormat, args...)
	os.Exit(1)
}