/*
Copyright 2015 The Kubernetes Authors All rights reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package jsonpath

import (
	"bytes"
	"encoding/json"
	"testing"
)

type jsonpathTest struct {
	name     string
	template string
	input    interface{}
	expect   string
}

func testJSONPath(tests []jsonpathTest, t *testing.T) {
	for _, test := range tests {
		j := New(test.name)
		err := j.Parse(test.template)
		if err != nil {
			t.Errorf("in %s, parse %s error %v", test.name, test.template, err)
		}
		buf := new(bytes.Buffer)
		err = j.Execute(buf, test.input)
		if err != nil {
			t.Errorf("in %s, execute error %v", test.name, err)
		}
		out := buf.String()
		if out != test.expect {
			t.Errorf(`in %s, expect to get "%s", got "%s"`, test.name, test.expect, out)
		}
	}
}

func testFailJSONPath(tests []jsonpathTest, t *testing.T) {
	for _, test := range tests {
		j := New(test.name)
		err := j.Parse(test.template)
		if err != nil {
			t.Errorf("in %s, parse %s error %v", test.name, test.template, err)
		}
		buf := new(bytes.Buffer)
		err = j.Execute(buf, test.input)
		var out string
		if err == nil {
			out = "nil"
		} else {
			out = err.Error()
		}
		if out != test.expect {
			t.Errorf("in %s, expect to get error %s, got %s", test.name, test.expect, out)
		}
	}
}

func TestStructInput(t *testing.T) {
	type book struct {
		Category string
		Author   string
		Title    string
		Price    float32
	}

	type bicycle struct {
		Color string
		Price float32
	}

	type store struct {
		Book    []book
		Bicycle bicycle
		Name    string
		Labels  map[string]int
	}

	storeData := store{
		Name: "jsonpath",
		Book: []book{
			{"reference", "Nigel Rees", "Sayings of the Centurey", 8.95},
			{"fiction", "Evelyn Waugh", "Sword of Honour", 12.99},
			{"fiction", "Herman Melville", "Moby Dick", 8.99},
		},
		Bicycle: bicycle{"red", 19.95},
		Labels: map[string]int{
			"engieer":  10,
			"web/html": 15,
			"k8s-app":  20,
		},
	}

	storeTests := []jsonpathTest{
		{"plain", "hello jsonpath", nil, "hello jsonpath"},
		{"recursive", "{..}", []int{1, 2, 3}, "[1, 2, 3]"},
		{"filter", "{[?(@<5)]}", []int{2, 6, 3, 7}, "2 3"},
		{"quote", `{"{"}`, nil, "{"},
		{"union", "{[1,3,4]}", []int{0, 1, 2, 3, 4}, "1 3 4"},
		{"array", "{[0:2]}", []string{"Monday", "Tudesday"}, "Monday Tudesday"},
		{"variable", "hello {.Name}", storeData, "hello jsonpath"},
		{"dict/", "{.Labels.web/html}", storeData, "15"},
		{"dict-", "{.Labels.k8s-app}", storeData, "20"},
		{"nest", "{.Bicycle.Color}", storeData, "red"},
		{"allarray", "{.Book[*].Author}", storeData, "Nigel Rees Evelyn Waugh Herman Melville"},
		{"allfileds", "{.Bicycle.*}", storeData, "red 19.95"},
		{"recurfileds", "{..Price}", storeData, "8.95 12.99 8.99 19.95"},
		{"lastarray", "{.Book[-1:]}", storeData,
			"{Category: fiction, Author: Herman Melville, Title: Moby Dick, Price: 8.99}"},
		{"recurarray", "{..Book[2]}", storeData,
			"{Category: fiction, Author: Herman Melville, Title: Moby Dick, Price: 8.99}"},
	}
	testJSONPath(storeTests, t)

	failStoreTests := []jsonpathTest{
		{"invalid identfier", "{hello}", storeData, "unrecongnized identifier hello"},
		{"nonexistent field", "{.hello}", storeData, "hello is not found"},
		{"invalid array", "{.Labels[0]}", storeData, "<map[string]int Value> is not array or slice"},
		{"invalid filter operator", "{.Book[?(@.Price<>10)]}", storeData, "unrecognized filter operator <>"},
		{"redundent end", "{range .Labels.*}{@}{end}{end}", storeData, "not in range, nothing to end"},
	}
	testFailJSONPath(failStoreTests, t)
}

func TestJSONInput(t *testing.T) {
	var pointsJSON = []byte(`[
		{"id": "i1", "x":4, "y":-5},
		{"id": "i2", "x":-2, "y":-5, "z":1},
		{"id": "i3", "x":  8, "y":  3 },
		{"id": "i4", "x": -6, "y": -1 },
		{"id": "i5", "x":  0, "y":  2, "z": 1 },
		{"id": "i6", "x":  1, "y":  4 }
	]`)
	var pointsData interface{}
	err := json.Unmarshal(pointsJSON, &pointsData)
	if err != nil {
		t.Error(err)
	}
	pointsTests := []jsonpathTest{
		{"exists filter", "{[?(@.z)].id}", pointsData, "i2 i5"},
		{"bracket key", "{[0]['id']}", pointsData, "i1"},
	}
	testJSONPath(pointsTests, t)
}

// TestKubenates tests some use cases from kubenates
func TestKubenates(t *testing.T) {
	var input = []byte(`{
	  "kind": "List",
	  "items":[
		{
		  "kind":"None",
		  "metadata":{"name":"127.0.0.1"},
		  "status":{
			"capacity":{"cpu":"4"},
			"addresses":[{"type": "LegacyHostIP", "address":"127.0.0.1"}]
		  }
		},
		{
		  "kind":"None",
		  "metadata":{"name":"127.0.0.2"},
		  "status":{
			"capacity":{"cpu":"8"},
			"addresses":[
			  {"type": "LegacyHostIP", "address":"127.0.0.2"},
			  {"type": "another", "address":"127.0.0.3"}
			]
		  }
		}
	  ],
	  "users":[
	    {
	      "name": "myself",
	      "user": {}
	    },
	    {
	      "name": "e2e",
	      "user": {"username": "admin", "password": "secret"}
	  	}
	  ]
	}`)
	var nodesData interface{}
	err := json.Unmarshal(input, &nodesData)
	if err != nil {
		t.Error(err)
	}
	nodesTests := []jsonpathTest{
		{"range item", "{range .items[*]}{.metadata.name}, {end}{.kind}", nodesData, `127.0.0.1, 127.0.0.2, List`},
		{"range addresss", "{.items[*].status.addresses[*].address}", nodesData,
			`127.0.0.1 127.0.0.2 127.0.0.3`},
		{"double range", "{range .items[*]}{range .status.addresses[*]}{.address}, {end}{end}", nodesData,
			`127.0.0.1, 127.0.0.2, 127.0.0.3, `},
		{"item name", "{.items[*].metadata.name}", nodesData, `127.0.0.1 127.0.0.2`},
		{"union nodes capacity", "{.items[*]['metadata.name', 'status.capacity']}", nodesData,
			`127.0.0.1 127.0.0.2 {cpu: 4} {cpu: 8}`},
		{"range nodes capacity", "{range .items[*]}[{.metadata.name}, {.status.capacity}] {end}", nodesData,
			`[127.0.0.1, {cpu: 4}] [127.0.0.2, {cpu: 8}] `},
		{"user password", `{.users[?(@.name=="e2e")].user.password}`, nodesData, "secret"},
	}
	testJSONPath(nodesTests, t)
}
