use crate::error::ParserError;
use crate::parser::demo::svc::SvcMsg;
use crate::proto::*;
use crate::reader::{BitsReader, MessageReader};
use crate::{try_observers, GameEvent, GameEventList};
use crate::{Interests, Parser};

#[cfg(feature = "dota")]
use crate::event::CombatLogEntry;

pub trait DemoMessages {
    fn on_base_user_message(
        &mut self,
        msg_type: EBaseUserMessages,
        msg: &[u8],
    ) -> Result<(), ParserError>;
    fn on_base_game_event(
        &mut self,
        msg_type: EBaseGameEvents,
        msg: &[u8],
    ) -> Result<(), ParserError>;

    fn on_svc_message(&mut self, msg_type: SvcMessages, msg: &[u8]) -> Result<(), ParserError>;

    fn on_net_message(&mut self, msg_type: NetMessages, msg: &[u8]) -> Result<(), ParserError>;

    fn on_tick_start(&mut self, msg_tick: u32) -> Result<(), ParserError>;

    fn on_tick_end(&mut self) -> Result<(), ParserError>;

    fn on_stop(&mut self) -> Result<(), ParserError>;

    #[cfg(feature = "dota")]
    fn on_dota_user_message(
        &mut self,
        msg_type: EDotaUserMessages,
        msg: &[u8],
    ) -> Result<(), ParserError>;

    #[cfg(feature = "deadlock")]
    fn on_citadel_game_event(
        &mut self,
        msg_type: ECitadelGameEvents,
        msg: &[u8],
    ) -> Result<(), ParserError>;

    #[cfg(feature = "deadlock")]
    fn on_citadel_user_message(
        &mut self,
        msg_type: CitadelUserMessageIds,
        msg: &[u8],
    ) -> Result<(), ParserError>;

    #[cfg(feature = "cs2")]
    fn on_cs2_user_message(
        &mut self,
        msg_type: ECstrike15UserMessages,
        msg: &[u8],
    ) -> Result<(), ParserError>;

    #[cfg(feature = "cs2")]
    fn on_cs2_game_event(
        &mut self,
        msg_type: ECsgoGameEvents,
        msg: &[u8],
    ) -> Result<(), ParserError>;
}

