use crate::commands::AppCommand;
use crate::state::undo::{UndoEntry, UndoScope};

use super::App;

impl App {
    pub(super) fn undoable_scope(&self, cmd: &AppCommand) -> Option<UndoScope> {
        match cmd {
            AppCommand::ToggleSteps
            | AppCommand::LengthIncrease
            | AppCommand::LengthDecrease
            | AppCommand::SpeedIncrease
            | AppCommand::SpeedDecrease
            | AppCommand::SaveEditorToStep
            | AppCommand::CompileCurrentStep
            | AppCommand::HardenSteps
            | AppCommand::PasteSteps
            | AppCommand::LinkPasteSteps
            | AppCommand::DuplicateSteps
            | AppCommand::CopySteps => {
                let (bank, pattern) = self.current_bank_pattern();
                let data = self.project_state.project.pattern_at(bank, pattern).clone();
                Some(UndoScope::Pattern { bank, pattern, data })
            }
            AppCommand::SetLength { bank, pattern, .. }
            | AppCommand::SetSpeed { bank, pattern, .. }
            | AppCommand::DeleteStep { bank, pattern, .. }
            | AppCommand::DeleteSteps { bank, pattern, .. }
            | AppCommand::ResetPattern { bank, pattern }
            | AppCommand::PastePattern { bank, pattern }
            | AppCommand::ImportPattern { bank, pattern }
            | AppCommand::RenamePattern { bank, pattern, .. } => {
                let data = self.project_state.project.pattern_at(*bank, *pattern).clone();
                Some(UndoScope::Pattern { bank: *bank, pattern: *pattern, data })
            }
            AppCommand::RenameStep { bank, pattern, .. }
            | AppCommand::DescribePattern { bank, pattern, .. } => {
                let data = self.project_state.project.pattern_at(*bank, *pattern).clone();
                Some(UndoScope::Pattern { bank: *bank, pattern: *pattern, data })
            }
            AppCommand::ApplyEuclideanDistribution { bank, pattern, .. } => {
                let data = self.project_state.project.pattern_at(*bank, *pattern).clone();
                Some(UndoScope::Pattern { bank: *bank, pattern: *pattern, data })
            }
            AppCommand::ResetBank { bank }
            | AppCommand::PasteBank { bank }
            | AppCommand::ImportBank { bank } => {
                let data = self.project_state.project.banks[*bank].clone();
                Some(UndoScope::Bank { bank: *bank, data })
            }
            AppCommand::ShiftPatternsUp | AppCommand::ShiftPatternsDown => {
                let bank = self.patterns_nav.bank_cursor;
                let data = self.project_state.project.banks[bank].clone();
                Some(UndoScope::Bank { bank, data })
            }
            AppCommand::PastePatterns { bank, .. }
            | AppCommand::ResetPatterns { bank, .. }
            | AppCommand::RenameBank { bank, .. } => {
                let data = self.project_state.project.banks[*bank].clone();
                Some(UndoScope::Bank { bank: *bank, data })
            }
            AppCommand::PasteBanks { .. } | AppCommand::ResetBanks { .. } => None,
            _ => None,
        }
    }

    pub(super) fn apply_undo_entry(&mut self, entry: UndoEntry) -> UndoEntry {
        let cursor = (self.editor_ctx.bank, self.editor_ctx.pattern, self.editor_ctx.step);
        let reverse_scope = match entry.scope {
            UndoScope::Pattern { bank, pattern, data } => {
                let current = std::mem::replace(
                    self.project_state.project.pattern_at_mut(bank, pattern),
                    data,
                );
                self.project_state.mark_dirty(bank, pattern);
                UndoScope::Pattern { bank, pattern, data: current }
            }
            UndoScope::Bank { bank, data } => {
                let current = std::mem::replace(
                    &mut self.project_state.project.banks[bank],
                    data,
                );
                let pat_count = current.patterns.len();
                for p in 0..pat_count {
                    self.project_state.mark_dirty(bank, p);
                }
                UndoScope::Bank { bank, data: current }
            }
        };
        self.editor_ctx.bank = entry.cursor.0;
        self.editor_ctx.pattern = entry.cursor.1;
        self.editor_ctx.step = entry.cursor.2;
        self.load_step_to_editor();
        UndoEntry { scope: reverse_scope, cursor }
    }
}
