package log

import (
    "fmt"
    "io"
    "os"
    "strings"
    "runtime"
)

const (
    // Internal constant
    UNKNOWN_FILENAME = "unknown"
)

// Log levels; not fully implemented. Currently supports DEBUG (everything with
// callpoint instrumentation) and WARN (WARN and ERR with no callpoints).
const (
    DEBUG = iota
    LOG
    WARN
    ERR
)

// designed to allow a generic logging function to call the appropriate logging
// handler; needs integration
var logLvlMap = map[byte](func(string, ...interface{})) {
    DEBUG: Debugln,
    LOG:   Logln,
    WARN:  Warnln,
    ERR:   Errln,
}

// LogInterface defines the handlers a log system must implement.  Golang's
// syslog.Writer interface implements LogInterface.
type LogInterface interface {
    // Info prints an informational message; Log level DEBUG and LOG print
    // here.
    Info(mesg string) error

    // Warning prints an informational message; Log level WARN prints here.
    Warning(mesg string) error

    // Err prints an informational message; Log level ERR prints here.
    Err(mesg string) error
}

var (
    _slog     LogInterface = new(StdLogger)
    _do_debug bool
)

// StdLogger defines a LogLogger that talks to stdout.
type StdLogger int

func (StdLogger) Info(mesg string) error {
    io.WriteString(os.Stdout, mesg)
    return nil
}

func (StdLogger) Warning(mesg string) error {
    io.WriteString(os.Stdout, mesg)
    return nil
}

func (StdLogger) Err(mesg string) error {
    io.WriteString(os.Stdout, mesg)
    return nil
}

var _ LogInterface = new(StdLogger)

// NullLogger defines a LogLogger that talks does nothing with all log
// requests.
type NullLogger int

func (NullLogger) Info(mesg string) error {
    return nil
}

func (NullLogger) Warning(mesg string) error {
    return nil
}

func (NullLogger) Err(mesg string) error {
    return nil
}

var _ LogInterface = new(NullLogger)

// Init initializes the logging layer.  By default, an uninitialized logging
// later uses the StdLogger interface and print to stdout.
func Init(logger LogInterface, debug bool) {
    _slog = logger
    _do_debug = debug
}

// LogInContext marks the call site as part of a higher-level logging library
// that uses log.  This allows log to print the proper callsite when running in
// debug mode.
func LogInContext(logf func(string, ...interface{}), mesg string, params ...interface{}) {
    logf(mesg, params...)
}

const (
    // Internal constants.
    LOG_BASE_STACK_LVL  = 4 // LogInContext will always exist four frames above, if it exists at all
    LOG_ADD_CONTEXT_LVL = 2 // If called from LogInContext, add two additional frames
    LOG_CONTEXT_FNAME   = "log.LogInContext"
)

func determineCallerStack() (string, int, bool) {
    var name string
    var lvl  int

    // runtime.Caller returns the PC of the call-site, not the function.  This
    // means runtime.FuncForPC will not resolve.  As such, we have to grab the
    // entire stack.
    pcs := make([]uintptr, 30) // try to avoid more allocation
    num_pcs := runtime.Callers(0, pcs)

    if num_pcs >= LOG_BASE_STACK_LVL + LOG_ADD_CONTEXT_LVL {
        f := runtime.FuncForPC(pcs[LOG_BASE_STACK_LVL+1])
        if f != nil {
            parts := strings.Split(f.Name(), "/")
            name := parts[len(parts) - 1]
            if name != "" && name == LOG_CONTEXT_FNAME {
                lvl = LOG_BASE_STACK_LVL + LOG_ADD_CONTEXT_LVL
            } else {
                lvl = LOG_BASE_STACK_LVL
            }
            _, filepath, line, ok := runtime.Caller(lvl)
            return filepath, line, ok
        }
    }

    _slog.Err("Could not determine stack information in log module!\n")

    return name, 0, false
}

func addCallerInfo(mesg string) string {
    filepath, line, ok := determineCallerStack()

    if ok {
        filename := UNKNOWN_FILENAME
        parts := strings.Split(filepath, "/")
        if len(parts) > 0 {
            filename = parts[len(parts) - 1]
        }
        return fmt.Sprintf("%s:%d %s", filename, line, mesg)
    }

    return mesg
}

func _log(mesg string) {
    if _do_debug {
        mesg = addCallerInfo(mesg)
    }
    _slog.Info(mesg)
}

// Logln logs a normal message with a terminating line-break
func Logln(mesg string, params ...interface{}) {
    m := fmt.Sprintf(mesg + "\n", params...)
    _log(m)
}

// Log logs a normal message
func Log(mesg string, params ...interface{}) {
    m := fmt.Sprintf(mesg, params...)
    _log(m)
}

func _debug(mesg string) {
    _slog.Info(addCallerInfo(mesg))
}

// Debugln logs a debug message with a terminating line-break
func Debugln(mesg string, params ...interface{}) {
    if _do_debug {
        m := fmt.Sprintf(mesg + "\n", params...)
        _debug(m)
    }
}

// Debug logs an error message
func Debug(mesg string, params ...interface{}) {
    if _do_debug {
        m := fmt.Sprintf(mesg, params...)
        _debug(m)
    }
}

func _warn(mesg string) {
    _slog.Warning(addCallerInfo(mesg))
}

// Warnln logs a debug message with a terminating line-break
func Warnln(mesg string, params ...interface{}) {
    m := fmt.Sprintf(mesg + "\n", params...)
    _warn(m)
}

// Warn logs an error message
func Warn(mesg string, params ...interface{}) {
    m := fmt.Sprintf(mesg, params...)
    _warn(m)
}

func _err(mesg string) {
    _slog.Err(addCallerInfo(mesg))
}

// Errln logs an error message with a terminating line-break
func Errln(mesg string, params ...interface{}) {
    m := fmt.Sprintf(mesg + "\n", params...)
    _err(m)
}

// Err logs an error message
func Err(mesg string, params ...interface{}) {
    m := fmt.Sprintf(mesg, params...)
    _err(m)
}

// Return the log level the log framework is running in; largely intended for
// logging libraries that rest on top.
func LogLevel() byte {
    if _do_debug {
        return DEBUG
    }

    return WARN
}
