package goja

import (
	"testing"
)

func TestSparseArraySetLengthWithPropItems(t *testing.T) {
	const SCRIPT = `
	var a = [1,2,3,4];
	a[100000] = 5;
	var thrown = false;

	Object.defineProperty(a, "2", {value: 42, configurable: false, writable: false});
	try {
		Object.defineProperty(a, "length", {value: 0, writable: false});
	} catch (e) {
		thrown = e instanceof TypeError;
	}
	thrown && a.length === 3;
	`

	testScript(SCRIPT, valueTrue, t)
}

func TestSparseArraySwitch(t *testing.T) {
	vm := New()
	_, err := vm.RunString(`
	var a = [];
	a[20470] = 5; // switch to sparse`)
	if err != nil {
		t.Fatal(err)
	}
	a := vm.Get("a").(*Object)
	if _, ok := a.self.(*sparseArrayObject); !ok {
		t.Fatal("1: array is not sparse")
	}
	_, err = vm.RunString(`
	var cutoffIdx = Math.round(20470 - 20470/8);
	for (var i = a.length - 1; i >= cutoffIdx; i--) {
		a[i] = i;
	}

	// At this point it will have switched to a normal array
	if (a.length != 20471) {
		throw new Error("Invalid length: " + a.length);
	}

	for (var i = 0; i < cutoffIdx; i++) {
		if (a[i] !== undefined) {
			throw new Error("Invalid value at " + i + ": " + a[i]);
		}
	}

	for (var i = cutoffIdx; i < a.length; i++) {
		if (a[i] !== i) {
			throw new Error("Invalid value at " + i + ": " + a[i]);
		}
	}`)
	if err != nil {
		t.Fatal(err)
	}
	if _, ok := a.self.(*arrayObject); !ok {
		t.Fatal("2: array is not normal")
	}
	_, err = vm.RunString(`
	// Now try to expand. Should stay a normal array
	a[20471] = 20471;
	if (a.length != 20472) {
		throw new Error("Invalid length: " + a.length);
	}

	for (var i = 0; i < cutoffIdx; i++) {
		if (a[i] !== undefined) {
			throw new Error("Invalid value at " + i + ": " + a[i]);
		}
	}

	for (var i = cutoffIdx; i < a.length; i++) {
		if (a[i] !== i) {
			throw new Error("Invalid value at " + i + ": " + a[i]);
		}
	}`)
	if err != nil {
		t.Fatal(err)
	}
	if _, ok := a.self.(*arrayObject); !ok {
		t.Fatal("3: array is not normal")
	}
	_, err = vm.RunString(`
	// Delete enough elements for it to become sparse again.
	var cutoffIdx1 = Math.round(20472 - 20472/10);
	for (var i = cutoffIdx; i < cutoffIdx1; i++) {
		delete a[i];
	}

	// This should switch it back to sparse.
	a[25590] = 25590;
	if (a.length != 25591) {
		throw new Error("Invalid length: " + a.length);
	}

	for (var i = 0; i < cutoffIdx1; i++) {
		if (a[i] !== undefined) {
			throw new Error("Invalid value at " + i + ": " + a[i]);
		}
	}

	for (var i = cutoffIdx1; i < 20472; i++) {
		if (a[i] !== i) {
			throw new Error("Invalid value at " + i + ": " + a[i]);
		}
	}

	for (var i = 20472; i < 25590; i++) {
		if (a[i] !== undefined) {
			throw new Error("Invalid value at " + i + ": " + a[i]);
		}
	}

	if (a[25590] !== 25590) {
		throw new Error("Invalid value at 25590: " + a[25590]);
	}
	`)
	if err != nil {
		t.Fatal(err)
	}
	if _, ok := a.self.(*sparseArrayObject); !ok {
		t.Fatal("4: array is not sparse")
	}
}

func TestSparseArrayOwnKeys(t *testing.T) {
	const SCRIPT = `
	var a1 = [];
	a1[500000] = 1;
	var seen = false;
	var count = 0;
	var keys = Object.keys(a1);
	keys.length === 1 && keys[0] === "500000"; 
	`

	testScript(SCRIPT, valueTrue, t)
}

func TestSparseArrayEnumerate(t *testing.T) {
	const SCRIPT = `
	var a1 = [];
	a1[500000] = 1;
	var seen = false;
	var count = 0;
	for (var i in a1) {
		if (i === "500000") {
			if (seen) {
				throw new Error("seen twice");
			}
			seen = true;
		}
		count++;
	}
	seen && count === 1;
	`

	testScript(SCRIPT, valueTrue, t)
}

func TestArraySparseMaxLength(t *testing.T) {
	const SCRIPT = `
	var a = [];
	a[4294967294]=1;
	a.length === 4294967295 && a[4294967294] === 1;
	`

	testScript(SCRIPT, valueTrue, t)
}

func TestArraySparseExportProps(t *testing.T) {
	vm := New()
	proto := vm.NewArray()
	for _, idx := range []string{"0", "500", "9999", "10001", "20471"} {
		err := proto.Set(idx, true)
		if err != nil {
			t.Fatal(err)
		}
	}

	arr := vm.NewArray()
	err := arr.SetPrototype(proto)
	if err != nil {
		t.Fatal(err)
	}
	err = arr.DefineDataProperty("20470", vm.ToValue(true), FLAG_TRUE, FLAG_FALSE, FLAG_TRUE)
	if err != nil {
		t.Fatal(err)
	}
	err = arr.DefineDataProperty("10000", vm.ToValue(true), FLAG_TRUE, FLAG_FALSE, FLAG_TRUE)
	if err != nil {
		t.Fatal(err)
	}
	err = arr.Set("length", 20472)
	if err != nil {
		t.Fatal(err)
	}
	actual := arr.Export()
	if actualArr, ok := actual.([]interface{}); ok {
		if len(actualArr) == 20472 {
			expectedIdx := map[int]struct{}{
				0:     {},
				500:   {},
				9999:  {},
				10000: {},
				10001: {},
				20470: {},
				20471: {},
			}
			for i, v := range actualArr {
				if _, exists := expectedIdx[i]; exists {
					if v != true {
						t.Fatalf("Expected true at %d, got %v", i, v)
					}
				} else {
					if v != nil {
						t.Fatalf("Expected nil at %d, got %v", i, v)
					}
				}
			}
		} else {
			t.Fatalf("Expected len 20471, actual: %d", len(actualArr))
		}
	} else {
		t.Fatalf("Invalid export type: %T", actual)
	}
}

func TestSparseArrayExportToSlice(t *testing.T) {
	vm := New()
	arr := vm.NewArray()
	err := arr.Set("20470", 120470)
	if err != nil {
		t.Fatal(err)
	}
	err = arr.DefineDataProperty("20471", vm.ToValue(220471), FLAG_TRUE, FLAG_FALSE, FLAG_TRUE)
	if err != nil {
		t.Fatal(err)
	}
	var exp []int
	err = vm.ExportTo(arr, &exp)
	if err != nil {
		t.Fatal(err)
	}
	if len(exp) != 20472 {
		t.Fatalf("len: %d", len(exp))
	}
	if e := exp[20470]; e != 120470 {
		t.Fatalf("20470: %d", e)
	}
	if e := exp[20471]; e != 220471 {
		t.Fatalf("20471: %d", e)
	}
	for i := 0; i < 20470; i++ {
		if exp[i] != 0 {
			t.Fatalf("at %d: %d", i, exp[i])
		}
	}
}
