// Copyright 2016 syzkaller project authors. All rights reserved.
// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.

// Package log provides functionality similar to standard log package with some extensions:
//  - verbosity levels
//  - global verbosity setting that can be used by multiple packages
//  - ability to disable all output
//  - ability to cache recent output in memory
package log

import (
	"bytes"
	"flag"
	"fmt"
	golog "log"
	"sync"
	"time"
)

var (
	flagV        = flag.Int("v", 0, "verbosity")
	mu           sync.Mutex
	cacheMem     int
	cacheMaxMem  int
	cachePos     int
	cacheEntries []string
	prependTime  = true // for testing
)

// EnableCaching enables in memory caching of log output.
// Caches up to maxLines, but no more than maxMem bytes.
// Cached output can later be queried with CachedOutput.
func EnableLogCaching(maxLines, maxMem int) {
	mu.Lock()
	defer mu.Unlock()
	if cacheEntries != nil {
		Fatalf("log caching is already enabled")
	}
	if maxLines < 1 || maxMem < 1 {
		panic("invalid maxLines/maxMem")
	}
	cacheMaxMem = maxMem
	cacheEntries = make([]string, maxLines)
}

// Retrieves cached log output.
func CachedLogOutput() string {
	mu.Lock()
	defer mu.Unlock()
	buf := new(bytes.Buffer)
	for i := range cacheEntries {
		pos := (cachePos + i) % len(cacheEntries)
		if cacheEntries[pos] == "" {
			continue
		}
		buf.WriteString(cacheEntries[pos])
		buf.Write([]byte{'\n'})
	}
	return buf.String()
}

func Logf(v int, msg string, args ...interface{}) {
	mu.Lock()
	doLog := v <= *flagV
	if cacheEntries != nil && v <= 1 {
		cacheMem -= len(cacheEntries[cachePos])
		if cacheMem < 0 {
			panic("log cache size underflow")
		}
		timeStr := ""
		if prependTime {
			timeStr = time.Now().Format("2006/01/02 15:04:05 ")
		}
		cacheEntries[cachePos] = fmt.Sprintf(timeStr+msg, args...)
		cacheMem += len(cacheEntries[cachePos])
		cachePos++
		if cachePos == len(cacheEntries) {
			cachePos = 0
		}
		for i := 0; i < len(cacheEntries)-1 && cacheMem > cacheMaxMem; i++ {
			pos := (cachePos + i) % len(cacheEntries)
			cacheMem -= len(cacheEntries[pos])
			cacheEntries[pos] = ""
		}
		if cacheMem < 0 {
			panic("log cache size underflow")
		}
	}
	mu.Unlock()

	if doLog {
		golog.Printf(msg, args...)
	}
}

func Fatal(err error) {
	golog.Fatal(err)
}

func Fatalf(msg string, args ...interface{}) {
	golog.Fatalf(msg, args...)
}