package neffos

import (
	"log"
	"os"
	"reflect"
)

var debugPrinter interface{}

// EnableDebug enables debug and optionally
// sets a custom printer to print out debug messages.
// The "printer" can be any compatible printer such as
// the standard `log.Logger` or a custom one like the `kataras/golog`.
//
// A "printer" is compatible when it contains AT LEAST ONE of the following methods:
// Debugf(string, ...interface{}) or
// Logf(string, ...interface{}) or
// Printf(string, ...interface{})
//
// If EnableDebug is called but the "printer" value is nil
// then neffos will print debug messages through a new log.Logger prefixed with "| neffos |".
//
// Note that neffos, currently, uses debug mode only on the build state of the events.
// Therefore enabling the debugger has zero performance cost on up-and-running servers and clients.
//
// There is no way to disable the debug mode on serve-time.
func EnableDebug(printer interface{}) {
	if debugEnabled() {
		Debugf("debug mode is already set")
		return
	}

	if _, boolean := printer.(bool); boolean {
		// if for some reason by accident EnableDebug(true) instead of a printer value.
		printer = nil
	}
	if printer == nil {
		logger := log.New(os.Stderr, "| neffos | ", 0)
		printer = logger
		logger.Println("debug mode is set")
	}

	debugPrinter = printer
}

type (
	debugfer interface {
		Debugf(string, ...interface{})
	}
	logfer interface {
		Logf(string, ...interface{})
	}
	printfer interface {
		Printf(string, ...interface{})
	}
)

func debugEnabled() bool {
	return debugPrinter != nil
}

// Debugf prints debug messages to the printer defined on `EnableDebug`.
// Runs only on debug mode.
func Debugf(format string, args ...interface{}) {
	if !debugEnabled() {
		return
	}

	if len(args) == 1 {
		// handles:
		// Debugf("format", func() dargs {
		//    time-consumed action that should run only on debug.
		// })
		if onDebugWithArgs, ok := args[0].(func() dargs); ok {
			args = onDebugWithArgs()
		}
	}

	switch printer := debugPrinter.(type) {
	case debugfer:
		printer.Debugf(format, args...)
	case logfer:
		printer.Logf(format, args...)
	case printfer:
		printer.Printf(format, args...)
	default:
		panic("unsported debug printer")
	}
}

type dargs []interface{}

// DebugEach prints debug messages for each of "mapOrSlice" elements
// to the printer defined on `EnableDebug`.
// Runs only on debug mode.
// Usage:
//
//	DebugEach(staticFields, func(idx int, f reflect.Value) {
//		fval := f.Interface()
//		Debugf("field [%s.%s] will be automatically re-filled with [%T(%s)]", typ.Name(), typ.Field(idx).Name, fval, fval)
//	})
func DebugEach(mapOrSlice interface{}, onDebugVisitor interface{}) {
	if !debugEnabled() || onDebugVisitor == nil {
		return
	}

	visitor := reflect.ValueOf(onDebugVisitor)

	visitorTyp := visitor.Type()
	if visitorTyp.Kind() != reflect.Func {
		return
	}

	userNumIn := visitorTyp.NumIn()

	v := reflect.ValueOf(mapOrSlice)

	switch v.Kind() {
	case reflect.Map:
		for ranger := v.MapRange(); ranger.Next(); {
			in := make([]reflect.Value, userNumIn)
			in[0] = ranger.Key()

			if userNumIn > 1 {
				// assume both key and value are expected.
				in[1] = ranger.Value()
			}

			// note that we don't make any further checks here, it's only for internal
			// use and I want to panic in my tests if I didn't expect the correct values.
			visitor.Call(in)
		}
	case reflect.Slice:
		// TODO: whenever I need this.
	}
}
