mod battle;
mod plot_play;
mod procedure_mgr;
mod select_role;

pub use battle::ProcedureBattle;
pub use plot_play::ProcedurePlotPlay;
pub use procedure_mgr::ProcedureManager;
pub use select_role::ProcedureSelectRole;

use data::tables::{self, ProcedureConfigID, ProcedureConfigTemplate};
use num_enum::TryFromPrimitive;
use thiserror::Error;

#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
pub enum ProcedureState {
    Init,
    Running,
    Finish,
}

#[derive(Clone, Copy, PartialEq, Eq, Debug, TryFromPrimitive)]
#[repr(u32)]
pub enum ProcedureType {
    Battle = 1,
    PlotPlay = 2,
    SelectRole = 4,
}

#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum ProcedureAction {
    PerformTrigger,
    PerformEnd,
    SelectRole,
    BeginnerBattleBegin,
    BeginnerBattleEnd,
}

#[derive(Error, Debug)]
pub enum ProcedureError {
    #[error("action {0:?} is not allowed for procedure of type {1:?}")]
    UnsupportedAction(ProcedureAction, ProcedureType),
    #[error("action {0:?} is not allowed in current state: {1:?}")]
    InvalidState(ProcedureAction, ProcedureState),
    #[error("can't advance procedure because it's not finished")]
    NotFinished,
    #[error("trying to complete procedure: {0}, current procedure: {1}")]
    InvalidProcedureId(ProcedureConfigID, ProcedureConfigID),
    #[error("current procedure is NULL!")]
    ProcedureIsNull,
}

pub trait ProcedureBase {
    fn id(&self) -> ProcedureConfigID;
    fn procedure_type(&self) -> ProcedureType;

    fn get_next_id(&self) -> Option<ProcedureConfigID> {
        let config = tables::procedure_config_template_tb::iter()
            .find(|tmpl| tmpl.procedure_id == self.id())
            .unwrap();

        config
            .jump_tos
            .iter()
            .next()
            .map(|id| ProcedureConfigID::new_unchecked(*id))
    }

    fn on_action(&mut self, action: ProcedureAction) -> Result<ProcedureState, ProcedureError>;
    fn is_finished(&self) -> bool;
}

pub enum Procedure {
    Battle(ProcedureBattle),
    PlotPlay(ProcedurePlotPlay),
    SelectRole(ProcedureSelectRole),
}

impl Procedure {
    pub fn new(template: &ProcedureConfigTemplate) -> Self {
        use ProcedureType::*;
        let procedure_type = ProcedureType::try_from_primitive(template.procedure_type).unwrap();

        match procedure_type {
            Battle => Procedure::Battle(ProcedureBattle::new(template.procedure_id)),
            PlotPlay => Procedure::PlotPlay(ProcedurePlotPlay::new(template.procedure_id)),
            SelectRole => Procedure::SelectRole(ProcedureSelectRole::new(template.procedure_id)),
        }
    }

    pub fn base(&self) -> &dyn ProcedureBase {
        match self {
            Self::Battle(proc) => proc,
            Self::PlotPlay(proc) => proc,
            Self::SelectRole(proc) => proc,
        }
    }

    pub fn base_mut(&mut self) -> &mut dyn ProcedureBase {
        match self {
            Self::Battle(proc) => proc,
            Self::PlotPlay(proc) => proc,
            Self::SelectRole(proc) => proc,
        }
    }
}
