use crate::{
    element::BPMNElement,
    elements::collapsed_pool::BPMNCollapsedPool,
    parser::{
        parser::NameSpace,
        parser_state::{GlobalIndex, ParserState},
        parser_traits::{Closeable, Openable, Recognisable},
        tag_collaboration::Collaboration,
        tag_definitions::Definitions,
        tag_end_event::TagEndEvent,
        tag_event_based_gateway::TagEventBasedGateway,
        tag_exclusive_gateway::TagExclusiveGateway,
        tag_inclusive_gateway::TagInclusiveGateway,
        tag_intermediate_catch_event::TagIntermediateCatchEvent,
        tag_intermediate_throw_event::TagIntermediateThrowEvent,
        tag_message_event_definition::TagMessageEventDefinition,
        tag_message_flow::{DraftMessageFlow, TagMessageFlow},
        tag_parallel_gateway::TagParallelGateway,
        tag_participant::TagParticipant,
        tag_process::TagProcess,
        tag_receive_task::TagReceiveTask,
        tag_sequence_flow::{DraftSequenceFlow, TagSequenceFlow},
        tag_start_event::TagStartEvent,
        tag_subprocess::TagSubProcess,
        tag_task::TagTask,
        tag_timer_event_definition::TagTimerEventDefinition,
        tag_weight::TagWeight,
    },
};
use anyhow::Result;
use ebi_activity_key::Activity;
use ebi_arithmetic::Fraction;
use quick_xml::events::{BytesEnd, BytesStart};
use strum::IntoEnumIterator;
use strum_macros::{Display, EnumIs, EnumIter, EnumString};

#[derive(Clone, Copy, EnumString, EnumIter, Display)]
pub(crate) enum Tag {
    Collaboration,
    Definitions,
    EndEvent,
    EventBasedGateway,
    ExclusiveGateway,
    InclusiveGateway,
    IntermediateCatchEvent,
    IntermediateThrowEvent,
    MessageEventDefinition,
    MessageFlow,
    ParallelGateway,
    Participant,
    Process,
    ReceiveTask,
    SequenceFlow,
    SubProcess,
    StartEvent,
    Task,
    TimerEventDefinition,
    Weight,
}

impl Recognisable for Tag {
    fn recognise_tag(e: &BytesStart, state: &ParserState, n: NameSpace) -> Option<Self>
    where
        Self: Sized,
    {
        for tag in Tag::iter() {
            let x = match tag {
                Tag::Definitions => Definitions::recognise_tag(e, state, n),
                Tag::Process => TagProcess::recognise_tag(e, state, n),
                Tag::Collaboration => Collaboration::recognise_tag(e, state, n),
                Tag::MessageEventDefinition => {
                    TagMessageEventDefinition::recognise_tag(e, state, n)
                }
                Tag::MessageFlow => TagMessageFlow::recognise_tag(e, state, n),
                Tag::StartEvent => TagStartEvent::recognise_tag(e, state, n),
                Tag::Task => TagTask::recognise_tag(e, state, n),
                Tag::SequenceFlow => TagSequenceFlow::recognise_tag(e, state, n),
                Tag::EndEvent => TagEndEvent::recognise_tag(e, state, n),
                Tag::ExclusiveGateway => TagExclusiveGateway::recognise_tag(e, state, n),
                Tag::InclusiveGateway => TagInclusiveGateway::recognise_tag(e, state, n),
                Tag::IntermediateThrowEvent => {
                    TagIntermediateThrowEvent::recognise_tag(e, state, n)
                }
                Tag::IntermediateCatchEvent => {
                    TagIntermediateCatchEvent::recognise_tag(e, state, n)
                }
                Tag::ParallelGateway => TagParallelGateway::recognise_tag(e, state, n),
                Tag::EventBasedGateway => TagEventBasedGateway::recognise_tag(e, state, n),
                Tag::SubProcess => TagSubProcess::recognise_tag(e, state, n),
                Tag::Participant => TagParticipant::recognise_tag(e, state, n),
                Tag::TimerEventDefinition => TagTimerEventDefinition::recognise_tag(e, state, n),
                Tag::Weight => TagWeight::recognise_tag(e, state, n),
                Tag::ReceiveTask => TagReceiveTask::recognise_tag(e, state, n),
            };
            if x.is_some() {
                return x;
            }
        }
        None
    }
}

