package compiler

import (
	"git.urbach.dev/cli/q/src/config"
	"git.urbach.dev/cli/q/src/core"
	"git.urbach.dev/cli/q/src/errors"
	"git.urbach.dev/cli/q/src/scanner"
)

// Compile waits for the scan to finish and compiles all functions.
func Compile(build *config.Build) (*core.Environment, error) {
	env, err := scanner.Scan(build)

	if err != nil {
		return nil, err
	}

	// Check for existence of `run.init`
	init := env.Function("run", "init")

	if init == nil {
		return nil, MissingInitFunction
	}

	env.Init = init

	// Check for existence of `main.main`
	main := env.Function("main", "main")

	if main == nil {
		return nil, MissingMainFunction
	}

	env.Main = main

	// Resolve all types so that we have type information ready
	// for all functions before parallel compilation starts.
	err = env.ResolveTypes()

	if err != nil {
		return nil, err
	}

	// Start parallel compilation of all functions.
	// We compile every function for syntax checks even if
	// they are thrown away later during dead code elimination.
	parallel(env.Functions(), func(f *core.Function) {
		f.Compile()
	})

	// Report errors if any occurred
	for f := range env.Functions() {
		if f.Err != nil {
			return nil, f.Err
		}
	}

	// Check for unused imports in all files
	for _, file := range env.Files {
		for _, imp := range file.Imports {
			if !imp.IsUsed() {
				return nil, errors.New(&UnusedImport{Package: imp.Package}, file, imp.Position)
			}
		}
	}

	// Now that we know which functions are alive, start parallel
	// assembly code generation only for the live functions.
	parallel(env.LiveFunctions(), func(f *core.Function) {
		f.Assemble()
	})

	return env, nil
}