// Copyright 2018 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package log

import (
	"os"
	"time"
)

// GoogleEmitter is a wrapper that emits logs in a format compatible with
// package github.com/golang/glog.
type GoogleEmitter struct {
	// Emitter is the underlying emitter.
	Emitter
}

// buffer is a simple inline buffer to avoid churn. The data slice is generally
// kept to the local byte array, and we avoid having to allocate it on the heap.
type buffer struct {
	local [256]byte
	data  []byte
}

func (b *buffer) start() {
	b.data = b.local[:0]
}

func (b *buffer) String() string {
	return unsafeString(b.data)
}

func (b *buffer) write(c byte) {
	b.data = append(b.data, c)
}

func (b *buffer) writeAll(d []byte) {
	b.data = append(b.data, d...)
}

func (b *buffer) writeOneDigit(d byte) {
	b.write('0' + d)
}

func (b *buffer) writeTwoDigits(v int) {
	v = v % 100
	b.writeOneDigit(byte(v / 10))
	b.writeOneDigit(byte(v % 10))
}

func (b *buffer) writeSixDigits(v int) {
	v = v % 1000000
	b.writeOneDigit(byte(v / 100000))
	b.writeOneDigit(byte((v % 100000) / 10000))
	b.writeOneDigit(byte((v % 10000) / 1000))
	b.writeOneDigit(byte((v % 1000) / 100))
	b.writeOneDigit(byte((v % 100) / 10))
	b.writeOneDigit(byte(v % 10))
}

func calculateBytes(v int, pad int) []byte {
	var d []byte
	r := 1

	for n := 10; v >= r; n = n * 10 {
		d = append(d, '0'+byte((v%n)/r))
		r = n
	}

	for i := len(d); i < pad; i++ {
		d = append(d, ' ')
	}

	for i := 0; i < len(d)/2; i++ {
		d[i], d[len(d)-(i+1)] = d[len(d)-(i+1)], d[i]
	}
	return d
}

// pid is used for the threadid component of the header.
//
// The glog package logger uses 7 spaces of padding. See
// glob.loggingT.formatHeader.
var pid = calculateBytes(os.Getpid(), 7)

// caller is faked out as the caller. See FIXME below.
var caller = []byte("x:0")

// Emit emits the message, google-style.
func (g GoogleEmitter) Emit(level Level, timestamp time.Time, format string, args ...interface{}) {
	var b buffer
	b.start()

	// Log lines have this form:
	//   Lmmdd hh:mm:ss.uuuuuu threadid file:line] msg...
	//
	// where the fields are defined as follows:
	//   L                A single character, representing the log level (eg 'I' for INFO)
	//   mm               The month (zero padded; ie May is '05')
	//   dd               The day (zero padded)
	//   hh:mm:ss.uuuuuu  Time in hours, minutes and fractional seconds
	//   threadid         The space-padded thread ID as returned by GetTID()
	//   file             The file name
	//   line             The line number
	//   msg              The user-supplied message

	// Log level.
	switch level {
	case Debug:
		b.write('D')
	case Info:
		b.write('I')
	case Warning:
		b.write('W')
	}

	// Timestamp.
	_, month, day := timestamp.Date()
	hour, minute, second := timestamp.Clock()
	b.writeTwoDigits(int(month))
	b.writeTwoDigits(int(day))
	b.write(' ')
	b.writeTwoDigits(int(hour))
	b.write(':')
	b.writeTwoDigits(int(minute))
	b.write(':')
	b.writeTwoDigits(int(second))
	b.write('.')
	b.writeSixDigits(int(timestamp.Nanosecond() / 1000))
	b.write(' ')

	// The pid.
	b.writeAll(pid)
	b.write(' ')

	// FIXME(b/73383460): The caller, fabricated. This really sucks, but it
	// is unacceptable to put runtime.Callers() in the hot path.
	b.writeAll(caller)
	b.write(']')
	b.write(' ')

	// User-provided format string, copied.
	for i := 0; i < len(format); i++ {
		b.write(format[i])
	}

	// End with a newline.
	b.write('\n')

	// Pass to the underlying routine.
	g.Emitter.Emit(level, timestamp, b.String(), args...)
}