use std::path::{Path, PathBuf};

use crate::assembler::TokenType;
use crate::{assembler::AssembleError, expect_token, expect_type, node};

use crate::assembler::model::{Node, Opcode, Token};
use common::prelude::*;

pub struct Parser {
    tokens: Vec<Token>,
    nodes: Vec<Node>,
}

#[derive(Debug)]
pub struct Program {
    pub nodes: Vec<Node>,
}

impl Program {
    #[must_use]
    pub const fn new() -> Self {
        Self { nodes: vec![] }
    }

    pub fn add_module(&mut self, module: Vec<Node>) {
        self.nodes.extend(module);
    }

    pub fn parser(&mut self) -> Parser {
        Parser {
            tokens: vec![],
            nodes: self.nodes.clone(),
        }
    }
}

impl Default for Program {
    fn default() -> Self {
        Self::new()
    }
}

impl Parser {
    pub fn parse_nodes(tokens: Vec<Token>) -> Result<Vec<Node>, AssembleError> {
        let mut self_ = Self {
            tokens: tokens.into_iter().rev().collect(),
            nodes: vec![],
        };

        while !self_.tokens.is_empty() {
            let ins = self_.parse_instruction()?;
            self_.nodes.push(ins);
        }

        Ok(self_.nodes.clone())
    }

    pub fn get_dependencies(
        nodes: &Vec<Node>,
        source_path: &Path,
    ) -> Result<Vec<PathBuf>, AssembleError> {
        let mut dependencies = Vec::new();
        // Get the parent directory of the source file to use as the base directory
        let base_dir = source_path
            .parent()
            .ok_or_else(|| AssembleError::InvalidFile(source_path.to_path_buf()))?;

        for node in nodes {
            if node.opcode() == Opcode::Include {
                let path_str = expect_token!(
                    node.args().get(1).ok_or(AssembleError::Generic)?,
                    StringLit
                )?;
                let path = PathBuf::from(path_str);

                // If the path is not absolute, make it relative to the base directory
                let full_path = if path.is_absolute() {
                    path
                } else {
                    base_dir.join(path)
                };

                dependencies.push(full_path);
            }
        }
        Ok(dependencies)
    }

    #[expect(clippy::too_many_lines, clippy::cognitive_complexity)]
    fn parse_instruction(&mut self) -> Result<Node, AssembleError> {
        if self.tokens.is_empty() {
            unreachable!();
        }

        // check if the Node starts with a label
        let label = expect_token!(self.peek_next()?, Symbol).ok();
        if label.is_some() {
            self.tokens.pop();
        }

        let opcode = expect_token!(self.next()?, Opcode)?;
        let args: Vec<Token>;

        #[allow(clippy::match_same_arms)]
        match opcode {
            // R-type instructions
            Opcode::Mov | Opcode::Movs => {
                let reg1 = expect_type!(self.next()?, Register, Symbol)?;
                let reg2 = expect_type!(self.next()?, Register, Symbol)?;
                args = vec![reg1, reg2];
            }

            Opcode::Ldb | Opcode::Ldbs | Opcode::Ldh | Opcode::Ldhs | Opcode::Ldw => {
                let base = expect_type!(self.next()?, Register, Symbol)?;
                let dest = expect_type!(self.next()?, Register)?;

                let offset = match self.peek_next() {
                    Ok(next) if expect_type!(next.clone(), Immediate).is_ok() => {
                        self.next()?
                    }
                    _ => Token::Immediate(0),
                };

                args = vec![base, dest, offset];
            }
            Opcode::Stb | Opcode::Sth | Opcode::Stw => {
                let base = expect_type!(self.next()?, Register)?;
                let dest = expect_type!(self.next()?, Register, Symbol)?;

                let offset = match self.peek_next() {
                    Ok(next) if expect_type!(next.clone(), Immediate).is_ok() => {
                        self.next()?
                    }
                    _ => Token::Immediate(0),
                };
                args = vec![base, dest, offset];
            }

            Opcode::Add
            | Opcode::Sub
            | Opcode::And
            | Opcode::Or
            | Opcode::Xor
            | Opcode::Nand
            | Opcode::Nor
            | Opcode::Xnor => {
                let src1 = expect_type!(self.next()?, Register, Symbol)?;
                let src2 = expect_type!(self.next()?, Register, Symbol)?;
                let dest = expect_type!(self.next()?, Register, Symbol)?;
                args = vec![src1, src2, dest];
            }

            Opcode::Not | Opcode::Cmp => {
                let src = expect_type!(self.next()?, Register, Symbol)?;
                let dest = expect_type!(self.next()?, Register, Symbol)?;
                args = vec![src, dest];
            }
            Opcode::Shl | Opcode::Shr => {
                let src = expect_type!(self.next()?, Register, Symbol)?;

                // First operand after src: could be immediate or register
                let first = self.next()?;

                let (r_shamt, i_shamt) = match first {
                    Token::Register(_) => (
                        first,
                        if let Ok(tok) = self.peek_next() {
                            if expect_type!(tok, Immediate).is_ok() {
                                self.next()?
                            } else {
                                Token::Immediate(0)
                            }
                        } else {
                            Token::Immediate(0)
                        },
                    ),
                    Token::Immediate(_) => (Token::Register(Register::Zero), first),
                    _ => {
                        return Err(AssembleError::UnexpectedToken(
                            first,
                            TokenType::Immediate,
                        ));
                    }
                };

                let dest = if let Ok(tok) = self.peek_next() {
                    if expect_type!(tok, Register).is_ok() {
                        self.next()?
                    } else {
                        src.clone() // Default to src if no dest specified
                    }
                } else {
                    src.clone() // Default to src if no dest specified
                };

                args = vec![src, r_shamt, i_shamt, dest];
            }

            Opcode::Inc | Opcode::Dec => {
                let reg = expect_type!(self.next()?, Register, Symbol)?;
                args = vec![reg];
            }

            Opcode::Include => {
                let mod_name = expect_type!(self.next()?, Symbol)?;
                let path = expect_type!(self.next()?, StringLit)?;
                args = vec![mod_name, path];
            }

            // J-type instructions
            Opcode::Jmp
            | Opcode::Jeq
            | Opcode::Jne
            | Opcode::Jgt
            | Opcode::Jge
            | Opcode::Jlt
            | Opcode::Jle => {
                let imm = expect_type!(self.next()?, Immediate, Symbol)?;
                let offset = match self.peek_next() {
                    Ok(token) => {
                        if expect_type!(token, Register).is_ok() {
                            self.next()?
                        } else {
                            Token::Register(Register::Zero)
                        }
                    }
                    Err(_) => Token::Register(Register::Zero),
                };
                args = vec![imm, offset];
            }

            Opcode::Call => {
                let addr = expect_type!(self.next()?, Symbol)?;
                args = vec![addr];
            }

            // I-type instructions
            Opcode::Lui | Opcode::Lli | Opcode::Lwi => {
                let imm = expect_type!(self.next()?, Immediate, Symbol)?;
                let reg = expect_type!(self.next()?, Register)?;
                args = vec![imm, reg];
            }

            // Immediate Arithmetic
            Opcode::AddI | Opcode::SubI => {
                let reg = expect_type!(self.next()?, Register)?;
                let imm = expect_type!(self.next()?, Immediate)?;
                let reg2 = if expect_type!(self.peek_next()?, Register).is_ok() {
                    self.next()?
                } else {
                    reg.clone()
                };
                args = vec![reg, imm, reg2];
            }

            // D-type pseudoinstructions (data definition)
            Opcode::Resb | Opcode::Resh | Opcode::Resw => {
                let name = expect_type!(self.next()?, Symbol)?;
                let num = expect_type!(self.next()?, Immediate)?;
                args = vec![name, num];
            }

            Opcode::Db | Opcode::Dh | Opcode::Dw => {
                args = self.parse_data_definition(opcode)?;
            }

            // E-type pseudoinstructions (stack operations)
            Opcode::Push | Opcode::Pop => {
                let reg = expect_type!(self.next()?, Register, Symbol)?;
                args = vec![reg];
            }

            Opcode::Pusha | Opcode::Popa => {
                let count =
                    expect_type!(self.next()?, Immediate).unwrap_or(Token::Immediate(8));
                args = vec![count];
            }

            // Special instructions
            Opcode::Int => {
                let val = expect_type!(self.next()?, Immediate)?;
                args = vec![val];
            }

            // Instructions with no arguments
            Opcode::Hlt | Opcode::Nop | Opcode::Irt | Opcode::Return => {
                args = vec![];
            }

            Opcode::Data | Opcode::Segment => {
                return Err(AssembleError::Generic);
            }
        }

        Ok(node!(label, opcode, args: args))
    }

