// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.

// operator-index.go
package expr

// -------- index term
func newIndexTerm(tk *Token) (inst *term) {
	return &term{
		tk:       *tk,
		children: make([]*term, 0, 2),
		position: posInfix,
		priority: priDot,
		evalFunc: evalIndex,
	}
}

func verifyKey(indexList *ListType) (index any, err error) {
	index = (*indexList)[0]
	return
}

func verifyIndex(indexTerm *term, indexList *ListType, maxValue int) (index int, err error) {
	var v int

	if v, err = ToGoInt((*indexList)[0], "index expression"); err == nil {
		if v < 0 && v >= -maxValue {
			v = maxValue + v
		}
		if v >= 0 && v < maxValue {
			index = v
		} else {
			err = indexTerm.Errorf("index %d out of bounds", v)
		}
	}
	return
}

func verifyRange(indexTerm *term, indexList *ListType, maxValue int) (startIndex, endIndex int, err error) {
	v, _ := ((*indexList)[0]).(*intPair)
	startIndex = v.a
	endIndex = v.b
	if endIndex == ConstLastIndex {
		endIndex = maxValue
	}
	if startIndex < 0 && startIndex >= -maxValue {
		startIndex = maxValue + startIndex
	}
	if endIndex < 0 && endIndex >= -maxValue {
		endIndex = maxValue + endIndex
	}
	if startIndex < 0 || startIndex > maxValue {
		err = indexTerm.Errorf("range start-index %d is out of bounds", startIndex)
	} else if endIndex < 0 || endIndex > maxValue {
		err = indexTerm.Errorf("range end-index %d is out of bounds", endIndex)
	} else if startIndex > endIndex {
		err = indexTerm.Errorf("range start-index %d must not be greater than end-index %d", startIndex, endIndex)
	}
	return
}

func evalIndex(ctx ExprContext, opTerm *term) (v any, err error) {
	var leftValue, rightValue any
	var indexList *ListType
	var ok bool

	if leftValue, rightValue, err = opTerm.evalInfix(ctx); err != nil {
		return
	}

	indexTerm := opTerm.children[1]
	if indexList, ok = rightValue.(*ListType); !ok {
		err = opTerm.Errorf("invalid index expression")
		return
	} else if len(*indexList) != 1 {
		err = indexTerm.Errorf("one index only is allowed")
		return
	}

	if IsInteger((*indexList)[0]) {
		switch unboxedValue := leftValue.(type) {
		case *ListType:
			var index int
			if index, err = verifyIndex(indexTerm, indexList, len(*unboxedValue)); err == nil {
				v = (*unboxedValue)[index]
			}
		case string:
			var index int
			if index, err = verifyIndex(indexTerm, indexList, len(unboxedValue)); err == nil {
				v = string(unboxedValue[index])
			}
		case *DictType:
			v, err = getDictItem(unboxedValue, indexTerm, indexList, rightValue)
		default:
			err = opTerm.errIncompatibleTypes(leftValue, rightValue)
		}
	} else if isIntPair((*indexList)[0]) {
		switch unboxedValue := leftValue.(type) {
		case *ListType:
			var start, end int
			if start, end, err = verifyRange(indexTerm, indexList, len(*unboxedValue)); err == nil {
				sublist := ListType((*unboxedValue)[start:end])
				v = &sublist
			}
		case string:
			var start, end int
			if start, end, err = verifyRange(indexTerm, indexList, len(unboxedValue)); err == nil {
				v = unboxedValue[start:end]
			}
		default:
			err = opTerm.errIncompatibleTypes(leftValue, rightValue)
		}
	} else if IsDict(leftValue) {
		d := leftValue.(*DictType)
		v, err = getDictItem(d, indexTerm, indexList, rightValue)
	} else {
		rightChild := opTerm.children[1]
		err = rightChild.Errorf("invalid index type: %v", (*indexList)[0])
	}
	return
}

func getDictItem(d *DictType, indexTerm *term, indexList *ListType, rightValue any) (v any, err error) {
	var ok bool
	var indexValue any

	if indexValue, err = verifyKey(indexList); err == nil {
		if v, ok = (*d)[indexValue]; !ok {
			err = indexTerm.Errorf("key %v does not belong to the dictionary", rightValue)
		}
	}
	return
}

// init
func init() {
	registerTermConstructor(SymIndex, newIndexTerm)
}
