// Copyright 2017 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 catmsg

import (
	"errors"
	"fmt"

	"golang.org/x/text/language"
)

// A Renderer renders a Message.
type Renderer interface {
	// Render renders the given string. The given string may be interpreted as a
	// format string, such as the one used by the fmt package or a template.
	Render(s string)

	// Arg returns the i-th argument passed to format a message. This method
	// should return nil if there is no such argument. Messages need access to
	// arguments to allow selecting a message based on linguistic features of
	// those arguments.
	Arg(i int) interface{}
}

// A Dictionary specifies a source of messages, including variables or macros.
type Dictionary interface {
	// Lookup returns the message for the given key. It returns false for ok if
	// such a message could not be found.
	Lookup(key string) (data string, ok bool)

	// TODO: consider returning an interface, instead of a string. This will
	// allow implementations to do their own message type decoding.
}

// An Encoder serializes a Message to a string.
type Encoder struct {
	// The root encoder is used for storing encoded variables.
	root *Encoder
	// The parent encoder provides the surrounding scopes for resolving variable
	// names.
	parent *Encoder

	tag language.Tag

	// buf holds the encoded message so far. After a message completes encoding,
	// the contents of buf, prefixed by the encoded length, are flushed to the
	// parent buffer.
	buf []byte

	// vars is the lookup table of variables in the current scope.
	vars []keyVal

	err    error
	inBody bool // if false next call must be EncodeMessageType
}

type keyVal struct {
	key    string
	offset int
}

// Language reports the language for which the encoded message will be stored
// in the Catalog.
func (e *Encoder) Language() language.Tag { return e.tag }

func (e *Encoder) setError(err error) {
	if e.root.err == nil {
		e.root.err = err
	}
}

// EncodeUint encodes x.
func (e *Encoder) EncodeUint(x uint64) {
	e.checkInBody()
	var buf [maxVarintBytes]byte
	n := encodeUint(buf[:], x)
	e.buf = append(e.buf, buf[:n]...)
}

// EncodeString encodes s.
func (e *Encoder) EncodeString(s string) {
	e.checkInBody()
	e.EncodeUint(uint64(len(s)))
	e.buf = append(e.buf, s...)
}

// EncodeMessageType marks the current message to be of type h.
//
// It must be the first call of a Message's Compile method.
func (e *Encoder) EncodeMessageType(h Handle) {
	if e.inBody {
		panic("catmsg: EncodeMessageType not the first method called")
	}
	e.inBody = true
	e.EncodeUint(uint64(h))
}

// EncodeMessage serializes the given message inline at the current position.
func (e *Encoder) EncodeMessage(m Message) error {
	e = &Encoder{root: e.root, parent: e}
	err := m.Compile(e)
	if _, ok := m.(*Var); !ok {
		e.flushTo(e.parent)
	}
	return err
}

func (e *Encoder) checkInBody() {
	if !e.inBody {
		panic("catmsg: expected prior call to EncodeMessageType")
	}
}

// stripPrefix indicates the number of prefix bytes that must be stripped to
// turn a single-element sequence into a message that is just this single member
// without its size prefix. If the message can be stripped, b[1:n] contains the
// size prefix.
func stripPrefix(b []byte) (n int) {
	if len(b) > 0 && Handle(b[0]) == msgFirst {
		x, n, _ := decodeUint(b[1:])
		if 1+n+int(x) == len(b) {
			return 1 + n
		}
	}
	return 0
}

func (e *Encoder) flushTo(dst *Encoder) {
	data := e.buf
	p := stripPrefix(data)
	if p > 0 {
		data = data[1:]
	} else {
		// Prefix the size.
		dst.EncodeUint(uint64(len(data)))
	}
	dst.buf = append(dst.buf, data...)
}

func (e *Encoder) addVar(key string, m Message) error {
	for _, v := range e.parent.vars {
		if v.key == key {
			err := fmt.Errorf("catmsg: duplicate variable %q", key)
			e.setError(err)
			return err
		}
	}
	scope := e.parent
	// If a variable message is Incomplete, and does not evaluate to a message
	// during execution, we fall back to the variable name. We encode this by
	// appending the variable name if the message reports it's incomplete.

	err := m.Compile(e)
	if err != ErrIncomplete {
		e.setError(err)
	}
	switch {
	case len(e.buf) == 1 && Handle(e.buf[0]) == msgFirst: // empty sequence
		e.buf = e.buf[:0]
		e.inBody = false
		fallthrough
	case len(e.buf) == 0:
		// Empty message.
		if err := String(key).Compile(e); err != nil {
			e.setError(err)
		}
	case err == ErrIncomplete:
		if Handle(e.buf[0]) != msgFirst {
			seq := &Encoder{root: e.root, parent: e}
			seq.EncodeMessageType(First)
			e.flushTo(seq)
			e = seq
		}
		// e contains a sequence; append the fallback string.
		e.EncodeMessage(String(key))
	}

	// Flush result to variable heap.
	offset := len(e.root.buf)
	e.flushTo(e.root)
	e.buf = e.buf[:0]

	// Record variable offset in current scope.
	scope.vars = append(scope.vars, keyVal{key: key, offset: offset})
	return err
}

const (
	substituteVar = iota
	substituteMacro
	substituteError
)

