// Copyright 2009 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 template

import (
	"bytes";
	"container/vector";
	"fmt";
	"io";
	"testing";
)

type Test struct {
	in, out, err string;
}

type T struct {
	item	string;
	value	string;
}

type S struct {
	header		string;
	integer		int;
	raw		string;
	innerT		T;
	innerPointerT	*T;
	data		[]T;
	pdata		[]*T;
	empty		[]*T;
	emptystring	string;
	null		[]*T;
	vec		*vector.Vector;
	true		bool;
	false		bool;
}

var t1 = T{"ItemNumber1", "ValueNumber1"}
var t2 = T{"ItemNumber2", "ValueNumber2"}

func uppercase(v interface{}) string {
	s := v.(string);
	t := "";
	for i := 0; i < len(s); i++ {
		c := s[i];
		if 'a' <= c && c <= 'z' {
			c = c+'A'-'a';
		}
		t += string(c);
	}
	return t;
}

func plus1(v interface{}) string {
	i := v.(int);
	return fmt.Sprint(i+1);
}

func writer(f func(interface{}) string) (func(io.Writer, interface{}, string)) {
	return func(w io.Writer, v interface{}, format string) { io.WriteString(w, f(v)) };
}


var formatters = FormatterMap{
	"uppercase": writer(uppercase),
	"+1": writer(plus1),
}

var tests = []*Test{
	// Simple
	&Test{"", "", ""},
	&Test{"abc\ndef\n", "abc\ndef\n", ""},
	&Test{" {.meta-left}   \n", "{", ""},
	&Test{" {.meta-right}   \n", "}", ""},
	&Test{" {.space}   \n", " ", ""},
	&Test{" {.tab}   \n", "\t", ""},
	&Test{"     {#comment}   \n", "", ""},

	// Variables at top level
	&Test{
		in: "{header}={integer}\n",

		out: "Header=77\n",
	},

	// Section
	&Test{
		in: "{.section data }\n"
		"some text for the section\n"
		"{.end}\n",

		out: "some text for the section\n",
	},
	&Test{
		in: "{.section data }\n"
		"{header}={integer}\n"
		"{.end}\n",

		out: "Header=77\n",
	},
	&Test{
		in: "{.section pdata }\n"
		"{header}={integer}\n"
		"{.end}\n",

		out: "Header=77\n",
	},
	&Test{
		in: "{.section pdata }\n"
		"data present\n"
		"{.or}\n"
		"data not present\n"
		"{.end}\n",

		out: "data present\n",
	},
	&Test{
		in: "{.section empty }\n"
		"data present\n"
		"{.or}\n"
		"data not present\n"
		"{.end}\n",

		out: "data not present\n",
	},
	&Test{
		in: "{.section null }\n"
		"data present\n"
		"{.or}\n"
		"data not present\n"
		"{.end}\n",

		out: "data not present\n",
	},
	&Test{
		in: "{.section pdata }\n"
		"{header}={integer}\n"
		"{.section @ }\n"
		"{header}={integer}\n"
		"{.end}\n"
		"{.end}\n",

		out: "Header=77\n"
		"Header=77\n",
	},
	&Test{
		in: "{.section data}{.end} {header}\n",

		out: " Header\n",
	},

	// Repeated
	&Test{
		in: "{.section pdata }\n"
		"{.repeated section @ }\n"
		"{item}={value}\n"
		"{.end}\n"
		"{.end}\n",

		out: "ItemNumber1=ValueNumber1\n"
		"ItemNumber2=ValueNumber2\n",
	},
	&Test{
		in: "{.section pdata }\n"
		"{.repeated section @ }\n"
		"{item}={value}\n"
		"{.or}\n"
		"this should not appear\n"
		"{.end}\n"
		"{.end}\n",

		out: "ItemNumber1=ValueNumber1\n"
		"ItemNumber2=ValueNumber2\n",
	},
	&Test{
		in: "{.section @ }\n"
		"{.repeated section empty }\n"
		"{item}={value}\n"
		"{.or}\n"
		"this should appear: empty field\n"
		"{.end}\n"
		"{.end}\n",

		out: "this should appear: empty field\n",
	},
	&Test{
		in: "{.repeated section pdata }\n"
		"{item}\n"
		"{.alternates with}\n"
		"is\nover\nmultiple\nlines\n"
		"{.end}\n",

		out: "ItemNumber1\n"
		"is\nover\nmultiple\nlines\n"
		"ItemNumber2\n",
	},
	&Test{
		in: "{.section pdata }\n"
		"{.repeated section @ }\n"
		"{item}={value}\n"
		"{.alternates with}DIVIDER\n"
		"{.or}\n"
		"this should not appear\n"
		"{.end}\n"
		"{.end}\n",

		out: "ItemNumber1=ValueNumber1\n"
		"DIVIDER\n"
		"ItemNumber2=ValueNumber2\n",
	},
	&Test{
		in: "{.repeated section vec }\n"
		"{@}\n"
		"{.end}\n",

		out: "elt1\n"
		"elt2\n",
	},
	&Test{
		in: "{.repeated section integer}{.end}",

		err: "line 1: .repeated: cannot repeat integer (type int)",
	},

	// Nested names
	&Test{
		in: "{.section @ }\n"
		"{innerT.item}={innerT.value}\n"
		"{.end}",

		out: "ItemNumber1=ValueNumber1\n",
	},
	&Test{
		in: "{.section @ }\n"
		"{innerT.item}={.section innerT}{.section value}{@}{.end}{.end}\n"
		"{.end}",

		out: "ItemNumber1=ValueNumber1\n",
	},


	// Formatters
	&Test{
		in: "{.section pdata }\n"
		"{header|uppercase}={integer|+1}\n"
		"{header|html}={integer|str}\n"
		"{.end}\n",

		out: "HEADER=78\n"
		"Header=77\n",
	},

	&Test{
		in: "{raw}\n"
		"{raw|html}\n",

		out: "&<>!@ #$%^\n"
		"&amp;&lt;&gt;!@ #$%^\n",
	},

	&Test{
		in: "{.section emptystring}emptystring{.end}\n"
		"{.section header}header{.end}\n",

		out: "\nheader\n",
	},

	&Test{
		in: "{.section true}1{.or}2{.end}\n"
		"{.section false}3{.or}4{.end}\n",

		out: "1\n4\n",
	},
}

