package goja

import (
	"hash/maphash"
	"math"
	"math/big"
	"strconv"
	"testing"
)

func testMapHashVal(v1, v2 Value, expected bool, t *testing.T) {
	var h maphash.Hash
	actual := v1.hash(&h) == v2.hash(&h)
	if actual != expected {
		t.Fatalf("testMapHashVal failed for %v, %v", v1, v2)
	}
}

func TestMapHash(t *testing.T) {
	testMapHashVal(_NaN, _NaN, true, t)
	testMapHashVal(valueTrue, valueFalse, false, t)
	testMapHashVal(valueTrue, valueTrue, true, t)
	testMapHashVal(intToValue(0), _negativeZero, true, t)
	testMapHashVal(asciiString("Test"), asciiString("Test"), true, t)
	testMapHashVal(newStringValue("Тест"), newStringValue("Тест"), true, t)
	testMapHashVal(floatToValue(1.2345), floatToValue(1.2345), true, t)
	testMapHashVal(SymIterator, SymToStringTag, false, t)
	testMapHashVal(SymIterator, SymIterator, true, t)
	testMapHashVal((*valueBigInt)(big.NewInt(1)), (*valueBigInt)(big.NewInt(-1)), false, t)
	testMapHashVal((*valueBigInt)(big.NewInt(1)), (*valueBigInt)(big.NewInt(1)), true, t)

	// The following tests introduce indeterministic behaviour
	//testMapHashVal(asciiString("Test"), asciiString("Test1"), false, t)
	//testMapHashVal(newStringValue("Тест"), asciiString("Test"), false, t)
	//testMapHashVal(newStringValue("Тест"), newStringValue("Тест1"), false, t)
}

func TestOrderedMap(t *testing.T) {
	m := newOrderedMap(&maphash.Hash{})
	for i := int64(0); i < 50; i++ {
		m.set(intToValue(i), asciiString(strconv.FormatInt(i, 10)))
	}
	if m.size != 50 {
		t.Fatalf("Unexpected size: %d", m.size)
	}

	for i := int64(0); i < 50; i++ {
		expected := asciiString(strconv.FormatInt(i, 10))
		actual := m.get(intToValue(i))
		if !expected.SameAs(actual) {
			t.Fatalf("Wrong value for %d", i)
		}
	}

	for i := int64(0); i < 50; i += 2 {
		if !m.remove(intToValue(i)) {
			t.Fatalf("remove(%d) return false", i)
		}
	}
	if m.size != 25 {
		t.Fatalf("Unexpected size: %d", m.size)
	}

	iter := m.newIter()
	count := 0
	for {
		entry := iter.next()
		if entry == nil {
			break
		}
		m.remove(entry.key)
		count++
	}

	if count != 25 {
		t.Fatalf("Unexpected iter count: %d", count)
	}

	if m.size != 0 {
		t.Fatalf("Unexpected size: %d", m.size)
	}
}

func TestOrderedMapCollision(t *testing.T) {
	m := newOrderedMap(&maphash.Hash{})
	n1 := uint64(123456789)
	n2 := math.Float64frombits(n1)
	n1Key := intToValue(int64(n1))
	n2Key := floatToValue(n2)
	m.set(n1Key, asciiString("n1"))
	m.set(n2Key, asciiString("n2"))
	if m.size == len(m.hashTable) {
		t.Fatal("Expected a collision but there wasn't one")
	}
	if n2Val := m.get(n2Key); !asciiString("n2").SameAs(n2Val) {
		t.Fatalf("unexpected n2Val: %v", n2Val)
	}
	if n1Val := m.get(n1Key); !asciiString("n1").SameAs(n1Val) {
		t.Fatalf("unexpected nVal: %v", n1Val)
	}

	if !m.remove(n1Key) {
		t.Fatal("removing n1Key returned false")
	}
	if n2Val := m.get(n2Key); !asciiString("n2").SameAs(n2Val) {
		t.Fatalf("2: unexpected n2Val: %v", n2Val)
	}
}

func TestOrderedMapIter(t *testing.T) {
	m := newOrderedMap(&maphash.Hash{})
	iter := m.newIter()
	ent := iter.next()
	if ent != nil {
		t.Fatal("entry should be nil")
	}
	iter1 := m.newIter()
	m.set(intToValue(1), valueTrue)
	ent = iter.next()
	if ent != nil {
		t.Fatal("2: entry should be nil")
	}
	ent = iter1.next()
	if ent == nil {
		t.Fatal("entry is nil")
	}
	if !intToValue(1).SameAs(ent.key) {
		t.Fatal("unexpected key")
	}
	if !valueTrue.SameAs(ent.value) {
		t.Fatal("unexpected value")
	}
}

func TestOrderedMapIterVisitAfterReAdd(t *testing.T) {
	m := newOrderedMap(&maphash.Hash{})
	one := intToValue(1)
	two := intToValue(2)

	m.set(one, valueTrue)
	m.set(two, valueTrue)
	iter := m.newIter()
	entry := iter.next()
	if !one.SameAs(entry.key) {
		t.Fatalf("1: unexpected key: %v", entry.key)
	}
	if !m.remove(one) {
		t.Fatal("remove returned false")
	}
	entry = iter.next()
	if !two.SameAs(entry.key) {
		t.Fatalf("2: unexpected key: %v", entry.key)
	}
	m.set(one, valueTrue)
	entry = iter.next()
	if entry == nil {
		t.Fatal("entry is nil")
	}
	if !one.SameAs(entry.key) {
		t.Fatalf("3: unexpected key: %v", entry.key)
	}
}

func TestOrderedMapIterAddAfterClear(t *testing.T) {
	m := newOrderedMap(&maphash.Hash{})
	one := intToValue(1)
	m.set(one, valueTrue)
	iter := m.newIter()
	iter.next()
	m.clear()
	m.set(one, valueTrue)
	entry := iter.next()
	if entry == nil {
		t.Fatal("entry is nil")
	}
	if entry.key != one {
		t.Fatalf("unexpected key: %v", entry.key)
	}
	entry = iter.next()
	if entry != nil {
		t.Fatalf("entry is not nil: %v", entry)
	}
}

func TestOrderedMapIterDeleteCurrent(t *testing.T) {
	m := newOrderedMap(&maphash.Hash{})
	one := intToValue(1)
	two := intToValue(2)
	iter := m.newIter()
	m.set(one, valueTrue)
	m.set(two, valueTrue)
	entry := iter.next()
	if entry.key != one {
		t.Fatalf("unexpected key: %v", entry.key)
	}
	m.remove(one)
	entry = iter.next()
	if entry.key != two {
		t.Fatalf("2: unexpected key: %v", entry.key)
	}
}