impl<'a, R> DemoMessages for Parser<'a, R>
where
    R: BitsReader + MessageReader,
{
    fn on_base_user_message(
        &mut self,
        msg_type: EBaseUserMessages,
        msg: &[u8],
    ) -> Result<(), ParserError> {
        try_observers!(
            self,
            BASE_USER_MESSAGE,
            on_base_user_message(&self.context, msg_type, msg)
        )?;
        Ok(())
    }

    fn on_base_game_event(
        &mut self,
        msg_type: EBaseGameEvents,
        msg: &[u8],
    ) -> Result<(), ParserError> {
        if self.anyone_interested(Interests::BASE_GAME_EVENT) {
            if msg_type == EBaseGameEvents::GeSource1LegacyGameEventList {
                self.context.game_events = GameEventList::new(CSvcMsgGameEventList::decode(msg)?);
            }

            if msg_type == EBaseGameEvents::GeSource1LegacyGameEvent {
                let ge = GameEvent::new(&self.context.game_events, CSvcMsgGameEvent::decode(msg)?);
                try_observers!(self, BASE_GAME_EVENT, on_game_event(&self.context, &ge))?;
            }

            try_observers!(
                self,
                BASE_GAME_EVENT,
                on_base_game_event(&self.context, msg_type, msg)
            )?;
        }
        Ok(())
    }

    fn on_svc_message(&mut self, msg_type: SvcMessages, msg: &[u8]) -> Result<(), ParserError> {
        match msg_type {
            SvcMessages::SvcServerInfo => self.server_info(CSvcMsgServerInfo::decode(msg)?)?,
            SvcMessages::SvcCreateStringTable => {
                let msg = CSvcMsgCreateStringTable::decode(msg)?;
                self.create_string_table(msg)?
            }
            SvcMessages::SvcUpdateStringTable => {
                if self.anyone_interested(Interests::STRING_TABLE_STATE) {
                    let msg = CSvcMsgUpdateStringTable::decode(msg)?;
                    self.update_string_table(msg)?
                } else if self.anyone_interested(Interests::ENTITY_STATE) {
                    let msg = CSvcMsgUpdateStringTable::decode(msg)?;
                    if self.context.string_tables.tables[msg.table_id() as usize].name
                        == "instancebaseline"
                    {
                        self.update_string_table(msg)?
                    }
                }
            }
            SvcMessages::SvcPacketEntities => {
                if self.anyone_interested(Interests::ENTITY_STATE) {
                    self.packet_entities(CSvcMsgPacketEntities::decode(msg)?)?
                }
            }
            _ => {}
        }

        try_observers!(
            self,
            SVC_MESSAGE,
            on_svc_message(&self.context, msg_type, msg)
        )?;
        Ok(())
    }

    fn on_net_message(&mut self, msg_type: NetMessages, msg: &[u8]) -> Result<(), ParserError> {
        if msg_type == NetMessages::NetTick {
            self.context.net_tick = CNetMsgTick::decode(msg)?.tick();
        }

        try_observers!(
            self,
            NET_MESSAGE,
            on_net_message(&self.context, msg_type, msg)
        )?;
        Ok(())
    }

    fn on_tick_start(&mut self, msg_tick: u32) -> Result<(), ParserError> {
        if msg_tick > self.context.tick {
            self.on_tick_end()?;
        }

        self.context.previous_tick = self.context.tick;
        self.context.tick = msg_tick;

        if self.context.previous_tick == msg_tick {
            return Ok(());
        }

        try_observers!(self, TICK_START, on_tick_start(&self.context))?;
        Ok(())
    }

    fn on_tick_end(&mut self) -> Result<(), ParserError> {
        #[cfg(feature = "dota")]
        if self.anyone_interested(Interests::STRING_TABLE_STATE | Interests::COMBAT_LOG_ENTRIES) {
            if let Ok(names) = self.context.string_tables.get_by_name("CombatLogNames") {
                while let Some(log) = self.combat_log.pop_front() {
                    let entry = CombatLogEntry { names, log };
                    try_observers!(
                        self,
                        COMBAT_LOG_ENTRIES,
                        on_combat_log(&self.context, &entry)
                    )?;
                }
            }
        }

        try_observers!(self, TICK_END, on_tick_end(&self.context))?;
        Ok(())
    }

    fn on_stop(&mut self) -> Result<(), ParserError> {
        try_observers!(self, REPLAY_END, on_stop(&self.context))?;
        Ok(())
    }

    #[cfg(feature = "dota")]
    fn on_dota_user_message(
        &mut self,
        msg_type: EDotaUserMessages,
        msg: &[u8],
    ) -> Result<(), ParserError> {
        if self.anyone_interested(Interests::COMBAT_LOG_ENTRIES)
            && msg_type == EDotaUserMessages::DotaUmCombatLogDataHltv
        {
            let entry = CMsgDotaCombatLogEntry::decode(msg)?;
            self.combat_log.push_back(entry);
        }

        try_observers!(
            self,
            DOTA_USER_MESSAGE,
            on_dota_user_message(&self.context, msg_type, msg)
        )?;
        Ok(())
    }

    #[cfg(feature = "deadlock")]
    fn on_citadel_game_event(
        &mut self,
        msg_type: ECitadelGameEvents,
        msg: &[u8],
    ) -> Result<(), ParserError> {
        try_observers!(
            self,
            CITADEL_GAME_EVENT,
            on_citadel_game_event(&self.context, msg_type, msg)
        )?;
        Ok(())
    }

    #[cfg(feature = "deadlock")]
    fn on_citadel_user_message(
        &mut self,
        msg_type: CitadelUserMessageIds,
        msg: &[u8],
    ) -> Result<(), ParserError> {
        try_observers!(
            self,
            CITADEL_USER_MESSAGE,
            on_citadel_user_message(&self.context, msg_type, msg)
        )?;
        Ok(())
    }

    #[cfg(feature = "cs2")]
    fn on_cs2_user_message(
        &mut self,
        msg_type: ECstrike15UserMessages,
        msg: &[u8],
    ) -> Result<(), ParserError> {
        try_observers!(
            self,
            CS2_USER_MESSAGE,
            on_cs2_user_message(&self.context, msg_type, msg)
        )?;
        Ok(())
    }

    #[cfg(feature = "cs2")]
    fn on_cs2_game_event(
        &mut self,
        msg_type: ECsgoGameEvents,
        msg: &[u8],
    ) -> Result<(), ParserError> {
        try_observers!(
            self,
            CS2_GAME_EVENT,
            on_cs2_game_event(&self.context, msg_type, msg)
        )?;
        Ok(())
    }
}