impl Openable for Tag {
    fn open_tag(tag: Tag, e: &BytesStart, state: &mut ParserState) -> Result<OpenedTag>
    where
        Self: Sized,
    {
        match tag {
            Tag::Definitions => Definitions::open_tag(tag, e, state),
            Tag::Process => TagProcess::open_tag(tag, e, state),
            Tag::Collaboration => Collaboration::open_tag(tag, e, state),
            Tag::MessageEventDefinition => TagMessageEventDefinition::open_tag(tag, e, state),
            Tag::MessageFlow => TagMessageFlow::open_tag(tag, e, state),
            Tag::StartEvent => TagStartEvent::open_tag(tag, e, state),
            Tag::Task => TagTask::open_tag(tag, e, state),
            Tag::SequenceFlow => TagSequenceFlow::open_tag(tag, e, state),
            Tag::EndEvent => TagEndEvent::open_tag(tag, e, state),
            Tag::ExclusiveGateway => TagExclusiveGateway::open_tag(tag, e, state),
            Tag::InclusiveGateway => TagInclusiveGateway::open_tag(tag, e, state),
            Tag::IntermediateThrowEvent => TagIntermediateThrowEvent::open_tag(tag, e, state),
            Tag::IntermediateCatchEvent => TagIntermediateCatchEvent::open_tag(tag, e, state),
            Tag::ParallelGateway => TagParallelGateway::open_tag(tag, e, state),
            Tag::EventBasedGateway => TagEventBasedGateway::open_tag(tag, e, state),
            Tag::SubProcess => TagSubProcess::open_tag(tag, e, state),
            Tag::Participant => TagParticipant::open_tag(tag, e, state),
            Tag::TimerEventDefinition => TagTimerEventDefinition::open_tag(tag, e, state),
            Tag::Weight => TagWeight::open_tag(tag, e, state),
            Tag::ReceiveTask => TagReceiveTask::open_tag(tag, e, state),
        }
    }
}

#[derive(Debug, EnumIs)]
pub(crate) enum OpenedTag {
    Unknown,
    Collaboration {
        global_index: GlobalIndex,
        id: String,
        collapsed_pools: Vec<BPMNCollapsedPool>,
        message_flows: Vec<DraftMessageFlow>,
    },
    Definitions {
        global_index: GlobalIndex,
        id: String,
        collaboration_index: Option<GlobalIndex>,
        collaboration_id: Option<String>,
        stochastic_namespace: bool,
        draft_message_flows: Vec<DraftMessageFlow>,
        elements: Vec<BPMNElement>,
    },
    EndEvent {
        global_index: GlobalIndex,
        id: String,
        message_marker_id: Option<String>,
    },
    EventBasedGateway {
        global_index: GlobalIndex,
        id: String,
    },
    ExclusiveGateway {
        global_index: GlobalIndex,
        id: String,
    },
    InclusiveGateway {
        global_index: GlobalIndex,
        id: String,
    },
    IntermediateCatchEvent {
        global_index: GlobalIndex,
        id: String,
        message_marker_id: Option<String>,
        timer_marker_id: Option<String>,
    },
    IntermediateThrowEvent {
        global_index: GlobalIndex,
        id: String,
        message_marker_id: Option<String>,
    },
    MessageEventDefinition {
        id: String,
    },
    MessageFlow {
        global_index: GlobalIndex,
        id: String,
        source_id: String,
        target_id: String,
    },
    ParallelGateway {
        global_index: GlobalIndex,
        id: String,
    },
    Participant {
        global_index: GlobalIndex,
        id: String,
        name: Option<String>,
        process_id: Option<String>,
    },
    Process {
        global_index: GlobalIndex,
        id: String,
        elements: Vec<BPMNElement>,
        draft_sequence_flows: Vec<DraftSequenceFlow>,
    },
    ReceiveTask {
        global_index: GlobalIndex,
        id: String,
        activity: Activity,
    },
    SequenceFlow {
        global_index: GlobalIndex,
        id: String,
        source_ref: String,
        target_ref: String,
        weight: Option<Fraction>,
    },
    SubProcess {
        global_index: GlobalIndex,
        id: String,
        name: Option<String>,
        elements: Vec<BPMNElement>,
        draft_sequence_flows: Vec<DraftSequenceFlow>,
    },
    StartEvent {
        global_index: GlobalIndex,
        id: String,
        message_marker_id: Option<String>,
        timer_marker_id: Option<String>,
    },
    Task {
        global_index: GlobalIndex,
        id: String,
        activity: Activity,
    },
    TimerEventDefinition {
        id: String,
    },
    Weight {
        weight: Fraction,
    },
}

