/*
 * ECAL
 *
 * Copyright 2020 Matthias Ladkau. All rights reserved.
 *
 * This Source Code Form is subject to the terms of the MIT
 * License, If a copy of the MIT License was not distributed with this
 * file, You can obtain one at https://opensource.org/licenses/MIT.
 */

package interpreter

import (
	"fmt"
	"reflect"
	"testing"

	"devt.de/krotik/ecal/stdlib"
)

func TestStdlib(t *testing.T) {
	stdlib.AddStdlibPkg("fmt", "fmt package")
	stdlib.AddStdlibFunc("fmt", "Sprint",
		stdlib.NewECALFunctionAdapter(reflect.ValueOf(fmt.Sprint), "foo"))

	res, err := UnitTestEvalAndAST(
		`fmt.Sprint([1,2,3])`, nil,
		`
identifier: fmt
  identifier: Sprint
    funccall
      list
        number: 1
        number: 2
        number: 3
`[1:])

	if err != nil || res != "[1 2 3]" {
		t.Error("Unexpected result: ", res, err)
		return
	}

	res, err = UnitTestEvalAndAST(
		`fmt.Sprint(math.Pi)`, nil,
		`
identifier: fmt
  identifier: Sprint
    funccall
      identifier: math
        identifier: Pi
`[1:])

	if err != nil || res != "3.141592653589793" {
		t.Error("Unexpected result: ", res, err)
		return
	}

	// Negative case

	res, err = UnitTestEvalAndAST(
		`a.fmtSprint([1,2,3])`, nil,
		`
identifier: a
  identifier: fmtSprint
    funccall
      list
        number: 1
        number: 2
        number: 3
`[1:])

	if err == nil ||
		err.Error() != "ECAL error in ECALTestRuntime: Unknown construct (Unknown function: fmtSprint) (Line:1 Pos:3)" {
		t.Error("Unexpected result: ", res, err)
		return
	}
}

