package compiler

type varScope struct {
	localsCnt int
	argCnt    int
	arguments map[string]int
	locals    []map[string]int
}

func newVarScope() varScope {
	return varScope{
		arguments: make(map[string]int),
	}
}

func (c *varScope) newScope() {
	c.locals = append(c.locals, map[string]int{})
}

func (c *varScope) dropScope() {
	c.locals = c.locals[:len(c.locals)-1]
}

func (c *varScope) getVarIndex(name string) (varType, int) {
	for i := len(c.locals) - 1; i >= 0; i-- {
		if i, ok := c.locals[i][name]; ok {
			return varLocal, i
		}
	}
	if i, ok := c.arguments[name]; ok {
		return varArgument, i
	}
	return 0, -1
}

// newVariable creates a new local variable or argument in the scope of the function.
func (c *varScope) newVariable(t varType, name string) int {
	var n int
	switch t {
	case varLocal:
		return c.newLocal(name)
	case varArgument:
		_, ok := c.arguments[name]
		if ok {
			panic("argument is already allocated")
		}
		n = len(c.arguments)
		c.arguments[name] = n
	default:
		panic("invalid type")
	}
	return n
}

// newLocal creates a new local variable in the current scope.
func (c *varScope) newLocal(name string) int {
	idx := len(c.locals) - 1
	m := c.locals[idx]
	m[name] = c.localsCnt
	c.localsCnt++
	c.locals[idx] = m
	return c.localsCnt - 1
}