/*
 * Copyright 2021 ByteDance Inc.
 *
 * 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 ast

import (
	"fmt"
	"strconv"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func getTestIteratorSample(loop int) (string, int) {
    var data []int
    var v1 = ""
    var v2 = ""
    for i:=0;i<loop;i++{
        data = append(data, i*i)
        v1 += strconv.Itoa(i)
        v2 += `"k`+strconv.Itoa(i)+`":`+strconv.Itoa(i)
        if i!=loop-1{
            v1+=`,`
            v2+=`,`
        }
    }
    return `{"array":[`+v1+`], "object":{`+v2+`}}`, loop
}

func TestForEach(t *testing.T) {
    pathes := []Sequence{}
    values := []*Node{}
    sc := func(path Sequence, node *Node) bool {
        pathes = append(pathes, path)
        values = append(values, node)
        if path.Key != nil && *path.Key == "array" {
            node.ForEach(func(path Sequence, node *Node)bool{
                pathes = append(pathes, path)
                values = append(values, node)
                return true
            })
        }
        return true
    }

    str, _ := getTestIteratorSample(3)
    fmt.Println(str)
    root, err := NewSearcher(str).GetByPath()
    if err != nil {
        t.Fatal(err)
    }
    err = root.ForEach(sc)
    if err != nil {
        t.Fatal(err)
    }
    eObjKey := "object"
    eArrKey := "array"
    expPath := []Sequence{
        {0, &eArrKey},
        {0, nil},
        {1, nil},
        {2, nil},
        {1, &eObjKey},
    }
    expValue := []*Node{
        root.Get("array"),
        root.GetByPath("array", 0),
        root.GetByPath("array", 1),
        root.GetByPath("array", 2),
        root.Get("object"),
    }
    // fmt.Printf("pathes:%+v\n", pathes)
    // fmt.Printf("values:%+v\n", values)
    assert.Equal(t, expPath, pathes)
    assert.Equal(t, expValue, values)
    
}

func TestRawIterator(t *testing.T) {
    str, loop := getTestIteratorSample(_DEFAULT_NODE_CAP)
    fmt.Println(str)
    
    root, err := NewSearcher(str).GetByPath("array")
    if err != nil {
        t.Fatal(err)
    }
    ai, _ := root.Values()
    i := int64(0)
    for ai.HasNext() {
        v := &Node{}
        if !ai.Next(v) {
            t.Fatalf("no next")
        }
        x, _ := v.Int64()
        if i < int64(loop) && x != i {
            t.Fatalf("exp:%v, got:%v", i, v)
        }
        if i != int64(ai.Pos())-1 || i >= int64(ai.Len()) {
            t.Fatal(i)
        }
        i++
    }
    if i != int64(loop) {
        t.Fatal(i)
    }
    
    root, err = NewSearcher(str).GetByPath("object")
    if err != nil {
        t.Fatal(err)
    }
    mi, _ := root.Properties()
    i = int64(0)
    for mi.HasNext() {
        v := &Pair{}
        if !mi.Next(v) {
            t.Fatalf("no next")
        }
        x, _ := v.Value.Int64()
        if i < int64(loop) &&( x != i ||v.Key != fmt.Sprintf("k%d", i)) {
            vv, _ := v.Value.Interface()
            t.Fatalf("exp:%v, got:%v", i, vv)
        }
        if i != int64(mi.Pos())-1 || i >= int64(mi.Len()) {
            t.Fatal(i)
        }
        i++
    }
    if i != int64(loop) {
        t.Fatal(i)
    }
}

func TestIterator(t *testing.T) {
    str, loop := getTestIteratorSample(_DEFAULT_NODE_CAP)
    fmt.Println(str)

    root, err := NewParser(str).Parse()
    if err != 0 {
        t.Fatal(err)
    }
    ai, _ := root.Get("array").Values()
    i := int64(0)
    for ai.HasNext() {
        v := &Node{}
        if !ai.Next(v) {
            t.Fatalf("no next")
        }
        x, _ := v.Int64()
        if i < int64(loop) && x != i {
            t.Fatalf("exp:%v, got:%v", i, v)
        }
        if i != int64(ai.Pos())-1 || i >= int64(ai.Len()) {
            t.Fatal(i)
        }
        i++
    }
    if i != int64(loop) {
        t.Fatal(i)
    }

    root, err = NewParser(str).Parse()
    if err != 0 {
        t.Fatal(err)
    }
    mi, _ := root.Get("object").Properties()
    i = int64(0)
    for mi.HasNext() {
        v := &Pair{}
        if !mi.Next(v) {
            t.Fatalf("no next")
        }
        x, _ := v.Value.Int64()
        if i < int64(loop) &&( x != i ||v.Key != fmt.Sprintf("k%d", i)) {
            vv, _ := v.Value.Interface()
            t.Fatalf("exp:%v, got:%v", i, vv)
        }
        if i != int64(mi.Pos())-1 || i >= int64(mi.Len()) {
            t.Fatal(i)
        }
        i++
    }
    if i != int64(loop) {
        t.Fatal(i)
    }

    str, _ = getTestIteratorSample(0)
    root, err = NewParser(str).Parse()
    if err != 0 {
        t.Fatal(err)
    }
    mi, _ = root.Get("object").Properties()
    if mi.HasNext() {
        t.Fatalf("should not have next")
    }
}

func TestExist(t *testing.T) {
    n := NewRaw(`null`)
    if !n.Exists() {
        t.Fatal()
    }
    nn := n.Get("xx")
    if nn.Exists() {
        t.Fatal() 
    }

    root := NewRaw(`{"a":1, "b":[1,2], "c":{"1":1, "2":2}}`)
    if !root.Exists() {
        t.Fatal()
    }
    exi, err := root.Unset("a")
    if !exi || err != nil {
        t.Fatal(exi, err)
    }

    root.ForEach(func(path Sequence, node *Node) bool {
        if path.Key != nil && *path.Key == "a" {
            t.Fatal()
        }
        if path.Index == 0 {
            if *path.Key != "b" {
                t.Fatal()
            }
            exi, err := node.UnsetByIndex(1)
            if !exi || err != nil {
                t.Fatal(exi, err)
            }
            node.ForEach(func(path Sequence, node *Node) bool {
                if path.Index == 1 {
                    t.Fatal()
                }
                return true
            })
        }

        if path.Index == 1 {
            if *path.Key != "c" {
                t.Fatal()
            }
            exi, err := node.UnsetByIndex(1)
            if !exi || err != nil {
                t.Fatal(exi, err)
            }
            node.ForEach(func(path Sequence, node *Node) bool {
                if path.Index == 1 {
                    t.Fatal()
                }
                return true
            })
        }
        return true
    })

    out, err := root.Raw()
    if err != nil {
        t.Fatal(err)
    }
    require.Equal(t, `{"b":[1],"c":{"1":1}}`, out)

}

func BenchmarkArrays(b *testing.B) {
    for i:=0;i<b.N;i++{
        root,err := NewSearcher(_TwitterJson).GetByPath("statuses",1,"entities","hashtags")
        if err != nil {
            b.Fatal(err)
        }
        a, _ := root.Array()
        for _,v := range a {
            _ = v
        }
    }
}

func BenchmarkListIterator(b *testing.B) {
    for i:=0;i<b.N;i++{
        root,err := NewSearcher(_TwitterJson).GetByPath("statuses",1,"entities","hashtags")
        if err != nil {
            b.Fatal(err)
        }
        it, _ := root.Values()
        for it.HasNext() {
            v := &Node{}
            if !it.Next(v) {
                b.Fatalf("no value")
            }
        }
    }
}

func BenchmarkMap(b *testing.B) {
    for i:=0;i<b.N;i++{
        root,err := NewSearcher(_TwitterJson).GetByPath("statuses",1, "user")
        if err != nil {
            b.Fatal(err)
        }
        m, _ := root.Map()
        for k,v := range m {
            _ = v
            _ = k
        }
    }
}

func BenchmarkObjectIterator(b *testing.B) {
    for i:=0;i<b.N;i++{
        root,err := NewSearcher(_TwitterJson).GetByPath("statuses",1, "user")
        if err != nil {
            b.Fatal(err)
        }
        it, _ := root.Properties()
        for it.HasNext() {
            v := &Pair{}
            if !it.Next(v)  {
                b.Fatalf("no value")
            }
        }
    }
}