func TestSimpleFunctions(t *testing.T) {

	res, err := UnitTestEvalAndAST(
		`len([1,2,3])`, nil,
		`
identifier: len
  funccall
    list
      number: 1
      number: 2
      number: 3
`[1:])

	if err != nil || res != 3. {
		t.Error("Unexpected result: ", res, err)
		return
	}

	res, err = UnitTestEvalAndAST(
		`len({"a":1, 2:"b"})`, nil,
		`
identifier: len
  funccall
    map
      kvp
        string: 'a'
        number: 1
      kvp
        number: 2
        string: 'b'
`[1:])

	if err != nil || res != 2. {
		t.Error("Unexpected result: ", res, err)
		return
	}

	res, err = UnitTestEvalAndAST(
		`del([1,2,3], 1)`, nil,
		`
identifier: del
  funccall
    list
      number: 1
      number: 2
      number: 3
    number: 1
`[1:])

	if err != nil || fmt.Sprint(res) != "[1 3]" {
		t.Error("Unexpected result: ", res, err)
		return
	}

	res, err = UnitTestEvalAndAST(
		`del({
  "a" : 1
  "b" : 2
  "c" : 3
}, "b")`, nil,
		`
identifier: del
  funccall
    map
      kvp
        string: 'a'
        number: 1
      kvp
        string: 'b'
        number: 2
      kvp
        string: 'c'
        number: 3
    string: 'b'
`[1:])

	if err != nil || fmt.Sprint(res) != "map[a:1 c:3]" {
		t.Error("Unexpected result: ", res, err)
		return
	}

	res, err = UnitTestEvalAndAST(
		`add([1,2,3], 4)`, nil,
		`
identifier: add
  funccall
    list
      number: 1
      number: 2
      number: 3
    number: 4
`[1:])

	if err != nil || fmt.Sprint(res) != "[1 2 3 4]" {
		t.Error("Unexpected result: ", res, err)
		return
	}

	res, err = UnitTestEvalAndAST(
		`add([1,2,3], 4, 0)`, nil,
		`
identifier: add
  funccall
    list
      number: 1
      number: 2
      number: 3
    number: 4
    number: 0
`[1:])

	if err != nil || fmt.Sprint(res) != "[4 1 2 3]" {
		t.Error("Unexpected result: ", res, err)
		return
	}

	res, err = UnitTestEvalAndAST(
		`add([1,2,3], 4, 1)`, nil,
		`
identifier: add
  funccall
    list
      number: 1
      number: 2
      number: 3
    number: 4
    number: 1
`[1:])

	if err != nil || fmt.Sprint(res) != "[1 4 2 3]" {
		t.Error("Unexpected result: ", res, err)
		return
	}

	res, err = UnitTestEvalAndAST(
		`concat([1,2,3], [4,5,6], [7,8,9])`, nil,
		`
identifier: concat
  funccall
    list
      number: 1
      number: 2
      number: 3
    list
      number: 4
      number: 5
      number: 6
    list
      number: 7
      number: 8
      number: 9
`[1:])

	if err != nil || fmt.Sprint(res) != "[1 2 3 4 5 6 7 8 9]" {
		t.Error("Unexpected result: ", res, err)
		return
	}

	res, err = UnitTestEvalAndAST(
		`dumpenv()`, nil,
		`
identifier: dumpenv
  funccall
`[1:])

	if err != nil || fmt.Sprint(res) != `GlobalScope {
}` {
		t.Error("Unexpected result: ", res, err)
		return
	}

	res, err = UnitTestEval(
		`
func foo() {
	log("hello")
}
doc(foo)`, nil)

	if err != nil || fmt.Sprint(res) != `Declared function: foo (Line 2, Pos 1)` {
		t.Error("Unexpected result: ", res, err)
		return
	}

	res, err = UnitTestEval(
		`doc(len)`, nil)

	if err != nil || fmt.Sprint(res) != `Len returns the size of a list or map.` {
		t.Error("Unexpected result: ", res, err)
		return
	}

	stdlib.AddStdlibPkg("fmt", "fmt package")
	stdlib.AddStdlibFunc("fmt", "Println",
		stdlib.NewECALFunctionAdapter(reflect.ValueOf(fmt.Sprint), "foo"))

	res, err = UnitTestEval(
		`doc(fmt.Println)`, nil)

	if err != nil || res != "foo" {
		t.Error("Unexpected result: ", res, err)
		return
	}

	res, err = UnitTestEval(
		`
/*
Foo is my custom function.
*/
func foo() {
	log("hello")
}
doc(foo)`, nil)

	if err != nil || fmt.Sprint(res) != `Foo is my custom function.` {
		t.Error("Unexpected result: ", res, err)
		return
	}

	// Negative case

	res, err = UnitTestEvalAndAST(
		`a.len([1,2,3])`, nil,
		`
identifier: a
  identifier: len
    funccall
      list
        number: 1
        number: 2
        number: 3
`[1:])

	if err == nil ||
		err.Error() != "ECAL error in ECALTestRuntime: Unknown construct (Unknown function: len) (Line:1 Pos:3)" {
		t.Error("Unexpected result: ", res, err)
		return
	}

}

func TestDocstrings(t *testing.T) {
	for k, v := range InbuildFuncMap {
		if res, _ := v.DocString(); res == "" {
			t.Error("Docstring missing for ", k)
			return
		}
	}
}

func TestErrorConditions(t *testing.T) {

	ib := &inbuildBaseFunc{}

	if _, err := ib.AssertNumParam(1, "bob"); err == nil || err.Error() != "Parameter 1 should be a number" {
		t.Error("Unexpected result:", err)
		return
	}

	if _, err := ib.AssertMapParam(1, "bob"); err == nil || err.Error() != "Parameter 1 should be a map" {
		t.Error("Unexpected result:", err)
		return
	}

	if _, err := ib.AssertListParam(1, "bob"); err == nil || err.Error() != "Parameter 1 should be a list" {
		t.Error("Unexpected result:", err)
		return
	}

	rf := &rangeFunc{&inbuildBaseFunc{}}

	if _, err := rf.Run("", nil, nil, nil); err == nil || err.Error() != "Need at least an end range as first parameter" {
		t.Error("Unexpected result:", err)
		return
	}

	if _, err := rf.Run("", nil, nil, []interface{}{"bob"}); err == nil || err.Error() != "Parameter 1 should be a number" {
		t.Error("Unexpected result:", err)
		return
	}

}
