// Copyright (c) 2014 Alex Kalyvitis

package mustache

import (
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"reflect"
	"strings"
)

// The node type is the base type that represents a node in the parse tree.
type node interface {
	// The render function should be defined by any type wishing to satisfy the
	// node interface. Implementations should be able to render itself to the
	// w Writer with c given as context.
	render(t *Template, w *writer, c ...interface{}) error
}

// The textNode type represents a part of the template that is made up solely of
// text. It's an alias to string and it ignores c when rendering.
type textNode string

func (n textNode) render(t *Template, w *writer, c ...interface{}) error {
	for _, r := range n {
		if !whitespace(r) {
			w.text()
		}
		err := w.write(r)
		if err != nil {
			return err
		}
	}
	return nil
}

func (n textNode) String() string {
	return fmt.Sprintf("[text: %q]", string(n))
}

// The varNode type represents a part of the template that needs to be replaced
// by a variable that exists within c.
type varNode struct {
	name   string
	escape bool
}

func (n *varNode) render(t *Template, w *writer, c ...interface{}) error {
	w.text()
	v, _ := lookup(n.name, c...)
	// If the value is present but 'falsy', such as a false bool, or a zero int,
	// we still want to render that value.
	if v != nil {
		if n.escape {
			v = escape(fmt.Sprintf("%v", v))
		}
		print(w, v)
		return nil
	}
	return fmt.Errorf("failed to lookup %s", n.name)
}

func (n *varNode) String() string {
	return fmt.Sprintf("[var: %q escaped: %t]", n.name, n.escape)
}

// The sectionNode type is a complex node which recursively renders its child
// elements while passing along its context along with the global context.
type sectionNode struct {
	name     string
	inverted bool
	elems    []node
}

func (n *sectionNode) render(t *Template, w *writer, c ...interface{}) error {
	w.tag()
	defer w.tag()
	elemFn := func(v ...interface{}) {
		for _, elem := range n.elems {
			elem.render(t, w, append(v, c...)...)
		}
	}
	v, ok := lookup(n.name, c...)
	if ok != n.inverted {
		r := reflect.ValueOf(v)
		switch r.Kind() {
		case reflect.Slice, reflect.Array:
			if r.Len() > 0 {
				for i := 0; i < r.Len(); i++ {
					elemFn(r.Index(i).Interface())
				}
			} else {
				elemFn(v)
			}
		default:
			elemFn(v)
		}
		return nil
	}
	return fmt.Errorf("failed to lookup %s", n.name)
}

func (n *sectionNode) String() string {
	return fmt.Sprintf("[section: %q inv: %t elems: %s]", n.name, n.inverted, n.elems)
}

// The commentNode type is a part of the template wich gets ignored. Perhaps it
// can be optionally enabled to print comments.
type commentNode string

func (n commentNode) render(t *Template, w *writer, c ...interface{}) error {
	w.tag()
	return nil
}

func (n commentNode) String() string {
	return fmt.Sprintf("[comment: %q]", string(n))
}

// The partialNode type represents a named partial template.
type partialNode struct {
	name string
}

func (p *partialNode) render(t *Template, w *writer, c ...interface{}) error {
	w.tag()
	if template, ok := t.partials[p.name]; ok {
		template.partials = t.partials
		template.render(w, c...)
	}
	return nil
}

func (p *partialNode) String() string {
	return fmt.Sprintf("[partial: %s]", p.name)
}

type delimNode string

func (n delimNode) String() string {
	return "[delim]"
}

func (n delimNode) render(t *Template, w *writer, c ...interface{}) error {
	w.tag()
	return nil
}

// The print function is able to format the interface v and write it to w using
// the best possible formatting flags.
func print(w io.Writer, v interface{}) {
	if s, ok := v.(fmt.Stringer); ok {
		fmt.Fprint(w, s.String())
	} else {
		switch v.(type) {
		case string:
			fmt.Fprintf(w, "%s", v)
		case int, uint, int8, uint8, int16, uint16, int32, uint32, int64, uint64:
			fmt.Fprintf(w, "%d", v)
		case float32, float64:
			fmt.Fprintf(w, "%g", v)
		default:
			fmt.Fprintf(w, "%v", v)
		}
	}
}

