package json // import "github.com/tdewolff/parse/json"

import (
	"bytes"
	"fmt"
	"io"
	"testing"

	"github.com/tdewolff/parse"
	"github.com/tdewolff/test"
)

type GTs []GrammarType

func TestGrammars(t *testing.T) {
	var grammarTests = []struct {
		json     string
		expected []GrammarType
	}{
		{" \t\n\r", GTs{}}, // WhitespaceGrammar
		{"null", GTs{LiteralGrammar}},
		{"[]", GTs{StartArrayGrammar, EndArrayGrammar}},
		{"15.2", GTs{NumberGrammar}},
		{"0.4", GTs{NumberGrammar}},
		{"5e9", GTs{NumberGrammar}},
		{"-4E-3", GTs{NumberGrammar}},
		{"true", GTs{LiteralGrammar}},
		{"false", GTs{LiteralGrammar}},
		{"null", GTs{LiteralGrammar}},
		{`""`, GTs{StringGrammar}},
		{`"abc"`, GTs{StringGrammar}},
		{`"\""`, GTs{StringGrammar}},
		{`"\\"`, GTs{StringGrammar}},
		{"{}", GTs{StartObjectGrammar, EndObjectGrammar}},
		{`{"a": "b", "c": "d"}`, GTs{StartObjectGrammar, StringGrammar, StringGrammar, StringGrammar, StringGrammar, EndObjectGrammar}},
		{`{"a": [1, 2], "b": {"c": 3}}`, GTs{StartObjectGrammar, StringGrammar, StartArrayGrammar, NumberGrammar, NumberGrammar, EndArrayGrammar, StringGrammar, StartObjectGrammar, StringGrammar, NumberGrammar, EndObjectGrammar, EndObjectGrammar}},
		{"[null,]", GTs{StartArrayGrammar, LiteralGrammar, EndArrayGrammar}},
		// {"[\"x\\\x00y\", 0]", GTs{StartArrayGrammar, StringGrammar, NumberGrammar, EndArrayGrammar}},
	}
	for _, tt := range grammarTests {
		t.Run(tt.json, func(t *testing.T) {
			p := NewParser(bytes.NewBufferString(tt.json))
			i := 0
			for {
				grammar, _ := p.Next()
				if grammar == ErrorGrammar {
					test.T(t, p.Err(), io.EOF)
					test.T(t, i, len(tt.expected), "when error occurred we must be at the end")
					break
				} else if grammar == WhitespaceGrammar {
					continue
				}
				test.That(t, i < len(tt.expected), "index", i, "must not exceed expected grammar types size", len(tt.expected))
				if i < len(tt.expected) {
					test.T(t, grammar, tt.expected[i], "grammar types must match")
				}
				i++
			}
		})
	}

	test.T(t, WhitespaceGrammar.String(), "Whitespace")
	test.T(t, GrammarType(100).String(), "Invalid(100)")
	test.T(t, ValueState.String(), "Value")
	test.T(t, ObjectKeyState.String(), "ObjectKey")
	test.T(t, ObjectValueState.String(), "ObjectValue")
	test.T(t, ArrayState.String(), "Array")
	test.T(t, State(100).String(), "Invalid(100)")
}

func TestGrammarsError(t *testing.T) {
	var grammarErrorTests = []struct {
		json string
		col  int
	}{
		{"true, false", 5},
		{"[true false]", 7},
		{"]", 1},
		{"}", 1},
		{"{0: 1}", 2},
		{"{\"a\" 1}", 6},
		{"1.", 2},
		{"1e+", 2},
		{`{"":"`, 0},
		{"\"a\\", 0},
	}
	for _, tt := range grammarErrorTests {
		t.Run(tt.json, func(t *testing.T) {
			p := NewParser(bytes.NewBufferString(tt.json))
			for {
				grammar, _ := p.Next()
				if grammar == ErrorGrammar {
					if tt.col == 0 {
						test.T(t, p.Err(), io.EOF)
					} else if perr, ok := p.Err().(*parse.Error); ok {
						_, col, _ := perr.Position()
						test.T(t, col, tt.col)
					} else {
						test.Fail(t, "bad error:", p.Err())
					}
					break
				}
			}
		})
	}
}

func TestStates(t *testing.T) {
	var stateTests = []struct {
		json     string
		expected []State
	}{
		{"null", []State{ValueState}},
		{"[null]", []State{ArrayState, ArrayState, ValueState}},
		{"{\"\":null}", []State{ObjectKeyState, ObjectValueState, ObjectKeyState, ValueState}},
	}
	for _, tt := range stateTests {
		t.Run(tt.json, func(t *testing.T) {
			p := NewParser(bytes.NewBufferString(tt.json))
			i := 0
			for {
				grammar, _ := p.Next()
				state := p.State()
				if grammar == ErrorGrammar {
					test.T(t, p.Err(), io.EOF)
					test.T(t, i, len(tt.expected), "when error occurred we must be at the end")
					break
				} else if grammar == WhitespaceGrammar {
					continue
				}
				test.That(t, i < len(tt.expected), "index", i, "must not exceed expected states size", len(tt.expected))
				if i < len(tt.expected) {
					test.T(t, state, tt.expected[i], "states must match")
				}
				i++
			}
		})
	}
}

////////////////////////////////////////////////////////////////

func ExampleNewParser() {
	p := NewParser(bytes.NewBufferString(`{"key": 5}`))
	out := ""
	for {
		state := p.State()
		gt, data := p.Next()
		if gt == ErrorGrammar {
			break
		}
		out += string(data)
		if state == ObjectKeyState && gt != EndObjectGrammar {
			out += ":"
		}
		// not handling comma insertion
	}
	fmt.Println(out)
	// Output: {"key":5}
}
