use crate::debugger::Debugger;
use crate::debugger::Error;
use crate::debugger::WatchpointView;
use crate::debugger::address::RelocatedAddress;
use crate::debugger::register::debug::{BreakCondition, BreakSize};
use crate::debugger::variable::dqe::Dqe;

#[derive(Debug, Clone, PartialEq)]
pub enum WatchpointIdentity {
    DQE(String, Dqe),
    Address(usize, u8),
    Number(u32),
}

#[derive(Debug, Clone, PartialEq)]
pub enum Command {
    Add(WatchpointIdentity, BreakCondition),
    Remove(WatchpointIdentity),
    Info,
}

pub struct Handler<'a> {
    dbg: &'a mut Debugger,
}

pub enum ExecutionResult<'a> {
    New(WatchpointView<'a>),
    Removed(Option<WatchpointView<'a>>),
    Dump(Vec<WatchpointView<'a>>),
}

impl<'a> Handler<'a> {
    pub fn new(debugger: &'a mut Debugger) -> Self {
        Self { dbg: debugger }
    }

    pub fn handle(&mut self, cmd: Command) -> Result<ExecutionResult<'_>, Error> {
        match cmd {
            Command::Add(ident, cond) => {
                let new = match ident {
                    WatchpointIdentity::DQE(expr_string, dqe) => {
                        self.dbg.set_watchpoint_on_expr(&expr_string, dqe, cond)
                    }
                    WatchpointIdentity::Address(addr, size) => self.dbg.set_watchpoint_on_memory(
                        RelocatedAddress::from(addr),
                        BreakSize::try_from(size).expect("infallible (checked by parser)"),
                        cond,
                        false,
                    ),
                    WatchpointIdentity::Number(_) => {
                        unreachable!()
                    }
                }?;
                Ok(ExecutionResult::New(new))
            }
            Command::Remove(ident) => {
                let rem = match ident {
                    WatchpointIdentity::DQE(_, dqe) => self.dbg.remove_watchpoint_by_expr(dqe),
                    WatchpointIdentity::Address(addr, _) => self
                        .dbg
                        .remove_watchpoint_by_addr(RelocatedAddress::from(addr)),
                    WatchpointIdentity::Number(num) => self.dbg.remove_watchpoint_by_number(num),
                }?;
                Ok(ExecutionResult::Removed(rem))
            }
            Command::Info => {
                let list = self.dbg.watchpoint_list();
                Ok(ExecutionResult::Dump(list))
            }
        }
    }
}
