// goredo -- djb's redo implementation on pure Go
// Copyright (C) 2020-2026 Sergey Matveev <stargrave@stargrave.org>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 3 of the License.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

// Logging facilities

package main

import (
	"bytes"
	"flag"
	"fmt"
	"os"
	"strings"
	"sync"

	"golang.org/x/term"
)

const (
	EnvLevel      = "REDO_LEVEL"
	EnvNoProgress = "REDO_NO_PROGRESS"
	EnvDebug      = "REDO_DEBUG"
	EnvLogWait    = "REDO_LOG_WAIT"
	EnvLogLock    = "REDO_LOG_LOCK"
	EnvLogPid     = "REDO_LOG_PID"
	EnvLogJS      = "REDO_LOG_JS"
	EnvNoColor    = "NO_COLOR"
)

var (
	Level      = 0
	NoColor    bool
	NoProgress bool
	Debug      bool
	LogWait    bool
	LogLock    bool
	LogJS      bool
	MyPID      int

	CDebug string
	CRedo  string
	CWait  string
	CLock  string
	CErr   string
	CWarn  string
	CJS    string
	CReset string
	CNone  = "NONE"

	flagDebug      = flag.Bool("d", false, fmt.Sprintf("enable debug logging (%s=1)", EnvDebug))
	flagNoProgress *bool
	flagLogWait    *bool
	flagLogLock    *bool
	flagLogPid     *bool
	flagLogJS      *bool

	LogMutex      sync.Mutex
	KeyEraseLine  string
	LastLoggedTgt string
)

func init() {
	var b bytes.Buffer
	t := term.NewTerminal(&b, "")
	CDebug = string(t.Escape.Yellow)
	CRedo = string(t.Escape.Green)
	CWait = string(t.Escape.Blue)
	CLock = string(t.Escape.Cyan)
	CErr = string(t.Escape.Red)
	CWarn = string(t.Escape.Magenta)
	CJS = string(t.Escape.White)
	CReset = string(t.Escape.Reset)
	KeyEraseLine = fmt.Sprintf("%s[K", CReset[0:1])

	cmdName := CmdName()
	if !(cmdName == CmdNameRedo || cmdName == CmdNameRedoIfchange) {
		return
	}
	flagNoProgress = flag.Bool("no-progress", false,
		fmt.Sprintf("no progress printing (%s=1), also implies -no-status", EnvNoProgress))
	flagLogWait = flag.Bool("log-wait", false,
		fmt.Sprintf("enable wait messages logging (%s=1)", EnvLogWait))
	flagLogLock = flag.Bool("log-lock", false,
		fmt.Sprintf("enable lock messages logging (%s=1)", EnvLogLock))
	flagLogPid = flag.Bool("log-pid", false,
		fmt.Sprintf("append PIDs (%s=1)", EnvLogPid))
	flagLogJS = flag.Bool("log-js", false,
		fmt.Sprintf("enable jobserver messages logging (%s=1)", EnvLogJS))
}

func erasedStatus(s, end string) string {
	if NoProgress || NoColor {
		return s + end
	}
	return s + KeyEraseLine + end
}

func withPrependedTgt(s string) {
	if s[0] != '[' {
		stderrWrite(erasedStatus(s, "\n"))
		return
	}
	i := strings.IndexByte(s, ']')
	if i == -1 {
		stderrWrite(s)
		return
	}
	tgt, s := s[1:i], s[i+1:]
	if tgt != LastLoggedTgt {
		LastLoggedTgt = tgt
		tgt = "redo " + tgt + " ..."
		if MyPID != 0 {
			tgt = fmt.Sprintf("[%d] %s", MyPID, tgt)
		}
		stderrWrite(erasedStatus(colourize(CDebug, tgt), "\n"))
	}
	stderrWrite(erasedStatus(s, "\n"))
}

func stderrWrite(s string) {
	LogMutex.Lock()
	os.Stderr.WriteString(s)
	LogMutex.Unlock()
}

func tracef(level, format string, args ...any) {
	var p string
	if MyPID != 0 {
		p = fmt.Sprintf("[%d] ", MyPID)
	}
	switch level {
	case CNone:
		p = erasedStatus(StderrPrefix+p+fmt.Sprintf(format, args...), "\n")
		stderrWrite(p)
		return
	case CDebug:
		if !Debug {
			return
		}
		p += "dbg  "
	case CWait:
		if !(LogWait || Debug) {
			return
		}
		p += "wait "
	case CRedo:
		if NoProgress {
			return
		}
		p += "redo "
	case CLock:
		if !(LogLock || Debug) {
			return
		}
		p += "lock "
	case CJS:
		if !(LogJS || Debug) {
			return
		}
		p += "js   "
	case CErr:
		p += "err  "
	case CWarn:
		p += "warn "
	}
	msg := fmt.Sprintf(format, args...)
	msg = StderrPrefix + colourize(level, p+strings.Repeat(". ", Level)+msg)
	msg = erasedStatus(msg, "\n")
	stderrWrite(msg)
}

func colourize(colour, s string) string {
	if NoColor {
		return s
	}
	return colour + s + CReset
}
