package compiler

import (
	"errors"
	"fmt"

	"github.com/d5/tengo/compiler/ast"
	"github.com/d5/tengo/compiler/token"
)

func (c *Compiler) compileAssign(lhs, rhs []ast.Expr, op token.Token) error {
	numLHS, numRHS := len(lhs), len(rhs)
	if numLHS < numRHS {
		// # of LHS must be >= # of RHS
		return fmt.Errorf("assigntment count error: %d < %d", numLHS, numRHS)
	}
	if numLHS > 1 {
		// TODO: until we fully implement the tuple assignment
		return fmt.Errorf("tuple assignment not implemented")
	}
	//if numLHS > 1 && op != token.Assign && op != token.Define {
	//	return fmt.Errorf("invalid operator for tuple assignment: %s", op.String())
	//}

	// resolve and compile left-hand side
	ident, selectors, err := resolveAssignLHS(lhs[0])
	if err != nil {
		return err
	}

	numSel := len(selectors)

	if op == token.Define && numSel > 0 {
		// using selector on new variable does not make sense
		return errors.New("cannot use selector with ':='")
	}

	symbol, depth, exists := c.symbolTable.Resolve(ident)
	if op == token.Define {
		if depth == 0 && exists {
			return fmt.Errorf("'%s' redeclared in this block", ident)
		}

		symbol = c.symbolTable.Define(ident)
	} else {
		if !exists {
			return fmt.Errorf("unresolved reference '%s'", ident)
		}
	}

	// +=, -=, *=, /=
	if op != token.Assign && op != token.Define {
		if err := c.Compile(lhs[0]); err != nil {
			return err
		}
	}

	// compile RHSs
	for _, expr := range rhs {
		if err := c.Compile(expr); err != nil {
			return err
		}
	}

	switch op {
	case token.AddAssign:
		c.emit(OpAdd)
	case token.SubAssign:
		c.emit(OpSub)
	case token.MulAssign:
		c.emit(OpMul)
	case token.QuoAssign:
		c.emit(OpDiv)
	case token.RemAssign:
		c.emit(OpRem)
	case token.AndAssign:
		c.emit(OpBAnd)
	case token.OrAssign:
		c.emit(OpBOr)
	case token.AndNotAssign:
		c.emit(OpBAndNot)
	case token.XorAssign:
		c.emit(OpBXor)
	case token.ShlAssign:
		c.emit(OpBShiftLeft)
	case token.ShrAssign:
		c.emit(OpBShiftRight)
	}

	// compile selector expressions (right to left)
	for i := numSel - 1; i >= 0; i-- {
		if err := c.Compile(selectors[i]); err != nil {
			return err
		}
	}

	switch symbol.Scope {
	case ScopeGlobal:
		if numSel > 0 {
			c.emit(OpSetSelGlobal, symbol.Index, numSel)
		} else {
			c.emit(OpSetGlobal, symbol.Index)
		}
	case ScopeLocal:
		if numSel > 0 {
			c.emit(OpSetSelLocal, symbol.Index, numSel)
		} else {
			if op == token.Define {
				c.emit(OpDefineLocal, symbol.Index)
			} else {
				c.emit(OpSetLocal, symbol.Index)
			}
		}
	case ScopeFree:
		if numSel > 0 {
			c.emit(OpSetSelFree, symbol.Index, numSel)
		} else {
			c.emit(OpSetFree, symbol.Index)
		}
	default:
		return fmt.Errorf("invalid assignment variable scope: %s", symbol.Scope)
	}

	return nil
}

func resolveAssignLHS(expr ast.Expr) (name string, selectors []ast.Expr, err error) {
	switch term := expr.(type) {
	case *ast.SelectorExpr:
		name, selectors, err = resolveAssignLHS(term.Expr)
		if err != nil {
			return
		}

		selectors = append(selectors, term.Sel)

		return
	case *ast.IndexExpr:
		name, selectors, err = resolveAssignLHS(term.Expr)
		if err != nil {
			return
		}

		selectors = append(selectors, term.Index)

	case *ast.Ident:
		return term.Name, nil, nil
	}

	return
}