    fn parse_data_definition(
        &mut self,
        opcode: Opcode,
    ) -> Result<Vec<Token>, AssembleError> {
        let mut values = Vec::new();

        let name = expect_type!(self.next()?, Symbol)?;
        values.push(name);

        match opcode {
            Opcode::Db => {
                // db can take string literals or u8 immediates
                while !self.tokens.is_empty() {
                    let token = self
                        .tokens
                        .last()
                        .expect("Expected a token for data definition, but found none");

                    match token {
                        Token::StringLit(_) => {
                            values.push(self.tokens.pop().expect(
                                "Expected a token for data definition, but found none",
                            ));
                        }
                        Token::Immediate(val) if u8::try_from(*val).is_ok() => {
                            values.push(self.tokens.pop().expect(
                                "Expected a token for data definition, but found none",
                            ));
                        }
                        _ => break,
                    }
                }
            }

            Opcode::Dh => {
                // dh can take u16 immediates
                while !self.tokens.is_empty() {
                    let token = self
                        .tokens
                        .last()
                        .expect("Expected a token for data definition, but found none");

                    match token {
                        Token::StringLit(_) => {
                            values.push(self.tokens.pop().expect(
                                "Expected a token for data definition, but found none",
                            ));
                        }
                        Token::Immediate(val) if u16::try_from(*val).is_ok() => {
                            values.push(self.tokens.pop().expect(
                                "Expected a token for data definition, but found none",
                            ));
                        }
                        _ => break,
                    }
                }
            }

            Opcode::Dw => {
                // dw can take u32 immediates
                while !self.tokens.is_empty() {
                    match self
                        .tokens
                        .last()
                        .expect("Expected a token for data definition, but found none")
                    {
                        Token::StringLit(_) => {
                            values.push(self.tokens.pop().expect(
                                "Expected a token for data definition, but found none",
                            ));
                        }
                        Token::Immediate(val) => {
                            values.push(self.tokens.pop().expect(
                                "Expected a token for data definition, but found none",
                            ));
                        }
                        _ => break,
                    }
                }
            }

            _ => unreachable!(),
        }

        Ok(values)
    }

    fn next(&mut self) -> Result<Token, AssembleError> {
        if self.tokens.is_empty() {
            Err(AssembleError::UnexpectedEof)
        } else {
            Ok(self
                .tokens
                .pop()
                .expect("tokens vector was unexpectedly empty in next()"))
        }
    }

    fn peek_next(&self) -> Result<Token, AssembleError> {
        if self.tokens.is_empty() {
            Err(AssembleError::UnexpectedEof)
        } else {
            Ok(self
                .tokens
                .last()
                .expect("peek_next called on empty tokens vector")
                .clone())
        }
    }
}