impl Closeable for OpenedTag {
    fn close_tag(opened_tag: OpenedTag, e: &BytesEnd, state: &mut ParserState) -> Result<()> {
        match opened_tag {
            OpenedTag::Unknown => Ok(()),
            OpenedTag::Definitions { .. } => Definitions::close_tag(opened_tag, e, state),
            OpenedTag::Process { .. } => TagProcess::close_tag(opened_tag, e, state),
            OpenedTag::Collaboration { .. } => Collaboration::close_tag(opened_tag, e, state),
            OpenedTag::MessageEventDefinition { .. } => {
                TagMessageEventDefinition::close_tag(opened_tag, e, state)
            }
            OpenedTag::MessageFlow { .. } => TagMessageFlow::close_tag(opened_tag, e, state),
            OpenedTag::StartEvent { .. } => TagStartEvent::close_tag(opened_tag, e, state),
            OpenedTag::Task { .. } => TagTask::close_tag(opened_tag, e, state),
            OpenedTag::SequenceFlow { .. } => TagSequenceFlow::close_tag(opened_tag, e, state),
            OpenedTag::EndEvent { .. } => TagEndEvent::close_tag(opened_tag, e, state),
            OpenedTag::ExclusiveGateway { .. } => {
                TagExclusiveGateway::close_tag(opened_tag, e, state)
            }
            OpenedTag::InclusiveGateway { .. } => {
                TagInclusiveGateway::close_tag(opened_tag, e, state)
            }
            OpenedTag::IntermediateThrowEvent { .. } => {
                TagIntermediateThrowEvent::close_tag(opened_tag, e, state)
            }
            OpenedTag::IntermediateCatchEvent { .. } => {
                TagIntermediateCatchEvent::close_tag(opened_tag, e, state)
            }
            OpenedTag::ParallelGateway { .. } => {
                TagParallelGateway::close_tag(opened_tag, e, state)
            }
            OpenedTag::EventBasedGateway { .. } => {
                TagEventBasedGateway::close_tag(opened_tag, e, state)
            }
            OpenedTag::SubProcess { .. } => TagSubProcess::close_tag(opened_tag, e, state),
            OpenedTag::Participant { .. } => TagParticipant::close_tag(opened_tag, e, state),
            OpenedTag::TimerEventDefinition { .. } => {
                TagTimerEventDefinition::close_tag(opened_tag, e, state)
            }
            OpenedTag::Weight { .. } => TagWeight::close_tag(opened_tag, e, state),
            OpenedTag::ReceiveTask { .. } => TagReceiveTask::close_tag(opened_tag, e, state),
        }
    }
}
