/***********************************************************************************************************************
 *
 *   MIT License
 *
 *   Copyright (c) 2021 Nicolas JUHEL
 *
 *   Permission is hereby granted, free of charge, to any person obtaining a copy
 *   of this software and associated documentation files (the "Software"), to deal
 *   in the Software without restriction, including without limitation the rights
 *   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 *   copies of the Software, and to permit persons to whom the Software is
 *   furnished to do so, subject to the following conditions:
 *
 *   The above copyright notice and this permission notice shall be included in all
 *   copies or substantial portions of the Software.
 *
 *   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 *   SOFTWARE.
 *
 *
 **********************************************************************************************************************/

package logger

import (
	"context"
	"io"
	"sync"
	"sync/atomic"
	"time"

	iotclo "github.com/nabbar/golib/ioutils/mapCloser"
	logcfg "github.com/nabbar/golib/logger/config"
	logfld "github.com/nabbar/golib/logger/fields"
	logfil "github.com/nabbar/golib/logger/hookfile"
	logerr "github.com/nabbar/golib/logger/hookstderr"
	logout "github.com/nabbar/golib/logger/hookstdout"
	logsys "github.com/nabbar/golib/logger/hooksyslog"
	loglvl "github.com/nabbar/golib/logger/level"
	logtps "github.com/nabbar/golib/logger/types"
	"github.com/sirupsen/logrus"
)

func (o *logger) getCloser() iotclo.Closer {
	i := o.c.Load()
	if c, k := i.(iotclo.Closer); k && c != nil {
		return c
	}

	c := o.newCloser()
	o.c.Store(c)

	return c
}

func (o *logger) switchCloser(c iotclo.Closer) {
	if o == nil {
		return
	} else if c == nil {
		c = o.newCloser()
	}

	i := o.c.Swap(c)

	if i == nil {
		return
	} else if v, k := i.(iotclo.Closer); k && v != nil {
		go func() {
			// temp waiting all still calling log finish
			time.Sleep(10 * time.Second)
			_ = v.Close()
			v = nil
		}()
	}
}

func (o *logger) newCloser() iotclo.Closer {
	if o == nil || o.x == nil {
		return nil
	}

	return iotclo.New(o.x.GetContext())
}

func (o *logger) hasCloser() bool {
	if o == nil || o.x == nil {
		return false
	}

	if i := o.c.Load(); i != nil {
		if _, k := i.(iotclo.Closer); k {
			return true
		}
	}

	return false
}

func (o *logger) Clone() Logger {
	if o == nil {
		return nil
	}

	l := &logger{
		m: sync.RWMutex{},
		x: o.x.Clone(nil),
		f: o.f.FieldsClone(nil),
		c: new(atomic.Value),
	}

	return l
}

func (o *logger) RegisterFuncUpdateLogger(fct func(log Logger)) {
	o.x.Store(keyFctUpdLog, fct)
}

func (o *logger) runFuncUpdateLogger() {
	if i, l := o.x.Load(keyFctUpdLog); !l {
		return
	} else if f, k := i.(func(log Logger)); !k {
		return
	} else if f == nil {
		return
	} else {
		f(o)
	}
}

func (o *logger) RegisterFuncUpdateLevel(fct func(log Logger)) {
	o.x.Store(keyFctUpdLvl, fct)
}

func (o *logger) runFuncUpdateLevel() {
	if i, l := o.x.Load(keyFctUpdLvl); !l {
		return
	} else if f, k := i.(func(log Logger)); !k {
		return
	} else if f == nil {
		return
	} else {
		f(o)
	}
}

func (o *logger) SetLevel(lvl loglvl.Level) {
	o.x.Store(keyLevel, lvl)
	o.setLogrusLevel(o.GetLevel())
	o.runFuncUpdateLevel()
}

func (o *logger) GetLevel() loglvl.Level {
	if o == nil {
		return loglvl.NilLevel
	} else if o.x == nil {
		return loglvl.NilLevel
	} else if i, l := o.x.Load(keyLevel); !l {
		return loglvl.NilLevel
	} else if v, k := i.(loglvl.Level); !k {
		return loglvl.NilLevel
	} else {
		return v
	}
}

func (o *logger) SetFields(field logfld.Fields) {
	if o == nil {
		return
	}

	if field != nil {
		o.m.Lock()
		defer o.m.Unlock()
		o.f = field
	}
}

func (o *logger) GetFields() logfld.Fields {
	if o == nil {
		return logfld.New(context.Background)
	}

	o.m.RLock()
	defer o.m.RUnlock()
	return o.f.FieldsClone(nil)
}

func (o *logger) SetOptions(opt *logcfg.Options) error {
	var (
		lvl = o.GetLevel()
		obj = logrus.New()
		hkl = make([]logtps.Hook, 0)
	)

	o.optionsMerge(opt)

	obj.SetLevel(lvl.Logrus())
	obj.SetFormatter(o.defaultFormatter(nil))
	obj.SetOutput(io.Discard) // Send all logs to nowhere by default

	if opt.Stdout != nil && !opt.Stdout.DisableStandard {
		f := o.defaultFormatter(opt.Stdout)
		l := []logrus.Level{
			logrus.InfoLevel,
			logrus.DebugLevel,
			logrus.TraceLevel,
		}

		if h, e := logout.New(opt.Stdout, l, f); e != nil {
			return e
		} else {
			hkl = append(hkl, h)
		}

		l = []logrus.Level{
			logrus.PanicLevel,
			logrus.FatalLevel,
			logrus.ErrorLevel,
			logrus.WarnLevel,
		}

		if h, e := logerr.New(opt.Stdout, l, f); e != nil {
			return e
		} else {
			hkl = append(hkl, h)
		}
	}

	if len(opt.LogFile) > 0 {
		for _, f := range opt.LogFile {
			if h, e := logfil.New(f, o.defaultFormatterNoColor()); e != nil {
				return e
			} else {
				hkl = append(hkl, h)
			}
		}
	}

	if len(opt.LogSyslog) > 0 {
		for _, s := range opt.LogSyslog {
			if h, e := logsys.New(s, o.defaultFormatterNoColor()); e != nil {
				return e
			} else {
				hkl = append(hkl, h)
			}
		}
	}

	if len(hkl) > 0 {
		var clo = o.newCloser()

		for _, h := range hkl {
			clo.Add(h)
			h.RegisterHook(obj)
			go h.Run(o.x.GetContext())
		}

		o.switchCloser(clo)
	} else if o.hasCloser() {
		o.switchCloser(nil)
	}

	o.x.Store(keyOptions, opt)
	o.x.Store(keyLogrus, obj)
	o.runFuncUpdateLogger()

	return nil
}

func (o *logger) GetOptions() *logcfg.Options {
	if o == nil {
		return &logcfg.Options{}
	} else if o.x == nil {
		return &logcfg.Options{}
	} else if i, l := o.x.Load(keyOptions); !l {
		return &logcfg.Options{}
	} else if v, k := i.(*logcfg.Options); !k {
		return &logcfg.Options{}
	} else {
		return v
	}
}
