package core

import (
	"fmt"

	"git.akyoto.dev/cli/q/src/errors"
	"git.akyoto.dev/cli/q/src/expression"
	"git.akyoto.dev/cli/q/src/types"
	"git.akyoto.dev/cli/q/src/x64"
)

// 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) (*Function, error) {
	var (
		pkg      = f.Package
		nameNode = root.Children[0]
		fn       *Function
		name     string
		exists   bool
	)

	if nameNode.IsLeaf() {
		name = nameNode.Token.Text(f.File.Bytes)

		switch name {
		case "syscall":
			return nil, f.CompileSyscall(root)
		case "new":
			return &Function{
				Output: []*Output{
					{
						Type: &types.Pointer{
							To: f.Types[root.Children[1].Token.Text(f.File.Bytes)],
						},
					},
				},
			}, f.CompileNew(root)
		case "delete":
			return nil, f.CompileDelete(root)
		case "store":
			return nil, f.CompileMemoryStore(root)
		}
	} else {
		pkg = nameNode.Children[0].Token.Text(f.File.Bytes)
		name = nameNode.Children[1].Token.Text(f.File.Bytes)
	}

	if pkg == "kernel32" || pkg == "user32" || pkg == "gdi32" || pkg == "comctl32" {
		parameters := root.Children[1:]
		registers := x64.WindowsInputRegisters[:len(parameters)]

		for i := len(parameters) - 1; i >= 0; i-- {
			_, err := f.ExpressionToRegister(parameters[i], registers[i])

			if err != nil {
				return nil, err
			}
		}

		f.DLLs = f.DLLs.Append(pkg, name)
		f.DLLCall(fmt.Sprintf("%s.%s", pkg, name))
		return nil, nil
	} else if pkg != f.File.Package {
		if f.File.Imports == nil {
			return nil, errors.New(&errors.UnknownPackage{Name: pkg}, f.File, nameNode.Token.Position)
		}

		imp, exists := f.File.Imports[pkg]

		if !exists {
			return nil, errors.New(&errors.UnknownPackage{Name: pkg}, f.File, nameNode.Token.Position)
		}

		imp.Used = true
	}

	fn, exists = f.Functions[pkg+"."+name]

	if !exists {
		return nil, errors.New(&errors.UnknownFunction{Name: name}, f.File, nameNode.Token.Position)
	}

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

	for i := len(parameters) - 1; i >= 0; i-- {
		typ, err := f.ExpressionToRegister(parameters[i], registers[i])

		if err != nil {
			return nil, err
		}

		if !types.Is(typ, fn.Input[i].Type) {
			return nil, errors.New(&errors.TypeMismatch{
				Encountered:   typ.Name(),
				Expected:      fn.Input[i].Type.Name(),
				ParameterName: fn.Input[i].Name,
			}, f.File, parameters[i].Token.Position)
		}
	}

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