package icslog

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"runtime/debug"
	"strconv"
	"strings"
	"sync"
	"time"

	"gitlab.com/ics_cinnamon/voiceStatistics/icsconf"
	"gitlab.com/ics_cinnamon/voiceStatistics/icserror"
	"gitlab.com/ics_cinnamon/voiceStatistics/icsutil"
)

type IcsLog struct {
	logger *log.Logger

	Output      int
	Level       int
	Path        string
	LogFileName string
	LogFile     *os.File
	DiskLimit   int
	DelDay      int
	DelHour     int
	isRotate    bool
	logSize     int
	rotateNum   int

	buf         bytes.Buffer
	CurrentDate time.Time
	conf        *icsconf.LogConfig
	//conf        *icsconf.IcsConfig
	IsDiskFull bool

	M *sync.Mutex
}

//default max log size 30 megabytes
const DEFAULT_LOG_MAX_SIZE = 30

//default log rotate num
const DEFAULT_LOG_ROTATE_NUM = 10

//max log rotate num
const MAX_LOG_ROTATE_NUM = 99

const (
	LOG_LEVEL_DEBUG2 = iota
	LOG_LEVEL_DEBUG1
	LOG_LEVEL_DEBUG
	LOG_LEVEL_STATIS
	LOG_LEVEL_INFO
	LOG_LEVEL_WARN
	LOG_LEVEL_ERROR
	LOG_LEVEL_FATAL
	LOG_LEVEL_MAX
)

//log output
const (
	LOG_OUTPUT_FILE = 1 << (32 - 1 - iota)
	LOG_OUTPUT_STDOUT
)

var IcsLogLevelStr [LOG_LEVEL_MAX]string

var gIcsLog *IcsLog
var onceLog sync.Once

func init() {
	IcsLogLevelStr[LOG_LEVEL_DEBUG2] = "DEBUG2"
	IcsLogLevelStr[LOG_LEVEL_DEBUG1] = "DEBUG1"
	IcsLogLevelStr[LOG_LEVEL_DEBUG] = "DEBUG"
	IcsLogLevelStr[LOG_LEVEL_STATIS] = "STATISTICS"
	IcsLogLevelStr[LOG_LEVEL_INFO] = "INFO"
	IcsLogLevelStr[LOG_LEVEL_WARN] = "WARN"
	IcsLogLevelStr[LOG_LEVEL_ERROR] = "ERROR"
	IcsLogLevelStr[LOG_LEVEL_FATAL] = "FATAL"
}