func TestAll(t *testing.T) {
	s := new(S);
	// initialized by hand for clarity.
	s.header = "Header";
	s.integer = 77;
	s.raw = "&<>!@ #$%^";
	s.innerT = t1;
	s.data = []T{t1, t2};
	s.pdata = []*T{&t1, &t2};
	s.empty = []*T{};
	s.null = nil;
	s.vec = vector.New(0);
	s.vec.Push("elt1");
	s.vec.Push("elt2");
	s.true = true;
	s.false = false;

	var buf bytes.Buffer;
	for _, test := range tests {
		buf.Reset();
		tmpl, err := Parse(test.in, formatters);
		if err != nil {
			t.Error("unexpected parse error:", err);
			continue;
		}
		err = tmpl.Execute(s, &buf);
		if test.err == "" {
			if err != nil {
				t.Error("unexpected execute error:", err);
			}
		} else {
			if err == nil || err.String() != test.err {
				t.Errorf("expected execute error %q, got %q", test.err, err.String());
			}
		}
		if buf.String() != test.out {
			t.Errorf("for %q: expected %q got %q", test.in, test.out, buf.String());
		}
	}
}

func TestStringDriverType(t *testing.T) {
	tmpl, err := Parse("template: {@}", nil);
	if err != nil {
		t.Error("unexpected parse error:", err);
	}
	var b bytes.Buffer;
	err = tmpl.Execute("hello", &b);
	if err != nil {
		t.Error("unexpected execute error:", err);
	}
	s := b.String();
	if s != "template: hello" {
		t.Errorf("failed passing string as data: expected %q got %q", "template: hello", s);
	}
}

func TestTwice(t *testing.T) {
	tmpl, err := Parse("template: {@}", nil);
	if err != nil {
		t.Error("unexpected parse error:", err);
	}
	var b bytes.Buffer;
	err = tmpl.Execute("hello", &b);
	if err != nil {
		t.Error("unexpected parse error:", err);
	}
	s := b.String();
	text := "template: hello";
	if s != text {
		t.Errorf("failed passing string as data: expected %q got %q", text, s);
	}
	err = tmpl.Execute("hello", &b);
	if err != nil {
		t.Error("unexpected parse error:", err);
	}
	s = b.String();
	text += text;
	if s != text {
		t.Errorf("failed passing string as data: expected %q got %q", text, s);
	}
}

func TestCustomDelims(t *testing.T) {
	// try various lengths.  zero should catch error.
	for i := 0; i < 7; i++ {
		for j := 0; j < 7; j++ {
			tmpl := New(nil);
			// first two chars deliberately the same to test equal left and right delims
			ldelim := "$!#$%^&"[0:i];
			rdelim := "$*&^%$!"[0:j];
			tmpl.SetDelims(ldelim, rdelim);
			// if braces, this would be template: {@}{.meta-left}{.meta-right}
			text := "template: " +
				ldelim + "@" + rdelim +
				ldelim + ".meta-left" + rdelim +
				ldelim + ".meta-right" + rdelim;
			err := tmpl.Parse(text);
			if err != nil {
				if i == 0 || j == 0 {	// expected
					continue;
				}
				t.Error("unexpected parse error:", err);
			} else if i == 0 || j == 0 {
				t.Errorf("expected parse error for empty delimiter: %d %d %q %q", i, j, ldelim, rdelim);
				continue;
			}
			var b bytes.Buffer;
			err = tmpl.Execute("hello", &b);
			s := b.String();
			if s != "template: hello" + ldelim + rdelim {
				t.Errorf("failed delim check(%q %q) %q got %q", ldelim, rdelim, text, s);
			}
		}
	}
}

// Test that a variable evaluates to the field itself and does not further indirection
func TestVarIndirection(t *testing.T) {
	s := new(S);
	// initialized by hand for clarity.
	s.innerPointerT = &t1;

	var buf bytes.Buffer;
	input := "{.section @}{innerPointerT}{.end}";
	tmpl, err := Parse(input, nil);
	if err != nil {
		t.Fatal("unexpected parse error:", err);
	}
	err = tmpl.Execute(s, &buf);
	if err != nil {
		t.Fatal("unexpected execute error:", err);
	}
	expect := fmt.Sprintf("%v", &t1);	// output should be hex address of t1
	if buf.String() != expect {
		t.Errorf("for %q: expected %q got %q", input, expect, buf.String());
	}
}