package jsonlog

import (
	"bytes"
	"encoding/json"
	"time"
	"unicode/utf8"
)

// JSONLogs marshals encoded JSONLog objects
type JSONLogs struct {
	Log     []byte    `json:"log,omitempty"`
	Stream  string    `json:"stream,omitempty"`
	Created time.Time `json:"time"`

	// json-encoded bytes
	RawAttrs json.RawMessage `json:"attrs,omitempty"`
}

// MarshalJSONBuf is an optimized JSON marshaller that avoids reflection
// and unnecessary allocation.
func (mj *JSONLogs) MarshalJSONBuf(buf *bytes.Buffer) error {
	var first = true

	buf.WriteString(`{`)
	if len(mj.Log) != 0 {
		first = false
		buf.WriteString(`"log":`)
		ffjsonWriteJSONBytesAsString(buf, mj.Log)
	}
	if len(mj.Stream) != 0 {
		if first {
			first = false
		} else {
			buf.WriteString(`,`)
		}
		buf.WriteString(`"stream":`)
		ffjsonWriteJSONBytesAsString(buf, []byte(mj.Stream))
	}
	if len(mj.RawAttrs) > 0 {
		if first {
			first = false
		} else {
			buf.WriteString(`,`)
		}
		buf.WriteString(`"attrs":`)
		buf.Write(mj.RawAttrs)
	}
	if !first {
		buf.WriteString(`,`)
	}

	created, err := fastTimeMarshalJSON(mj.Created)
	if err != nil {
		return err
	}

	buf.WriteString(`"time":`)
	buf.WriteString(created)
	buf.WriteString(`}`)
	return nil
}

func ffjsonWriteJSONBytesAsString(buf *bytes.Buffer, s []byte) {
	const hex = "0123456789abcdef"

	buf.WriteByte('"')
	start := 0
	for i := 0; i < len(s); {
		if b := s[i]; b < utf8.RuneSelf {
			if 0x20 <= b && b != '\\' && b != '"' && b != '<' && b != '>' && b != '&' {
				i++
				continue
			}
			if start < i {
				buf.Write(s[start:i])
			}
			switch b {
			case '\\', '"':
				buf.WriteByte('\\')
				buf.WriteByte(b)
			case '\n':
				buf.WriteByte('\\')
				buf.WriteByte('n')
			case '\r':
				buf.WriteByte('\\')
				buf.WriteByte('r')
			default:

				buf.WriteString(`\u00`)
				buf.WriteByte(hex[b>>4])
				buf.WriteByte(hex[b&0xF])
			}
			i++
			start = i
			continue
		}
		c, size := utf8.DecodeRune(s[i:])
		if c == utf8.RuneError && size == 1 {
			if start < i {
				buf.Write(s[start:i])
			}
			buf.WriteString(`\ufffd`)
			i += size
			start = i
			continue
		}

		if c == '\u2028' || c == '\u2029' {
			if start < i {
				buf.Write(s[start:i])
			}
			buf.WriteString(`\u202`)
			buf.WriteByte(hex[c&0xF])
			i += size
			start = i
			continue
		}
		i += size
	}
	if start < len(s) {
		buf.Write(s[start:])
	}
	buf.WriteByte('"')
}
