package logger

// Get all handlers based on the Config (if available)
import (
	"fmt"
	"github.com/revel/config"
	"log"
	"os"
	"path/filepath"
	"strings"
)

func InitializeFromConfig(basePath string, config *config.Context) (c *CompositeMultiHandler) {
	// If running in test mode suppress anything that is not an error
	if config != nil && config.BoolDefault(TEST_MODE_FLAG, false) {
		// Preconfigure all the options
		config.SetOption("log.info.output", "none")
		config.SetOption("log.debug.output", "none")
		config.SetOption("log.warn.output", "none")
		config.SetOption("log.error.output", "stderr")
		config.SetOption("log.crit.output", "stderr")
	}

	// If the configuration has an all option we can skip some
	c, _ = NewCompositeMultiHandler()

	// Filters are assigned first, non filtered items override filters
	if config != nil && !config.BoolDefault(TEST_MODE_FLAG, false) {
		initAllLog(c, basePath, config)
	}
	initLogLevels(c, basePath, config)
	if c.CriticalHandler == nil && c.ErrorHandler != nil {
		c.CriticalHandler = c.ErrorHandler
	}
	if config != nil && !config.BoolDefault(TEST_MODE_FLAG, false) {
		initFilterLog(c, basePath, config)
		if c.CriticalHandler == nil && c.ErrorHandler != nil {
			c.CriticalHandler = c.ErrorHandler
		}
		initRequestLog(c, basePath, config)
	}

	return c
}

// Init the log.all configuration options
func initAllLog(c *CompositeMultiHandler, basePath string, config *config.Context) {
	if config != nil {
		extraLogFlag := config.BoolDefault(SPECIAL_USE_FLAG, false)
		if output, found := config.String("log.all.output"); found {
			// Set all output for the specified handler
			if extraLogFlag {
				log.Printf("Adding standard handler for levels to >%s< ", output)
			}
			initHandlerFor(c, output, basePath, NewLogOptions(config, true, nil, LvlAllList...))
		}
	}
}

// Init the filter options
// log.all.filter ....
// log.error.filter ....
func initFilterLog(c *CompositeMultiHandler, basePath string, config *config.Context) {

	if config != nil {
		extraLogFlag := config.BoolDefault(SPECIAL_USE_FLAG, false)

		for _, logFilter := range logFilterList {
			// Init for all filters
			for _, name := range []string{"all", "debug", "info", "warn", "error", "crit",
				"trace", // TODO trace is deprecated
			} {
				optionList := config.Options(logFilter.LogPrefix + name + logFilter.LogSuffix)
				for _, option := range optionList {
					splitOptions := strings.Split(option, ".")
					keyMap := map[string]interface{}{}
					for x := 3; x < len(splitOptions); x += 2 {
						keyMap[splitOptions[x]] = splitOptions[x+1]
					}
					phandler := logFilter.parentHandler(keyMap)
					if extraLogFlag {
						log.Printf("Adding key map handler %s %s output %s", option, name, config.StringDefault(option, ""))
						fmt.Printf("Adding key map handler %s %s output %s matching %#v\n", option, name, config.StringDefault(option, ""), keyMap)
					}

					if name == "all" {
						initHandlerFor(c, config.StringDefault(option, ""), basePath, NewLogOptions(config, false, phandler))
					} else {
						initHandlerFor(c, config.StringDefault(option, ""), basePath, NewLogOptions(config, false, phandler, toLevel[name]))
					}
				}
			}
		}
	}
}

// Init the log.error, log.warn etc configuration options
func initLogLevels(c *CompositeMultiHandler, basePath string, config *config.Context) {
	for _, name := range []string{"debug", "info", "warn", "error", "crit",
		"trace", // TODO trace is deprecated
	} {
		if config != nil {
			extraLogFlag := config.BoolDefault(SPECIAL_USE_FLAG, false)
			output, found := config.String("log." + name + ".output")
			if found {
				if extraLogFlag {
					log.Printf("Adding standard handler %s output %s", name, output)
				}
				initHandlerFor(c, output, basePath, NewLogOptions(config, true, nil, toLevel[name]))
			}
			// Gets the list of options with said prefix
		} else {
			initHandlerFor(c, "stderr", basePath, NewLogOptions(config, true, nil, toLevel[name]))
		}
	}
}

// Init the request log options
func initRequestLog(c *CompositeMultiHandler, basePath string, config *config.Context) {
	// Request logging to a separate output handler
	// This takes the InfoHandlers and adds a MatchAbHandler handler to it to direct
	// context with the word "section=requestlog" to that handler.
	// Note if request logging is not enabled the MatchAbHandler will not be added and the
	// request log messages will be sent out the INFO handler
	outputRequest := "stdout"
	if config != nil {
		outputRequest = config.StringDefault("log.request.output", "")
	}
	oldInfo := c.InfoHandler
	c.InfoHandler = nil
	if outputRequest != "" {
		initHandlerFor(c, outputRequest, basePath, NewLogOptions(config, false, nil, LvlInfo))
	}
	if c.InfoHandler != nil || oldInfo != nil {
		if c.InfoHandler == nil {
			c.InfoHandler = oldInfo
		} else {
			c.InfoHandler = MatchAbHandler("section", "requestlog", c.InfoHandler, oldInfo)
		}
	}
}

// Returns a handler for the level using the output string
// Accept formats for output string are
// LogFunctionMap[value] callback function
// `stdout` `stderr` `full/file/path/to/location/app.log` `full/file/path/to/location/app.json`
func initHandlerFor(c *CompositeMultiHandler, output, basePath string, options *LogOptions) {
	if options.Ctx != nil {
		options.SetExtendedOptions(
			"noColor", !options.Ctx.BoolDefault("log.colorize", true),
			"smallDate", options.Ctx.BoolDefault("log.smallDate", true),
			"maxSizeMB", options.Ctx.IntDefault("log.maxsize", 1024*10),
			"maxAgeDays", options.Ctx.IntDefault("log.maxage", 14),
			"maxBackups", options.Ctx.IntDefault("log.maxbackups", 14),
			"compress", !options.Ctx.BoolDefault("log.compressBackups", true),
		)
	}

	output = strings.TrimSpace(output)
	if funcHandler, found := LogFunctionMap[output]; found {
		funcHandler(c, options)
	} else {
		switch output {
		case "":
			fallthrough
		case "off":
		// No handler, discard data
		default:
			// Write to file specified
			if !filepath.IsAbs(output) {
				output = filepath.Join(basePath, output)
			}

			if err := os.MkdirAll(filepath.Dir(output), 0755); err != nil {
				log.Panic(err)
			}

			if strings.HasSuffix(output, "json") {
				c.SetJsonFile(output, options)
			} else {
				// Override defaults for a terminal file
				options.SetExtendedOptions("noColor", true)
				options.SetExtendedOptions("smallDate", false)
				c.SetTerminalFile(output, options)
			}
		}
	}
	return
}
