use std::fmt::Display;

use crate::{
    operation::{Operation, RegisterOperation},
    Error,
};

pub(crate) struct RegisterDefinition {
    pub name: char,
    pub contents: RegisterContents,
}

pub(crate) enum RegisterContents {
    Size(u16),
    Values(Vec<u16>),
}

#[derive(Debug, Copy, Clone)]
pub(crate) struct Line {
    pub label: Option<u32>,
    pub instruction: Instruction,
}

impl Display for Line {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let (start, op, end) = match self.instruction {
            Instruction::Register(name, op, val) => (
                Some(name.to_string()),
                Operation::Register(op),
                Some(format!("{val}")),
            ),
            Instruction::Result(val) => match val {
                ResultValue::All => (None, Operation::Result, None),
                ResultValue::Register(name) => (Some(name.to_string()), Operation::Result, None),
                ResultValue::Value(val) => (None, Operation::Result, Some(format!("{val}"))),
            },
            Instruction::Jump(reg, label) => (
                reg.map(|c| c.to_string()),
                Operation::Jump,
                Some(format!("{label}")),
            ),
            Instruction::Take(to, from) => (
                Some(from.to_string()),
                Operation::Take,
                Some(to.to_string()),
            ),
        };
        write!(
            f,
            "{}{}{}{}",
            self.label.map(|s| s.to_string()).unwrap_or_default(),
            start.unwrap_or_default(),
            op.to_char(),
            end.unwrap_or_default(),
        )
    }
}

pub(crate) type RegisterName = char;

#[derive(Debug, Copy, Clone)]
pub(crate) enum Instruction {
    Register(RegisterName, RegisterOperation, Value),
    Result(ResultValue),
    Jump(Option<RegisterName>, u32),
    Take(RegisterName, RegisterName),
}

#[derive(Debug, Copy, Clone)]
pub(crate) enum ResultValue {
    All,
    Register(RegisterName),
    Value(Value),
}

#[derive(Debug, Clone, Copy)]
pub(crate) enum Value {
    Register(RegisterName),
    Number(u16),
}

impl Display for Value {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Value::Register(name) => write!(f, "{name}"),
            Value::Number(val) => write!(f, "{val}"),
        }
    }
}

fn strip_comments(line: &str) -> &str {
    &line[0..line.find(';').unwrap_or(line.len())]
}

/// Parse an instruction and add it to the `bytecode` list.
fn parse_instruction(line: &str) -> Result<Line, String> {
    let mut chars = line.chars().peekable();
    let mut numerics = Vec::new();
    while let Some(number) = &chars.peek() {
        if !number.is_numeric() {
            break;
        }
        numerics.push(**number);
        chars.next();
    }
    let label = numerics.into_iter().collect::<String>().parse().ok();
    let ins_or_reg = chars.next().unwrap();
    let (left, op) = match Operation::try_from_char(ins_or_reg) {
        // Begins with operation.
        Some(code) => (None, code),
        // Begins with register.
        None => {
            let op = chars
                .next()
                .ok_or_else(|| "Expected operation".to_string())?;
            (
                Some(ins_or_reg),
                Operation::try_from_char(op).ok_or_else(|| format!("Invalid operation: {op}"))?,
            )
        }
    };
    // Parse the end: could be a number or a register.
    let right = chars
        .clone()
        .collect::<String>()
        .parse()
        .map(|num| Some(Value::Number(num)))
        .unwrap_or_else(|_| chars.next().map(Value::Register));

    if let Operation::Jump = op {
        if let Some(Value::Register(reg)) = right {
            return Err(format!(
                "Expected label for jump instruction, got register: {reg}"
            ));
        };
    };
    Ok(Line {
        label,
        instruction: match op {
            Operation::Register(regop) => Instruction::Register(
                left.ok_or(format!("Expected register for {} instruction", op))?,
                regop,
                right.unwrap_or(Value::Number(1)),
            ),
            Operation::Result => Instruction::Result(if let Some(reg) = left {
                ResultValue::Register(reg)
            } else if let Some(val) = right {
                ResultValue::Value(val)
            } else {
                ResultValue::All
            }),
            Operation::Jump => Instruction::Jump(
                left,
                match right {
                    Some(Value::Number(num)) => Ok(num as u32),
                    Some(Value::Register(reg)) => Err(format!(
                        "Expected label for jump instruction, got register {reg}"
                    )),
                    None => Err("Expected label for jump instruction".to_string()),
                }?,
            ),
            Operation::Take => Instruction::Take(
                left.ok_or(format!("Expected register for {} instruction", op))?,
                match right {
                    Some(Value::Register(reg)) => Ok(reg),
                    _ => Err("Expected register for take instruction".to_string()),
                }?,
            ),
        },
    })
}

/// Parse a line adding a register to the program.
/// Can be one of two formats:
/// Specifying the length: `a:1`
/// Specifying the content: `a:[1 2 3]`
fn parse_register_definition(line: &str) -> Result<RegisterDefinition, String> {
    let mut line = line.chars();
    let name = line.next().unwrap();
    let _column = line.next().unwrap();
    let def: String = line.collect();
    Ok(RegisterDefinition {
        name,
        contents: if let Some(list) = def.strip_prefix('[') {
            RegisterContents::Values(
                list.strip_suffix(']')
                    .ok_or_else(|| "Expected closing bracket".to_string())?
                    .split_whitespace()
                    .map(|n| n.parse::<u16>())
                    .collect::<Result<Vec<u16>, _>>()
                    .map_err(|e| format!("Couldn't parse number: {e}"))?,
            )
        } else {
            RegisterContents::Size(
                def.parse::<u16>()
                    .map_err(|e| format!("Couldn't parse register size '{def}': {e}"))?,
            )
        },
    })
}

pub(crate) type RegisterDefinitions = Vec<(usize, RegisterDefinition)>;

pub(crate) fn parse(source: &str) -> Result<(Vec<(usize, Line)>, RegisterDefinitions), Error> {
    let mut lines = Vec::new();
    let mut register_definitions = Vec::new();
    for (line_num, line) in source.lines().enumerate() {
        let line = strip_comments(line).trim();
        if line.is_empty() {
            continue;
        };
        if line.contains(':') {
            register_definitions.push((
                line_num,
                parse_register_definition(line).map_err(|message| Error {
                    message,
                    line: line_num,
                })?,
            ))
        } else {
            lines.push((
                line_num + 1,
                parse_instruction(line).map_err(|message| Error {
                    message,
                    line: line_num,
                })?,
            ))
        }
    }
    Ok((lines, register_definitions))
}