//if fail, return icserror
func NewIcsLog(conf *icsconf.LogConfig, level int, output int, path string, disklimit int) (*IcsLog, *icserror.IcsError) {
	//func NewIcsLog(conf *icsconf.IcsConfig, level int, output int, path string, disklimit int) (*IcsLog, *icserror.IcsError) {
	//func NewIcsLog(conf *icsconf.IcsConfig, level interface{}, output interface{}, path string, disklimit int) (*IcsLog, *icserror.IcsError) {
	var reterr *icserror.IcsError = nil

	//singleton
	onceLog.Do(func() {
		gIcsLog = &IcsLog{
			conf:       conf,
			Level:      level,
			Output:     output,
			Path:       path,
			DiskLimit:  disklimit,
			IsDiskFull: false,
			logSize:    DEFAULT_LOG_MAX_SIZE,
			rotateNum:  DEFAULT_LOG_ROTATE_NUM,
		}

		gIcsLog.M = &sync.Mutex{}
		gIcsLog.isRotate = strings.Compare("YES", strings.ToUpper(gIcsLog.conf.RotateConfig.YesNo)) == 0
		//gIcsLog.isRotate = strings.Compare("YES", strings.ToUpper(gIcsLog.conf.LogConfig.RotateConfig.YesNo)) == 0
		var serr error
		gIcsLog.logSize, serr = strconv.Atoi(gIcsLog.conf.RotateConfig.Size)
		//gIcsLog.logSize, serr = strconv.Atoi(gIcsLog.conf.LogConfig.RotateConfig.Size)
		if serr != nil {
			gIcsLog.logSize = DEFAULT_LOG_MAX_SIZE
		}
		gIcsLog.rotateNum, serr = strconv.Atoi(gIcsLog.conf.RotateConfig.Num)
		//gIcsLog.rotateNum, serr = strconv.Atoi(gIcsLog.conf.LogConfig.RotateConfig.Num)
		if serr != nil {
			gIcsLog.rotateNum = DEFAULT_LOG_ROTATE_NUM
		}
		if gIcsLog.rotateNum > MAX_LOG_ROTATE_NUM {
			gIcsLog.rotateNum = MAX_LOG_ROTATE_NUM
		}

		gIcsLog.logger = log.New(&gIcsLog.buf, "", log.Ldate|log.Ltime|log.Lmicroseconds|log.Lshortfile)

		gIcsLog.CurrentDate = time.Now()
		gIcsLog.DelDay = conf.DelConfig.DelDay
		gIcsLog.DelHour = conf.DelConfig.DelHour

		//make log file - icsvg.log-yyyymmdd
		yyyy, mm, dd := gIcsLog.CurrentDate.Date()
		gIcsLog.LogFileName = fmt.Sprintf("%s/icsvs.log-%d%02d%02d", gIcsLog.Path, yyyy, mm, dd)

		stat, err := os.Stat(gIcsLog.LogFileName)
		if err == nil {
			if gIcsLog.checkLogRotate() && stat.Size()/ONEMB >= int64(gIcsLog.logSize) {
				rotateNum := gIcsLog.getTodayLogFileNum()
				//fmt.Println("rotate num:", rotateNum)
				if rotateNum > 0 && rotateNum < gIcsLog.rotateNum {
					gIcsLog.shiftLogFiles(rotateNum)

					var oerr error
					oerr = os.MkdirAll(filepath.Dir(gIcsLog.LogFileName), 0777)
					if oerr != nil {
						icserror.ICSERRMakeDir.SetError(oerr)
						icserror.ICSERRMakeDir.PrintWithCaller(0)
						return
					}

					gIcsLog.LogFile, oerr = os.OpenFile(gIcsLog.LogFileName, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
					if oerr != nil {
						//fmt.Println(gIcsLog.LogFileName)
						icserror.ICSERRFileOpen.SetError(oerr)
						reterr = icserror.ICSERRFileOpen
						reterr.PrintWithCaller(0)
						return
					}
				}
			} else if stat.Size()/ONEMB < int64(gIcsLog.logSize) {
				var oerr error
				oerr = os.MkdirAll(filepath.Dir(gIcsLog.LogFileName), 0777)
				if oerr != nil {
					icserror.ICSERRMakeDir.SetError(oerr)
					icserror.ICSERRMakeDir.PrintWithCaller(0)
					return
				}

				gIcsLog.LogFile, oerr = os.OpenFile(gIcsLog.LogFileName, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
				if oerr != nil {
					//fmt.Println(gIcsLog.LogFileName)
					icserror.ICSERRFileOpen.SetError(oerr)
					reterr = icserror.ICSERRFileOpen
					reterr.PrintWithCaller(0)
					return
				}
			}
		} else {
			var oerr error
			oerr = os.MkdirAll(filepath.Dir(gIcsLog.LogFileName), 0777)
			if oerr != nil {
				icserror.ICSERRMakeDir.SetError(oerr)
				icserror.ICSERRMakeDir.PrintWithCaller(0)
				return
			}

			gIcsLog.LogFile, oerr = os.OpenFile(gIcsLog.LogFileName, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
			if oerr != nil {
				//fmt.Println(gIcsLog.LogFileName)
				icserror.ICSERRFileOpen.SetError(oerr)
				reterr = icserror.ICSERRFileOpen
				reterr.PrintWithCaller(0)
				return
			}
		}

	})

	return gIcsLog, reterr
}

func GetIcsLog() *IcsLog {
	return gIcsLog
}

func GetLogLevelID(str string) int {
	switch str {
	case "DEBUG2":
		return LOG_LEVEL_DEBUG2
	case "DEBUG1":
		return LOG_LEVEL_DEBUG1
	case "DEBUG":
		return LOG_LEVEL_DEBUG
	case "STATISTICS":
		return LOG_LEVEL_STATIS
	case "INFO":
		return LOG_LEVEL_INFO
	case "WARN":
		return LOG_LEVEL_WARN
	case "ERROR":
		return LOG_LEVEL_ERROR
	case "FATAL":
		return LOG_LEVEL_FATAL
	default:
		return -1
	}
}

func GetLogOutputID(str string) int {
	switch str {
	case "FILE":
		return LOG_OUTPUT_FILE
	default:
		return LOG_OUTPUT_STDOUT
	}
}

func (s *IcsLog) DelLog() {
	//var derr *icserror.IcsError = nil
	delLogCount := 0

	defer func() {
		if err := recover(); err != nil {
			switch v := err.(type) {
			case error:
				icserror.ICSERRFileOpen.SetError(v)
				s.Printf(LOG_LEVEL_WARN, -1, "PANIC! %s\n%s", icserror.ICSERRFileOpen.GetError().Error(), string(debug.Stack()))
			default:
				s.Print(LOG_LEVEL_WARN, -1, icserror.ICSERRFileOpen.GetError().Error())
			}
		}
		//derr = icserror.ICSERRFileOpen
	}()

	// 1. Get Log Del Time
	yyyy, mm, dd := s.CurrentDate.Date()
	curTime, _ := time.Parse("2006-01-02", fmt.Sprintf("%d-%02d-%02d", yyyy, mm, dd))
	delTime := curTime.AddDate(0, 0, -s.DelDay)

	files, rderr := ioutil.ReadDir(s.Path)
	if rderr != nil {
		s.Printf(LOG_LEVEL_ERROR, -1, " Read Log Dir error - %s \n", rderr)
		fmt.Printf("Read Log Dir error - %s", rderr)
	} else {
		var logYYYY, logMM, logDD string
		for _, file := range files {
			/*
				TODO
				1. 로그 파일을 시간으로 변환
				2. 현재 시간 - 삭제기간
				3. 둘 시간 차이가 0 이상 나면 삭제대상
			*/
			logTime := strings.SplitN(file.Name(), "-", -1)[1]
			logYYYY, logMM, logDD = logTime[:4], logTime[4:6], logTime[6:8]
			chgLogTime, perr := time.Parse("2006-01-02", fmt.Sprintf("%s-%s-%s", logYYYY, logMM, logDD))
			if perr != nil {
				s.Printf(LOG_LEVEL_ERROR, -1, " Log Time Parse error - %s \n", perr)
				fmt.Printf("Log Time Parse error - %s", perr)
			} else {
				diff := delTime.Sub(chgLogTime)

				// diff > 0 => delete log
				if diff > 0 {
					delResult := icsutil.DeleteFile(s.Path, file.Name())
					if delResult != "" {
						s.Printf(LOG_LEVEL_ERROR, -1, " Delete File error - %s \n", delResult)
					} else {
						s.Printf(LOG_LEVEL_INFO, -1, " Delete %s!!!!\n", file.Name())
						fmt.Printf("Delete %s!!!!\n", file.Name())
						delLogCount += 1
					}
				}
			}
		}
	}
	s.Printf(LOG_LEVEL_INFO, -1, " Delete File Count - %d \n", delLogCount)
	fmt.Printf("Log Delete Count : %d", delLogCount)
}
