/*
 * 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 (
	"testing"

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

func TestSimpleAssignments(t *testing.T) {

	vs := scope.NewScope(scope.GlobalScope)

	res, err := UnitTestEvalAndAST(
		`let a := 42`, vs,
		`
:=
  let
    identifier: a
  number: 42
`[1:])

	if vsRes := vs.String(); vsRes != `GlobalScope {
    a (float64) : 42
}` {
		t.Error("Unexpected result: ", vsRes, res, err)
		return
	}

	res, err = UnitTestEvalAndAST(
		`a := "test"`, vs,
		`
:=
  identifier: a
  string: 'test'
`[1:])

	if vsRes := vs.String(); vsRes != `GlobalScope {
    a (string) : test
}` {
		t.Error("Unexpected result: ", vsRes, res, err)
		return
	}

	res, err = UnitTestEvalAndAST(
		`a := [1,2,3]`, vs,
		`
:=
  identifier: a
  list
    number: 1
    number: 2
    number: 3
`[1:])

	if vsRes := vs.String(); vsRes != `GlobalScope {
    a ([]interface {}) : [1,2,3]
}` {
		t.Error("Unexpected result: ", vsRes, res, err)
		return
	}

	res, err = UnitTestEvalAndAST(
		`a := {
			1 : "foo",
			"2" : "bar",
			"foobar" : 3,
			x : 4,
		}`, vs,
		`
:=
  identifier: a
  map
    kvp
      number: 1
      string: 'foo'
    kvp
      string: '2'
      string: 'bar'
    kvp
      string: 'foobar'
      number: 3
    kvp
      identifier: x
      number: 4
`[1:])

	if vsRes := vs.String(); vsRes != `GlobalScope {
    a (map[interface {}]interface {}) : {"1":"foo","2":"bar","foobar":3,"null":4}
}` {
		t.Error("Unexpected result: ", vsRes, res, err)
		return
	}

	_, err = UnitTestEval(
		`1 := [1, 2]`, vs)

	if err.Error() != "ECAL error in ECALTestRuntime: Cannot access variable (Must have a variable or list of variables on the left side of the assignment) (Line:1 Pos:3)" {
		t.Error("Unexpected result:", err)
		return
	}

	_, err = UnitTestEval(
		`[1] := [1, 2]`, vs)

	if err.Error() != "ECAL error in ECALTestRuntime: Cannot access variable (Must have a list of variables on the left side of the assignment) (Line:1 Pos:5)" {
		t.Error("Unexpected result:", err)
		return
	}

	_, err = UnitTestEval(
		`[a, b] := [1, 2, 3]`, vs)

	if err.Error() != "ECAL error in ECALTestRuntime: Invalid state (Assigned number of variables is different to number of values (2 variables vs 3 values)) (Line:1 Pos:8)" {
		t.Error("Unexpected result:", err)
		return
	}

	_, err = UnitTestEval(
		`[a, b] := 1`, vs)

	if err.Error() != "ECAL error in ECALTestRuntime: Invalid state (Result is not a list (value is 1)) (Line:1 Pos:8)" {
		t.Error("Unexpected result:", err)
		return
	}

	_, err = UnitTestEval(
		`[a, b.c, c] := [1, 2, 3]`, vs)

	if err.Error() != "ECAL error in ECALTestRuntime: Cannot access variable (Variable b is not a container) (Line:1 Pos:13)" {
		t.Error("Unexpected result:", err)
		return
	}
}

func TestComplexAssignments(t *testing.T) {

	vs := scope.NewScope(scope.GlobalScope)

	res, err := UnitTestEvalAndAST(
		`let [a, b] := ["test", [1,2,3]]`, vs,
		`
:=
  let
    list
      identifier: a
      identifier: b
  list
    string: 'test'
    list
      number: 1
      number: 2
      number: 3
`[1:])

	if vsRes := vs.String(); err != nil || res != nil || vsRes != `GlobalScope {
    a (string) : test
    b ([]interface {}) : [1,2,3]
}` {
		t.Error("Unexpected result: ", vsRes, res, err)
		return
	}

	res, err = UnitTestEvalAndAST(`
a := {
	"b" : [ 0, {
		"c" : [ 0, 1, 2, {
			"test" : 1
		}]
	}]
}
a.b[1].c["3"]["test"] := 3
`, vs,
		`
statements
  :=
    identifier: a
    map
      kvp
        string: 'b'
        list
          number: 0
          map
            kvp
              string: 'c'
              list
                number: 0
                number: 1
                number: 2
                map
                  kvp
                    string: 'test'
                    number: 1
  :=
    identifier: a
      identifier: b
        compaccess
          number: 1
        identifier: c
          compaccess
            string: '3'
          compaccess
            string: 'test'
    number: 3
`[1:])

	if vsRes := vs.String(); err != nil || res != nil || vsRes != `GlobalScope {
    a (map[interface {}]interface {}) : {"b":[0,{"c":[0,1,2,{"test":3}]}]}
    b ([]interface {}) : [1,2,3]
}` {
		t.Error("Unexpected result: ", vsRes, res, err)
		return
	}

	res, err = UnitTestEvalAndAST(`
z := a.b[1].c["3"]["test"]
[x, y] := a.b
`, vs,
		`
statements
  :=
    identifier: z
    identifier: a
      identifier: b
        compaccess
          number: 1
        identifier: c
          compaccess
            string: '3'
          compaccess
            string: 'test'
  :=
    list
      identifier: x
      identifier: y
    identifier: a
      identifier: b
`[1:])

	if vsRes := vs.String(); err != nil || res != nil || vsRes != `GlobalScope {
    a (map[interface {}]interface {}) : {"b":[0,{"c":[0,1,2,{"test":3}]}]}
    b ([]interface {}) : [1,2,3]
    x (float64) : 0
    y (map[interface {}]interface {}) : {"c":[0,1,2,{"test":3}]}
    z (float64) : 3
}` {
		t.Error("Unexpected result: ", vsRes, res, err)
		return
	}

}

func TestScopedDeclaration(t *testing.T) {

	vs := scope.NewScope(scope.GlobalScope)

	res, err := UnitTestEval(`
a := 5
func foo() {
	let a := 2
}
foo()`, vs)

	if vsRes := vs.String(); err != nil || res != nil || vsRes != `GlobalScope {
    a (float64) : 5
    foo (*interpreter.function) : ecal.function: foo (Line 3, Pos 1)
}` {
		t.Error("Unexpected result: ", vsRes, res, err)
		return
	}

	res, err = UnitTestEval(`
a := 5
func foo() {
	let [a, b, c]
	a := 2
}
foo()`, vs)

	if vsRes := vs.String(); err != nil || res != nil || vsRes != `GlobalScope {
    a (float64) : 5
    foo (*interpreter.function) : ecal.function: foo (Line 3, Pos 1)
}` {
		t.Error("Unexpected result: ", vsRes, res, err)
		return
	}

	res, err = UnitTestEval(`let [1]`, vs)

	if err == nil || err.Error() != "ECAL error in ECALTestRuntime: Invalid construct (Let can only declare variables within a list) (Line:1 Pos:1)" {
		t.Error("Unexpected result: ", res, err)
		return
	}

	res, err = UnitTestEval(`let 1`, vs)

	if err == nil || err.Error() != "ECAL error in ECALTestRuntime: Invalid construct (Let must declare a variable or list of variables) (Line:1 Pos:1)" {
		t.Error("Unexpected result: ", res, err)
		return
	}

	res, err = UnitTestEval(`let a.b`, vs)

	if err == nil || err.Error() != "ECAL error in ECALTestRuntime: Invalid construct (Let can only declare simple variables) (Line:1 Pos:1)" {
		t.Error("Unexpected result: ", res, err)
		return
	}
}