// Copyright 2014 The Go 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 controller

import (
	"bytes"
	"encoding/binary"
	"errors"
	"fmt"
	"io"
	"sync"
	"time"
	"unicode/utf8"
)

// When sandbox time begins.
var epoch = time.Unix(1257894000, 0)

// Recorder records the standard and error outputs of a sandbox program
// (comprised of playback headers) and converts it to a sequence of Events.
// It sanitizes each Event's Message to ensure it is valid UTF-8.
//
// Playground programs precede all their writes with a header (described
// below) that describes the time the write occurred (in playground time) and
// the length of the data that will be written. If a non-header is
// encountered where a header is expected, the output is scanned for the next
// header and the intervening text string is added to the sequence an event
// occurring at the same time as the preceding event.
//
// A playback header has this structure:
//
//	4 bytes: "\x00\x00PB", a magic header
//	8 bytes: big-endian int64, unix time in nanoseconds
//	4 bytes: big-endian int32, length of the next write
type Recorder struct {
	stdout, stderr recorderWriter
}

func (r *Recorder) Stdout() io.Writer { return &r.stdout }
func (r *Recorder) Stderr() io.Writer { return &r.stderr }

type recorderWriter struct {
	mu     sync.Mutex
	writes []byte
}

func (w *recorderWriter) bytes() []byte {
	w.mu.Lock()
	defer w.mu.Unlock()
	return w.writes[0:len(w.writes):len(w.writes)]
}

func (w *recorderWriter) Write(b []byte) (n int, err error) {
	w.mu.Lock()
	defer w.mu.Unlock()
	w.writes = append(w.writes, b...)
	return len(b), nil
}

type Event struct {
	Message string
	Kind    string        // "stdout" or "stderr"
	Delay   time.Duration // time to wait before printing Message
}

func (r *Recorder) Events() ([]Event, error) {
	stdout, stderr := r.stdout.bytes(), r.stderr.bytes()

	evOut, err := decode("stdout", stdout)
	if err != nil {
		return nil, err
	}
	evErr, err := decode("stderr", stderr)
	if err != nil {
		return nil, err
	}

	events := sortedMerge(evOut, evErr)

	var (
		out []Event
		now = epoch
	)

	for _, e := range events {
		delay := e.time.Sub(now)
		if delay < 0 {
			delay = 0
		}
		out = append(out, Event{
			Message: string(sanitize(e.msg)),
			Kind:    e.kind,
			Delay:   delay,
		})
		if delay > 0 {
			now = e.time
		}
	}
	return out, nil
}

type event struct {
	msg  []byte
	kind string
	time time.Time
}

func decode(kind string, output []byte) ([]event, error) {
	var (
		magic     = []byte{0, 0, 'P', 'B'}
		headerLen = 8 + 4
		last      = epoch
		events    []event
	)
	add := func(t time.Time, b []byte) {
		var prev *event
		if len(events) > 0 {
			prev = &events[len(events)-1]
		}
		if prev != nil && t.Equal(prev.time) {
			// Merge this event with previous event, to avoid
			// sending a lot of events for a big output with no
			// significant timing information.
			prev.msg = append(prev.msg, b...)
		} else {
			e := event{msg: b, kind: kind, time: t}
			events = append(events, e)
		}
		last = t
	}
	for i := 0; i < len(output); {
		if !bytes.HasPrefix(output[i:], magic) {
			// Not a header; find next header.
			j := bytes.Index(output[i:], magic)
			if j < 0 {
				// No more headers; bail.
				add(last, output[i:])
				break
			}
			add(last, output[i:i+j])
			i += j
		}
		i += len(magic)

		// Decode header.
		if len(output)-i < headerLen {
			return nil, errors.New("short header")
		}
		header := output[i : i+headerLen]
		nanos := int64(binary.BigEndian.Uint64(header[0:]))
		t := time.Unix(0, nanos)
		if t.Before(last) {
			// Force timestamps to be monotonic. (This could
			// be an encoding error, which we ignore now but will
			// likely be picked up when decoding the length.)
			t = last
		}
		n := int(binary.BigEndian.Uint32(header[8:]))
		if n < 0 {
			return nil, fmt.Errorf("bad length: %v", n)
		}
		i += headerLen

		// Slurp output.
		// Truncated output is OK (probably caused by sandbox limits).
		end := i + n
		if end > len(output) {
			end = len(output)
		}
		add(t, output[i:end])
		i += n
	}
	return events, nil
}

// Sorted merge of two slices of events into one slice.
func sortedMerge(a, b []event) []event {
	if len(a) == 0 {
		return b
	}
	if len(b) == 0 {
		return a
	}

	sorted := make([]event, 0, len(a)+len(b))
	i, j := 0, 0
	for i < len(a) && j < len(b) {
		if a[i].time.Before(b[j].time) {
			sorted = append(sorted, a[i])
			i++
		} else {
			sorted = append(sorted, b[j])
			j++
		}
	}
	sorted = append(sorted, a[i:]...)
	sorted = append(sorted, b[j:]...)
	return sorted
}

// sanitize scans b for invalid utf8 code points. If found, it reconstructs
// the slice replacing the invalid codes with \uFFFD, properly encoded.
func sanitize(b []byte) []byte {
	if utf8.Valid(b) {
		return b
	}
	var buf bytes.Buffer
	for len(b) > 0 {
		r, size := utf8.DecodeRune(b)
		b = b[size:]
		buf.WriteRune(r)
	}
	return buf.Bytes()
}
