use retracer::{database::Database, interface::component_editor::CommandTypeEditor, History};

/// Generate commands from their type name
struct CommandFactory();
impl CommandFactory {
    fn new() -> Box<CommandFactory> {
        Box::new(CommandFactory())
    }
}

impl command_factory::CommandFactory for CommandFactory {
    fn new_command(&self, command_name: CTName) -> Box<dyn Command<D>> {
        match command_name {
            "AddCounter" => AddCounter::new(),
            "RemoveCounter" => RemoveCounter::new(),
            _ => panic!("Command type does not exist."),
        }
    }
}

/// Add a new Counter
struct AddCounter();
impl AddCounter {
    pub fn new() -> Self {
        AddCounter()
    }
    pub fn register<D: Database + Default>(history: HistoryInterface<D>) -> Result<(), ()> {
        CommandTypeEditor::new("AddCounter").register_to(history)
    }
    pub fn pre_run() {}
    pub fn setup() {}
}

impl Command for AddCounter {
    fn command_type(&self) -> CTName {
        "AddCounter"
    }

    fn set_arguments(&mut self, data: Arguments) {}

    fn arguments(&self) -> Arguments {}

    fn run(&self, history: HistoryInterface<D>) -> ExecutionResult<D> {
        let board = history.get_element_here("MainBoard");

        let element_name = change.new_element("Counter");
        let counter = Counter {
            name: element_name,
            value: 0,
        };
        change.history().element(element_name).new_version(); // new_version_named("X")
    }
}

/// Remove a Counter of the Board
struct RemoveCounter(Arguments);
impl RemoveCounter {
    pub fn new() -> Self {
        RemoveCounter(Arguments::new())
    }
    pub fn register<D: Database + Default>(history: HistoryInterface<D>) {
        CommandTypeEditor::new("RemoveCounter")
            .arguments(HashMap::from([("self", "Board"), ("idx", "Integer")]))
            .register_to(history);
    }
}

impl Command for RemoveCounter {
    fn command_type(&self) -> CTName {
        "RemoveCounter"
    }

    fn set_arguments(&mut self, data: Arguments) {
        self.0 = data;
    }

    fn arguments(&self) -> Arguments {
        self.0
    }

    fn run(&self, change: Change<D>) -> ExecutionResult<D> {
        let (board, idx): (Board, usize) = change.load_data();
        let (board_cmp, idx_cmp): (ElementVersionComponent, InputVersionComponent) =
            change.load_components();

        board_cmp
            .change(change)
            .children_mut(change)
            .remove(board.counters[idx]);
    }
}

/// Register command types
pub fn register_command_types() {
    history
        .register_new_command_type(vec![
            AddCounter::type_description(),
            RemoveCounter::type_description(),
            IncrementCounter::type_description(),
        ])
        .unwrap()
}