package core

import (
	"git.urbach.dev/cli/q/src/asm"
	"git.urbach.dev/cli/q/src/errors"
	"git.urbach.dev/cli/q/src/eval"
	"git.urbach.dev/cli/q/src/expression"
	"git.urbach.dev/cli/q/src/token"
	"git.urbach.dev/cli/q/src/types"
)

// CompileCall executes a function call.
// All call registers must hold the correct parameter values before the function invocation.
// Registers that are in use must be saved if they are modified by the function.
// After the function call, they must be restored in reverse order.
func (f *Function) CompileCall(root *expression.Expression) ([]types.Type, error) {
	if root.Children[0].Token.Kind == token.Identifier {
		name := root.Children[0].Token.Text(f.File.Bytes)

		switch name {
		case "len":
			return _len.OutputTypes, f.CompileLen(root)
		case "syscall":
			return nil, f.CompileSyscall(root)
		case "new":
			typ, err := f.CompileNew(root)
			return []types.Type{typ}, err
		case "delete":
			return nil, f.CompileDelete(root)
		case "store":
			return nil, f.CompileMemoryStore(root)
		}
	}

	value, err := f.Evaluate(root.Children[0])

	if err != nil {
		return nil, err
	}

	parameters := root.Children[1:]
	registers := f.CPU.Input[:len(parameters)]

	switch value := value.(type) {
	case *eval.Label:
		fn, exists := f.All.Functions[value.Label]

		if !exists {
			if value.Label == "main.main" && f.UniqueName == "core.init" {
				f.Label(asm.CALL, "main.main")
				return nil, nil
			}

			return nil, errors.New(&errors.UnknownIdentifier{Name: value.Label}, f.File, root.Children[0].Token.Position)
		}

		if len(parameters) != len(fn.Input) {
			return nil, errors.New(&errors.ParameterCountMismatch{Function: fn.Name, Count: len(parameters), ExpectedCount: len(fn.Input)}, f.File, root.Children[0].Token.End())
		}

		if fn.IsExtern() {
			return f.CallExtern(fn, parameters)
		}

		err := f.ExpressionsToRegisters(parameters, registers, fn.Input, true)

		if err != nil {
			return nil, err
		}

		f.CallSafe(fn, registers)
		return fn.OutputTypes, nil

	case *eval.Register:
		f.Register(asm.CALL, value.Register)

	case *eval.Memory:
		f.Memory(asm.CALL, value.Memory)
	}

	return nil, nil
}