use crate::{
    BusinessProcessModelAndNotation,
    element::BPMNElementTrait,
    execute_transition_parallel_split,
    parser::parser_state::GlobalIndex,
    semantics::{BPMNRootMarking, BPMNSubMarking, TransitionIndex},
    traits::{
        objectable::{BPMNObject, EMPTY_FLOWS},
        processable::Processable,
        transitionable::Transitionable,
    },
};
use anyhow::{Result, anyhow};
use bitvec::{bitvec, vec::BitVec};
use ebi_activity_key::Activity;

#[derive(Debug, Clone)]
pub struct BPMNStartEvent {
    pub(crate) global_index: GlobalIndex,
    pub(crate) id: String,
    pub(crate) local_index: usize,
    pub(crate) outgoing_sequence_flows: Vec<usize>,
}

impl BPMNElementTrait for BPMNStartEvent {
    fn add_incoming_sequence_flow(&mut self, _flow_index: usize) -> Result<()> {
        Err(anyhow!("start events cannot have incoming sequence flows"))
    }

    fn add_outgoing_sequence_flow(&mut self, flow_index: usize) -> Result<()> {
        self.outgoing_sequence_flows.push(flow_index);
        Ok(())
    }

    fn add_incoming_message_flow(&mut self, _flow_index: usize) -> Result<()> {
        Err(anyhow!("start events cannot have incoming message flows"))
    }

    fn add_outgoing_message_flow(&mut self, _flow_index: usize) -> Result<()> {
        Err(anyhow!("start events cannot have outgoing message flows"))
    }

    fn verify_structural_correctness(
        &self,
        _parent: &dyn Processable,
        _bpmn: &BusinessProcessModelAndNotation,
    ) -> Result<()> {
        Ok(())
    }
}

impl BPMNObject for BPMNStartEvent {
    fn local_index(&self) -> usize {
        self.local_index
    }

    fn global_index(&self) -> GlobalIndex {
        self.global_index
    }

    fn id(&self) -> &str {
        &self.id
    }

    fn is_unconstrained_start_event(
        &self,
        _bpmn: &BusinessProcessModelAndNotation,
    ) -> Result<bool> {
        Ok(true)
    }

    fn is_end_event(&self) -> bool {
        false
    }

    fn incoming_sequence_flows(&self) -> &[usize] {
        &EMPTY_FLOWS
    }

    fn outgoing_sequence_flows(&self) -> &[usize] {
        &self.outgoing_sequence_flows
    }

    fn incoming_message_flows(&self) -> &[usize] {
        &EMPTY_FLOWS
    }

    fn outgoing_message_flows(&self) -> &[usize] {
        &EMPTY_FLOWS
    }

    fn can_start_process_instance(&self, _bpmn: &BusinessProcessModelAndNotation) -> Result<bool> {
        Ok(true)
    }

    fn outgoing_message_flows_always_have_tokens(&self) -> bool {
        false
    }

    fn outgoing_messages_cannot_be_removed(&self) -> bool {
        false
    }

    fn incoming_messages_are_ignored(&self) -> bool {
        false
    }

    fn can_have_incoming_sequence_flows(&self) -> bool {
        false
    }

    fn can_have_outgoing_sequence_flows(&self) -> bool {
        true
    }
}

#[macro_export]
macro_rules! enabled_transitions_start_event {
    ($self:ident, $root_marking:ident, $sub_marking:ident,$parent:ident) => {
        if !$parent.is_sub_process() && $root_marking.root_initial_choice_token {
            //enabled by root initial choice token
            bitvec![1;1]
        } else if $parent.is_sub_process() && $sub_marking.initial_choice_token {
            //enabled by sub-process initial choice token
            bitvec![1;1]
        } else if $sub_marking.element_index_2_tokens[$self.local_index] >= 1 {
            //enabled by element token
            bitvec![1;1]
        } else {
            //not enabled
            bitvec![0;1]
        }
    };
}

#[macro_export]
macro_rules! execute_transition_start_event {
    ($self:ident, $root_marking:ident, $sub_marking:ident, $parent:ident) => {{
        //consume
        if !$parent.is_sub_process() && $root_marking.root_initial_choice_token {
            //enabled by root initial choice token
            $root_marking.root_initial_choice_token = false;
        } else if $parent.is_sub_process() && $sub_marking.initial_choice_token {
            //enabled by sub-process initial choice token
            $sub_marking.initial_choice_token = false;
        } else if $sub_marking.element_index_2_tokens[$self.local_index] >= 1 {
            //enabled by element token
            $sub_marking.element_index_2_tokens[$self.local_index] -= 1;
        } else {
            //not enabled
            return Err(anyhow!("transition is not enabled"));
        }

        //produce
        execute_transition_parallel_split!($self, $sub_marking);
    }};
}

impl Transitionable for BPMNStartEvent {
    fn number_of_transitions(&self, _marking: &BPMNSubMarking) -> usize {
        1
    }

    fn enabled_transitions(
        &self,
        root_marking: &BPMNRootMarking,
        sub_marking: &BPMNSubMarking,
        parent: &dyn Processable,
        _bpmn: &BusinessProcessModelAndNotation,
    ) -> Result<BitVec> {
        Ok(enabled_transitions_start_event!(
            self,
            root_marking,
            sub_marking,
            parent
        ))
    }

    fn execute_transition(
        &self,
        _transition_index: TransitionIndex,
        root_marking: &mut BPMNRootMarking,
        sub_marking: &mut BPMNSubMarking,
        parent: &dyn Processable,
        _bpmn: &BusinessProcessModelAndNotation,
    ) -> Result<()> {
        execute_transition_start_event!(self, root_marking, sub_marking, parent);
        Ok(())
    }

    fn transition_activity(
        &self,
        _transition_index: TransitionIndex,
        _marking: &BPMNSubMarking,
    ) -> Option<Activity> {
        None
    }

    fn transition_debug(
        &self,
        transition_index: TransitionIndex,
        _marking: &BPMNSubMarking,
        _bpmn: &BusinessProcessModelAndNotation,
    ) -> Option<String> {
        Some(format!(
            "start event `{}`; internal transition {}",
            self.id, transition_index
        ))
    }
}
