package parser

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"

	"git.jfdev.de/JonasFranzDEV/hal/hal"
)

func ParseFile(filepath string) (hal.Program, error) {
	if stats, err := os.Stat(filepath); err != nil || stats.IsDir() {
		return nil, err
	}
	file, err := os.Open(filepath)
	if err != nil {
		return nil, err
	}
	scanner := bufio.NewScanner(file)
	scanner.Split(bufio.ScanLines)
	var input []string
	for scanner.Scan() {
		input = append(input, scanner.Text())
	}
	if err := file.Close(); err != nil {
		return nil, err
	}
	return ParseProgram(input)
}

// ParseProgram parses an program by the following specification: LINE_NUMBER INSTRUCTION OPERAND(optional)
func ParseProgram(input []string) (hal.Program, error) {
	program := make(hal.Program)
	for index, line := range input {
		if len(line) == 0 || line[0] == '#' {
			continue
		}
		args := strings.Split(line, " ")
		if len(args) <= 1 {
			continue
		}
		lineNumber, err := strconv.ParseInt(args[0], 10, 64)
		if err != nil {
			return nil, fmt.Errorf("invalid line number at file line %d: %s", index, args[0])
		}
		program[lineNumber], err = parseInstruction(args[1:])
		if err != nil {
			return nil, fmt.Errorf("error while parsing line %d: %v", index+1, err)
		}
	}
	return program, nil
}

func parseInstruction(args []string) (*hal.ProgrammedInstruction, error) {
	if len(args) != 1 && len(args) != 2 {
		return nil, fmt.Errorf("invalid instruction args count (count = %d)", len(args))
	}
	instruction := hal.FindInstructionByName(args[0])
	if instruction == nil {
		return nil, fmt.Errorf("unknown instruction '%s'", args[0])
	}
	programmedInstruction := &hal.ProgrammedInstruction{
		Instruction: instruction,
	}
	if len(args) == 2 {
		operand, err := strconv.ParseFloat(args[1], 64)
		if err != nil {
			return nil, fmt.Errorf("error while parsing operand for instruction '%s': %v", args[0], err)
		}
		if instruction.ExecuteWithOperand == nil {
			return nil, fmt.Errorf("instruction '%s' has no operand, got operand %d", instruction.Name, operand)
		}
		programmedInstruction.Operand = operand
	}
	return programmedInstruction, nil
}