// Copyright 2023 The LevelDB-Go and Pebble Authors. All rights reserved. Use
// of this source code is governed by a BSD-style license that can be found in
// the LICENSE file.

package base

import (
	"bytes"
	"context"
	"fmt"
	"log"
	"os"
	"runtime"
	"sync"

	"github.com/cockroachdb/pebble/internal/invariants"
)

// Logger defines an interface for writing log messages.
type Logger interface {
	Infof(format string, args ...interface{})
	Errorf(format string, args ...interface{})
	Fatalf(format string, args ...interface{})
}
type defaultLogger struct{}

// DefaultLogger logs to the Go stdlib logs.
var DefaultLogger defaultLogger

var _ Logger = DefaultLogger

// Infof implements the Logger.Infof interface.
func (defaultLogger) Infof(format string, args ...interface{}) {
	_ = log.Output(2, fmt.Sprintf(format, args...))
}

// Errorf implements the Logger.Errorf interface.
func (defaultLogger) Errorf(format string, args ...interface{}) {
	_ = log.Output(2, fmt.Sprintf(format, args...))
}

// Fatalf implements the Logger.Fatalf interface.
func (defaultLogger) Fatalf(format string, args ...interface{}) {
	_ = log.Output(2, fmt.Sprintf(format, args...))
	os.Exit(1)
}

// InMemLogger implements Logger using an in-memory buffer (used for testing).
// The buffer can be read via String() and cleared via Reset().
type InMemLogger struct {
	mu struct {
		sync.Mutex
		buf bytes.Buffer
	}
}

var _ Logger = (*InMemLogger)(nil)

// Reset clears the internal buffer.
func (b *InMemLogger) Reset() {
	b.mu.Lock()
	defer b.mu.Unlock()
	b.mu.buf.Reset()
}

// String returns the current internal buffer.
func (b *InMemLogger) String() string {
	b.mu.Lock()
	defer b.mu.Unlock()
	return b.mu.buf.String()
}

// Infof is part of the Logger interface.
func (b *InMemLogger) Infof(format string, args ...interface{}) {
	s := fmt.Sprintf(format, args...)
	b.mu.Lock()
	defer b.mu.Unlock()
	b.mu.buf.Write([]byte(s))
	if n := len(s); n == 0 || s[n-1] != '\n' {
		b.mu.buf.Write([]byte("\n"))
	}
}

// Errorf is part of the Logger interface.
func (b *InMemLogger) Errorf(format string, args ...interface{}) {
	b.Infof(format, args...)
}

// Fatalf is part of the Logger interface.
func (b *InMemLogger) Fatalf(format string, args ...interface{}) {
	b.Infof(format, args...)
	runtime.Goexit()
}

// LoggerAndTracer defines an interface for logging and tracing.
type LoggerAndTracer interface {
	Logger
	// Eventf formats and emits a tracing log, if tracing is enabled in the
	// current context.
	Eventf(ctx context.Context, format string, args ...interface{})
	// IsTracingEnabled returns true if tracing is enabled. It can be used as an
	// optimization to avoid calling Eventf (which will be a noop when tracing
	// is not enabled) to avoid the overhead of boxing the args.
	IsTracingEnabled(ctx context.Context) bool
}

// LoggerWithNoopTracer wraps a logger and does no tracing.
type LoggerWithNoopTracer struct {
	Logger
}

var _ LoggerAndTracer = &LoggerWithNoopTracer{}

// Eventf implements LoggerAndTracer.
func (*LoggerWithNoopTracer) Eventf(ctx context.Context, format string, args ...interface{}) {
	if invariants.Enabled && ctx == nil {
		panic("Eventf context is nil")
	}
}

// IsTracingEnabled implements LoggerAndTracer.
func (*LoggerWithNoopTracer) IsTracingEnabled(ctx context.Context) bool {
	if invariants.Enabled && ctx == nil {
		panic("IsTracingEnabled ctx is nil")
	}
	return false
}

// NoopLoggerAndTracer does no logging and tracing. Remember that struct{} is
// special cased in Go and does not incur an allocation when it backs the
// interface LoggerAndTracer.
type NoopLoggerAndTracer struct{}

var _ LoggerAndTracer = NoopLoggerAndTracer{}

// Infof implements LoggerAndTracer.
func (l NoopLoggerAndTracer) Infof(format string, args ...interface{}) {}

// Errorf implements LoggerAndTracer.
func (l NoopLoggerAndTracer) Errorf(format string, args ...interface{}) {}

// Fatalf implements LoggerAndTracer.
func (l NoopLoggerAndTracer) Fatalf(format string, args ...interface{}) {}

// Eventf implements LoggerAndTracer.
func (l NoopLoggerAndTracer) Eventf(ctx context.Context, format string, args ...interface{}) {
	if invariants.Enabled && ctx == nil {
		panic("Eventf context is nil")
	}
}

// IsTracingEnabled implements LoggerAndTracer.
func (l NoopLoggerAndTracer) IsTracingEnabled(ctx context.Context) bool {
	if invariants.Enabled && ctx == nil {
		panic("IsTracingEnabled ctx is nil")
	}
	return false
}