// EncodeSubstitution inserts a resolved reference to a variable or macro.
//
// This call must be matched with a call to ExecuteSubstitution at decoding
// time.
func (e *Encoder) EncodeSubstitution(name string, arguments ...int) {
	if arity := len(arguments); arity > 0 {
		// TODO: also resolve macros.
		e.EncodeUint(substituteMacro)
		e.EncodeString(name)
		for _, a := range arguments {
			e.EncodeUint(uint64(a))
		}
		return
	}
	for scope := e; scope != nil; scope = scope.parent {
		for _, v := range scope.vars {
			if v.key != name {
				continue
			}
			e.EncodeUint(substituteVar) // TODO: support arity > 0
			e.EncodeUint(uint64(v.offset))
			return
		}
	}
	// TODO: refer to dictionary-wide scoped variables.
	e.EncodeUint(substituteError)
	e.EncodeString(name)
	e.setError(fmt.Errorf("catmsg: unknown var %q", name))
}

// A Decoder deserializes and evaluates messages that are encoded by an encoder.
type Decoder struct {
	tag    language.Tag
	dst    Renderer
	macros Dictionary

	err  error
	vars string
	data string

	macroArg int // TODO: allow more than one argument
}

// NewDecoder returns a new Decoder.
//
// Decoders are designed to be reused for multiple invocations of Execute.
// Only one goroutine may call Execute concurrently.
func NewDecoder(tag language.Tag, r Renderer, macros Dictionary) *Decoder {
	return &Decoder{
		tag:    tag,
		dst:    r,
		macros: macros,
	}
}

func (d *Decoder) setError(err error) {
	if d.err == nil {
		d.err = err
	}
}

// Language returns the language in which the message is being rendered.
//
// The destination language may be a child language of the language used for
// encoding. For instance, a decoding language of "pt-PT"" is consistent with an
// encoding language of "pt".
func (d *Decoder) Language() language.Tag { return d.tag }

// Done reports whether there are more bytes to process in this message.
func (d *Decoder) Done() bool { return len(d.data) == 0 }

// Render implements Renderer.
func (d *Decoder) Render(s string) { d.dst.Render(s) }

// Arg implements Renderer.
//
// During evaluation of macros, the argument positions may be mapped to
// arguments that differ from the original call.
func (d *Decoder) Arg(i int) interface{} {
	if d.macroArg != 0 {
		if i != 1 {
			panic("catmsg: only macros with single argument supported")
		}
		i = d.macroArg
	}
	return d.dst.Arg(i)
}

// DecodeUint decodes a number that was encoded with EncodeUint and advances the
// position.
func (d *Decoder) DecodeUint() uint64 {
	x, n, err := decodeUintString(d.data)
	d.data = d.data[n:]
	if err != nil {
		d.setError(err)
	}
	return x
}

// DecodeString decodes a string that was encoded with EncodeString and advances
// the position.
func (d *Decoder) DecodeString() string {
	size := d.DecodeUint()
	s := d.data[:size]
	d.data = d.data[size:]
	return s
}

// SkipMessage skips the message at the current location and advances the
// position.
func (d *Decoder) SkipMessage() {
	n := int(d.DecodeUint())
	d.data = d.data[n:]
}

// Execute decodes and evaluates msg.
//
// Only one goroutine may call execute.
func (d *Decoder) Execute(msg string) error {
	d.err = nil
	if !d.execute(msg) {
		return ErrNoMatch
	}
	return d.err
}

func (d *Decoder) execute(msg string) bool {
	saved := d.data
	d.data = msg
	ok := d.executeMessage()
	d.data = saved
	return ok
}

// executeMessageFromData is like execute, but also decodes a leading message
// size and clips the given string accordingly.
//
// It reports the number of bytes consumed and whether a message was selected.
func (d *Decoder) executeMessageFromData(s string) (n int, ok bool) {
	saved := d.data
	d.data = s
	size := int(d.DecodeUint())
	n = len(s) - len(d.data)
	// Sanitize the setting. This allows skipping a size argument for
	// RawString and method Done.
	d.data = d.data[:size]
	ok = d.executeMessage()
	n += size - len(d.data)
	d.data = saved
	return n, ok
}

var errUnknownHandler = errors.New("catmsg: string contains unsupported handler")

// executeMessage reads the handle id, initializes the decoder and executes the
// message. It is assumed that all of d.data[d.p:] is the single message.
func (d *Decoder) executeMessage() bool {
	if d.Done() {
		// We interpret no data as a valid empty message.
		return true
	}
	handle := d.DecodeUint()

	var fn Handler
	mutex.Lock()
	if int(handle) < len(handlers) {
		fn = handlers[handle]
	}
	mutex.Unlock()
	if fn == nil {
		d.setError(errUnknownHandler)
		d.execute(fmt.Sprintf("\x02$!(UNKNOWNMSGHANDLER=%#x)", handle))
		return true
	}
	return fn(d)
}

// ExecuteMessage decodes and executes the message at the current position.
func (d *Decoder) ExecuteMessage() bool {
	n, ok := d.executeMessageFromData(d.data)
	d.data = d.data[n:]
	return ok
}

// ExecuteSubstitution executes the message corresponding to the substitution
// as encoded by EncodeSubstitution.
func (d *Decoder) ExecuteSubstitution() {
	switch x := d.DecodeUint(); x {
	case substituteVar:
		offset := d.DecodeUint()
		d.executeMessageFromData(d.vars[offset:])
	case substituteMacro:
		name := d.DecodeString()
		data, ok := d.macros.Lookup(name)
		old := d.macroArg
		// TODO: support macros of arity other than 1.
		d.macroArg = int(d.DecodeUint())
		switch {
		case !ok:
			// TODO: detect this at creation time.
			d.setError(fmt.Errorf("catmsg: undefined macro %q", name))
			fallthrough
		case !d.execute(data):
			d.dst.Render(name) // fall back to macro name.
		}
		d.macroArg = old
	case substituteError:
		d.dst.Render(d.DecodeString())
	default:
		panic("catmsg: unreachable")
	}
}