// The escape function replicates the text/template.HTMLEscapeString but keeps
// "&apos;" and "&quot;" for compatibility with the mustache spec.
func escape(s string) string {
	if strings.IndexAny(s, `'"&<>`) < 0 {
		return s
	}
	var b bytes.Buffer
	for _, r := range s {
		switch r {
		case '"':
			b.WriteString("&quot;")
		case '\'':
			b.WriteString("&apos;")
		case '&':
			b.WriteString("&amp;")
		case '<':
			b.WriteString("&lt;")
		case '>':
			b.WriteString("&gt;")
		default:
			b.WriteRune(r)
		}
	}
	return b.String()
}

// The Option type describes functional options used with Templates. Check out
// Dave Cheney's talk on functional options http://bit.ly/1x9WWPi.
type Option func(*Template)

// Name sets the name of the template.
func Name(n string) Option {
	return func(t *Template) {
		t.name = n
	}
}

// Delimiters sets the start and end delimiters of the template.
func Delimiters(start, end string) Option {
	return func(t *Template) {
		t.startDelim = start
		t.endDelim = end
	}
}

// Partial sets p as a partial to the template. It is important to set the name
// of p so that it may be looked up by the parent template.
func Partial(p *Template) Option {
	return func(t *Template) {
		t.partials[p.name] = p
	}
}

// Errors enables missing variable errors. This option is deprecated. Please
// use SilentMiss instead.
func Errors() Option {
	return func(t *Template) {
		t.silentMiss = false
	}
}

// SilentMiss sets the silent miss behavior of variable lookups when rendering.
// If true, missed lookups will not produce any errors. Otherwise a missed
// variable lookup will stop the rendering and return an error.
func SilentMiss(silent bool) Option {
	return func(t *Template) {
		t.silentMiss = silent
	}
}

// The Template type represents a template and its components.
type Template struct {
	name       string
	elems      []node
	partials   map[string]*Template
	startDelim string
	endDelim   string
	silentMiss bool
}

// New returns a new Template instance.
func New(options ...Option) *Template {
	t := &Template{
		elems:      make([]node, 0),
		partials:   make(map[string]*Template),
		startDelim: "{{",
		endDelim:   "}}",
		silentMiss: true,
	}
	t.Option(options...)
	return t
}

// Option applies options to the currrent template t.
func (t *Template) Option(options ...Option) {
	for _, optionFn := range options {
		optionFn(t)
	}
}

// Parse parses a stream of bytes read from r and creates a parse tree that
// represents the template.
func (t *Template) Parse(r io.Reader) error {
	b, err := ioutil.ReadAll(r)
	if err != nil {
		return err
	}
	l := newLexer(string(b), t.startDelim, t.endDelim)
	p := newParser(l)
	elems, err := p.parse()
	if err != nil {
		return err
	}
	t.elems = elems
	return nil
}

// ParseString is a helper function that uses a string as input.
func (t *Template) ParseString(s string) error {
	return t.Parse(strings.NewReader(s))
}

// ParseBytes is a helper function that uses a byte array as input.
func (t *Template) ParseBytes(b []byte) error {
	return t.Parse(bytes.NewReader(b))
}

func (t *Template) render(w *writer, context ...interface{}) error {
	for _, elem := range t.elems {
		err := elem.render(t, w, context...)
		if err != nil {
			if !t.silentMiss {
				return err
			}
		}
	}
	return w.flush()
}

// Render walks through the template's parse tree and writes the output to w
// replacing the values found in context.
func (t *Template) Render(w io.Writer, context ...interface{}) error {
	return t.render(newWriter(w), context...)
}

// RenderString is a helper function that renders the template as a string.
func (t *Template) RenderString(context ...interface{}) (string, error) {
	b := &bytes.Buffer{}
	err := t.Render(b, context...)
	return b.String(), err
}

// RenderBytes is a helper function that renders the template as a byte slice.
func (t *Template) RenderBytes(context ...interface{}) ([]byte, error) {
	var b *bytes.Buffer
	err := t.Render(b, context...)
	return b.Bytes(), err
}

// Parse wraps the creation of a new template and parsing from r in one go.
func Parse(r io.Reader) (*Template, error) {
	t := New()
	err := t.Parse(r)
	return t, err
}

// Render wraps the parsing and rendering into a single function.
func Render(r io.Reader, w io.Writer, context ...interface{}) error {
	t, err := Parse(r)
	if err != nil {
		return err
	}
	return t.Render(w, context...)
}
