package ion

import (
	"testing"

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

func TestLocalSymbolTableAppend(t *testing.T) {
	text := `$ion_symbol_table::
			{
			  symbols:[ "s1", "s2" ]
			}
			$ion_symbol_table::
			{
			  imports: $ion_symbol_table,
			  symbols:[ "s3", "s4", "s5" ]
			}
			null`

	r := NewReaderString(text)
	assert.True(t, r.Next())
	st := r.SymbolTable()

	imports := st.Imports()
	systemTable := imports[0]
	systemMaxID := systemTable.MaxID()

	checkSymbol(t, "s1", systemMaxID+1, st)
	checkSymbol(t, "s2", systemMaxID+2, st)
	checkSymbol(t, "s3", systemMaxID+3, st)
	checkSymbol(t, "s4", systemMaxID+4, st)
	checkSymbol(t, "s5", systemMaxID+5, st)
	checkUnknownSymbolText(t, "unknown", st)
	checkUnknownSymbolID(t, 33, st)
}

func TestLocalSymbolTableMultiAppend(t *testing.T) {
	text := `$ion_symbol_table::
			{
			  symbols:[ "s1", "s2" ]
			}
			$ion_symbol_table::
			{
			  imports: $ion_symbol_table,
			  symbols:[ "s3" ]
			}
			$ion_symbol_table::
			{
			  imports: $ion_symbol_table,
			  symbols:[ "s4", "s5" ]
			}
			$ion_symbol_table::
			{
			  imports: $ion_symbol_table,
			  symbols:[ "s6" ]
			}
			null`

	r := NewReaderString(text)
	assert.True(t, r.Next())
	st := r.SymbolTable()

	imports := st.Imports()
	systemTable := imports[0]
	systemMaxID := systemTable.MaxID()

	checkSymbol(t, "s1", systemMaxID+1, st)
	checkSymbol(t, "s2", systemMaxID+2, st)
	checkSymbol(t, "s3", systemMaxID+3, st)
	checkSymbol(t, "s4", systemMaxID+4, st)
	checkSymbol(t, "s5", systemMaxID+5, st)
	checkSymbol(t, "s6", systemMaxID+6, st)

	checkUnknownSymbolText(t, "unknown", st)
	checkUnknownSymbolID(t, 33, st)
}

func TestLocalSymbolTableAppendEmptyList(t *testing.T) {
	original := `$ion_symbol_table::
				{
				  symbols:[ "s1" ]
				}`

	appended := original +
		`$ion_symbol_table::
		{
		  imports: $ion_symbol_table,
		  symbols:[]
		}
		null`

	r := NewReaderString(original + "null")
	assert.True(t, r.Next())
	ost := r.SymbolTable()

	originalSymbol := ost.Find("s1")

	r = NewReaderString(appended)
	assert.True(t, r.Next())
	ast := r.SymbolTable()
	appendedSymbol := ast.Find("s1")

	assert.Equal(t, originalSymbol.LocalSID, appendedSymbol.LocalSID,
		"Original symbol SID: %v,did not match Appended symbol SID: %v", originalSymbol.LocalSID, appendedSymbol.LocalSID)
}

func TestLocalSymbolTableAppendNonUnique(t *testing.T) {
	text := `$ion_symbol_table::
			{ 
			symbols:[ "foo" ]
			}
			$10
			$ion_symbol_table::
			{
			  imports: $ion_symbol_table,
			  symbols:[ "foo", "bar" ]
			}
			$11
			$12`

	r := NewReaderString(text)
	assert.True(t, r.Next())
	assert.True(t, r.Next())
	st := r.SymbolTable()
	systemMaxID := getSystemMaxID(st)

	checkSymbol(t, "foo", systemMaxID+1, st)
	checkSymbol(t, "foo", systemMaxID+2, st)
	checkSymbol(t, "bar", systemMaxID+3, st)
}

func TestLocalSymbolTableAppendOutOfBounds(t *testing.T) {
	text := `$ion_symbol_table::
			{
			  symbols:[ "foo" ]
			}
			$10
			$ion_symbol_table::
			{
			  imports: $ion_symbol_table,
			  symbols:[ "foo" ]
			}
			$11
			$12`

	r := NewReaderString(text)
	assert.True(t, r.Next())
	assert.True(t, r.Next())
	st := r.SymbolTable()
	systemMaxID := getSystemMaxID(st)

	checkSymbol(t, "foo", systemMaxID+1, st)
	checkSymbol(t, "foo", systemMaxID+2, st)
	checkUnknownSymbolID(t, systemMaxID+3, st)
}

func getSystemMaxID(st SymbolTable) uint64 {
	imports := st.Imports()
	systemTable := imports[0]
	return systemTable.MaxID()
}

func checkSymbol(t *testing.T, eval string, SID uint64, st SymbolTable) {
	val, ok := st.FindByID(SID)
	assert.True(t, ok, "Failed on checking symbol. Symbol table could not find symbol given the ID: %v", SID)
	assert.Equal(t, eval, val, "Failed on checking symbol. Symbol table returned the symbol name: %v, did not match the expected name: %v", val, eval)
}

func checkUnknownSymbolText(t *testing.T, val string, st SymbolTable) {
	symbolToken := st.Find(val)
	assert.Nil(t, symbolToken, "Failed on checking unknown symbol. Symbol table found symbol given the name: %v", val)
	_, ok := st.FindByName(val)
	assert.False(t, ok, "Failed on checking unknown symbol. Symbol table found symbol given the name: %v", val)
}

func checkUnknownSymbolID(t *testing.T, val uint64, st SymbolTable) {
	_, ok := st.FindByID(val)
	assert.False(t, ok, "Failed on checking unknown symbol. Symbol table found symbol given the SID: %v", val)
}
