use super::super::generic::trigger::TriggerRegistry;
use super::print::style::AsyncTaskView;
use crate::debugger::PlaceDescriptor;
use crate::debugger::address::RelocatedAddress;
use crate::debugger::register::debug::BreakCondition;
use crate::debugger::variable::value::Value;
use crate::debugger::{EventHook, FunctionInfo};
use crate::ui::command;
use crate::ui::generic::file::FileView;
use crate::ui::generic::print::ExternalPrinter;
use crate::ui::generic::print::style::{AddressView, FilePathView, FunctionNameView, KeywordView};
use crate::ui::generic::variable::render_value;
use crate::version;
use crossterm::style::Stylize;
use log::warn;
use nix::sys::signal::Signal;
use nix::unistd::Pid;
use std::cell::RefCell;
use std::ops::Add;
use std::rc::Rc;

#[derive(Default)]
struct Context {
    prev_func: Option<FunctionInfo>,
}

pub struct TerminalHook {
    file_view: Rc<FileView>,
    on_install_proc: Box<dyn Fn(Pid)>,
    printer: ExternalPrinter,
    context: RefCell<Context>,
    trigger_reg: Rc<TriggerRegistry>,
}

impl TerminalHook {
    pub fn new(
        printer: ExternalPrinter,
        fv: Rc<FileView>,
        on_install_proc: impl Fn(Pid) + 'static,
        trigger_reg: Rc<TriggerRegistry>,
    ) -> Self {
        Self {
            file_view: fv,
            on_install_proc: Box::new(on_install_proc),
            printer,
            context: RefCell::new(Context::default()),
            trigger_reg,
        }
    }
}

impl EventHook for TerminalHook {
    fn on_breakpoint(
        &self,
        pc: RelocatedAddress,
        num: u32,
        mb_place: Option<PlaceDescriptor>,
        mb_func: Option<&FunctionInfo>,
        _: Option<u32>,
    ) -> anyhow::Result<()> {
        let msg = format!("Hit breakpoint {num} at {}:", AddressView::from(pc));
        if let Some(place) = mb_place {
            self.printer.println(format!(
                "{msg} {}:{}",
                FilePathView::from(place.file.to_string_lossy()),
                place.line_number
            ));
            self.printer.print(self.file_view.render_source(&place, 0)?);
        } else {
            self.printer.println(format!("{msg} undefined place"));
        }

        self.context.borrow_mut().prev_func = mb_func.cloned();
        self.trigger_reg
            .fire_event(command::trigger::TriggerEvent::Breakpoint(num));

        Ok(())
    }

    fn on_watchpoint(
        &self,
        pc: RelocatedAddress,
        num: u32,
        mb_place: Option<PlaceDescriptor>,
        cond: BreakCondition,
        dqe_string: Option<&str>,
        old: Option<&Value>,
        new: Option<&Value>,
        end_of_scope: bool,
    ) -> anyhow::Result<()> {
        let source_dqe = dqe_string
            .map(|dqe| format!(" (expr: {dqe})"))
            .unwrap_or_default();
        let msg = if end_of_scope {
            format!(
                "Watchpoint {num}{source_dqe} end of scope (and it will be removed)\n{}:",
                AddressView::from(pc)
            )
        } else {
            format!(
                "Hit watchpoint {num}{source_dqe} ({cond}) at {}:",
                AddressView::from(pc)
            )
        };

        if let Some(place) = mb_place {
            self.printer.println(format!(
                "{msg} {}:{}",
                FilePathView::from(place.file.to_string_lossy()),
                place.line_number
            ))
        } else {
            self.printer.println(format!("{msg} undefined place"));
        };

        if cond == BreakCondition::DataReadsWrites && old == new {
            if let Some(old) = old {
                let val = render_value(old);
                self.printer.println(format!("value: {val}"));
            }
        } else {
            if let Some(old) = old {
                let old = render_value(old);
                self.printer.println(format!("old value: {old}"));
            }
            if let Some(new) = new {
                let new = render_value(new);
                self.printer.println(format!("new value: {new}"));
            }
        }

        self.trigger_reg
            .fire_event(command::trigger::TriggerEvent::Watchpoint(num));

        Ok(())
    }

    fn on_step(
        &self,
        _: RelocatedAddress,
        mb_place: Option<PlaceDescriptor>,
        mb_func: Option<&FunctionInfo>,
        _: Option<u32>,
    ) -> anyhow::Result<()> {
        if let Some(place) = mb_place {
            if self.context.borrow().prev_func.as_ref() != mb_func {
                self.context.borrow_mut().prev_func = mb_func.cloned();

                let func_name = mb_func.map(|f| {
                    f.namespace
                        .as_parts()
                        .join("::")
                        .add("::")
                        .add(f.name.as_deref().unwrap_or_default())
                });

                self.printer.println(format!(
                    "{} at {}:{}",
                    FunctionNameView::from(func_name),
                    FilePathView::from(place.file.to_string_lossy()),
                    place.line_number,
                ));
            }
            self.printer.print(self.file_view.render_source(&place, 0)?);
        } else {
            self.printer.println("undefined place, go to next");
        }

        Ok(())
    }

    fn on_async_step(
        &self,
        _: RelocatedAddress,
        mb_place: Option<PlaceDescriptor>,
        mb_func: Option<&FunctionInfo>,
        task_id: u64,
        task_completed: bool,
    ) -> anyhow::Result<()> {
        let text = if task_completed {
            format!("Task #{task_id} completed, stopped").bold()
        } else {
            format!("Task id: {task_id}").bold()
        };
        self.printer.println(AsyncTaskView::from(text));

        if let Some(place) = mb_place {
            if self.context.borrow().prev_func.as_ref() != mb_func {
                self.context.borrow_mut().prev_func = mb_func.cloned();

                let func_name = mb_func.map(|f| {
                    f.namespace
                        .as_parts()
                        .join("::")
                        .add("::")
                        .add(f.name.as_deref().unwrap_or_default())
                });

                self.printer.println(format!(
                    "{} at {}:{}",
                    FunctionNameView::from(func_name),
                    FilePathView::from(place.file.to_string_lossy()),
                    place.line_number,
                ));
            }
            self.printer.print(self.file_view.render_source(&place, 0)?);
        } else {
            self.printer.println("undefined place, go to next");
        }

        Ok(())
    }

    fn on_signal(&self, signal: Signal) {
        self.printer.println(format!(
            "Signal {} received, debugee stopped",
            KeywordView::from(signal)
        ));
    }

    fn on_exit(&self, code: i32) {
        self.printer.println(format!(
            "Program exit with code: {}",
            KeywordView::from(code)
        ));
    }

    fn on_process_install(&self, pid: Pid, object: Option<&object::File>) {
        if let Some(obj) = object
            && !version::probe_file(obj)
        {
            let supported_versions = version::supported_versions_to_string();
            warn!(target: "debugger", "Found unsupported rust version, some of program data may not be displayed correctly. \
                List of supported rustc versions: {supported_versions}.");
        }
        (self.on_install_proc)(pid)
    }
}
