mod models;

use avam_protocol::SimConnectPacket;
use core::ops::ControlFlow;
use models::*;
use simconnect_sdk::{Notification, Object, SimConnect as SC, SystemEvent, SystemEventRequest};
use std::{sync::Arc, time::Duration};
use tokio::{
    sync::{
        broadcast::{Receiver, Sender},
        RwLock,
    },
    time::sleep,
};

use crate::state_machine::Event;

pub struct SimState {
    atc_id: String,
    // gps location
    // fuel state
    // cargo state
    // is landed
    // is stopped
    // etc
    // basically, all the data we wanna send to socket

    // we also need to know if we're in a flight here, and what flight,
    // mission parameters etc maybe
    // unless we just wanna keep that purely serverside..
    // and only process incoming packets if there is an activem mission.

    // the thing i _dont_ want, is to be invasive with alerts and notifications
    // that the user is "forgetting" to start a flight etc with AVAM running
    // basically, avam should be able to be running always without the user noticing
    // any interaction is done via the dashboard
    // starting, stopping, status etc

    // eventually even with "realistic" flightplans, from time of takeoff, estimate time of arrival,
    // and even have time-critical jobs that need to get finished before a set time
}

impl Default for SimState {
    fn default() -> Self {
        Self {
            atc_id: String::from("AVAM"),
        }
    }
}

pub struct SimConnect {
    simconnect_receiver: Receiver<SimConnectPacket>, // Data from the socket
    socket_sender: Sender<SimConnectPacket>,         // Data to the socket
    sender: Sender<Event>,
    receiver: Receiver<Event>,
    state: Arc<RwLock<SimState>>,
}

impl SimConnect {
    async fn open(&self, client: &mut SC) -> Result<(), anyhow::Error> {
        client.subscribe_to_system_event(SystemEventRequest::AircraftLoaded)?;
        client.subscribe_to_system_event(SystemEventRequest::SimStart)?;

        let id = client.register_object::<AtcID>()?;
        client.register_object::<Airplane>()?;
        client.register_object::<Fuel>()?;
        client.register_object::<Gps>()?;
        client.register_object::<OnGround>()?;
        client.register_object::<IsParked>()?;

        let value = self.state.read().await.atc_id.clone();
        tracing::info!("Updating ATC_ID: {}", &value);
        client.set_data_on_sim_object_with_id(id, &mut atc_id(&value)?)?;

        let _ = self.sender.send(Event::SimConnected);
        Ok(())
    }

    async fn object(&self, _: &mut SC, object: &Object) -> Result<(), anyhow::Error> {
        if let Ok(data) = Airplane::try_from(object) {
            self.socket_sender
                .send(SimConnectPacket::Airplane(avam_protocol::Airplane {
                    atc_type: data.atc_type,
                    atc_model: data.atc_model,
                    title: data.title,
                    category: data.category,
                }))?;
            // We've already got our data, there's no point in trying another in this iteration
            return Ok(());
        }

        if let Ok(data) = Fuel::try_from(object) {
            self.socket_sender
                .send(SimConnectPacket::Fuel(avam_protocol::Fuel {
                    weight: data.weight,
                    center_quantity: data.center_quantity,
                    center_capacity: data.center_capacity,
                    center2_quantity: data.center2_quantity,
                    center2_capacity: data.center2_capacity,
                    center3_quantity: data.center3_quantity,
                    center3_capacity: data.center3_capacity,
                    left_main_quantity: data.left_main_quantity,
                    left_main_capacity: data.left_main_capacity,
                    left_aux_quantity: data.left_aux_quantity,
                    left_aux_capacity: data.left_aux_capacity,
                    left_tip_quantity: data.left_tip_quantity,
                    left_tip_capacity: data.left_tip_capacity,
                    right_main_quantity: data.right_main_quantity,
                    right_main_capacity: data.right_main_capacity,
                    right_aux_quantity: data.right_aux_quantity,
                    right_aux_capacity: data.right_aux_capacity,
                    right_tip_quantity: data.right_tip_quantity,
                    right_tip_capacity: data.right_tip_capacity,
                    external1_quantity: data.external1_quantity,
                    external1_capacity: data.external1_capacity,
                    external2_quantity: data.external2_quantity,
                    external2_capacity: data.external2_capacity,
                }))?;
            // We've already got our data, there's no point in trying another in this iteration
            return Ok(());
        }

        if let Ok(data) = Gps::try_from(object) {
            self.socket_sender
                .send(SimConnectPacket::Gps(avam_protocol::Gps {
                    lat: data.lat,
                    lon: data.lon,
                    alt: data.alt,
                }))?;
            // We've already got our data, there's no point in trying another in this iteration
            return Ok(());
        }

        if let Ok(data) = OnGround::try_from(object) {
            self.socket_sender
                .send(SimConnectPacket::OnGround(avam_protocol::OnGround {
                    sim_on_ground: data.sim_on_ground,
                }))?;
            // We've already got our data, there's no point in trying another in this iteration
            return Ok(());
        }

        // Change to parking break state or something, unless we're a helicopter, then we dont have one
        if let Ok(data) = IsParked::try_from(object) {
            self.socket_sender
                .send(SimConnectPacket::IsParked(avam_protocol::IsParked {
                    is_parked: data.is_parked,
                }))?;
            // We've already got our data, there's no point in trying another in this iteration
            return Ok(());
        }

        Ok(())
    }

    async fn system_event(
        &self,
        client: &mut SC,
        event: &SystemEvent,
    ) -> Result<(), anyhow::Error> {
        match event {
            SystemEvent::AircraftLoaded { .. } => {
                tracing::info!("Aircraft Loaded!");
                let id = client.get_object_id::<AtcID>()?;

                let value = self.state.read().await.atc_id.clone();
                tracing::info!("Updating ATC_ID: {}", &value);
                for _ in 1..=10 {
                    client.set_data_on_sim_object_with_id(id, &mut atc_id(&value)?)?;
                    sleep(Duration::from_millis(50)).await;
                }
            }
            SystemEvent::SimStart => {
                tracing::info!("Sim Start");
            }

            // SystemEvent::OneSecond => todo!(),
            // SystemEvent::FourSeconds => todo!(),
            // SystemEvent::SixTimesPerSecond => todo!(),
            // SystemEvent::AircraftLoaded { file_name } => todo!(),
            // SystemEvent::Crashed => todo!(),
            // SystemEvent::CrashReset => todo!(),
            // SystemEvent::FlightSaved { file_name } => todo!(),
            // SystemEvent::FlightPlanActivated { file_name } => todo!(),
            // SystemEvent::FlightPlanDeactivated => todo!(),
            // SystemEvent::Frame {
            //     frame_rate,
            //     sim_speed,
            // } => todo!(),
            // SystemEvent::Pause { state } => todo!(),
            // SystemEvent::Paused => todo!(),
            // SystemEvent::PauseFrame {
            //     frame_rate,
            //     sim_speed,
            // } => todo!(),
            // SystemEvent::PositionChanged => todo!(),
            // SystemEvent::Sim { state } => todo!(),
            // SystemEvent::SimStart => todo!(),
            // SystemEvent::SimStop => todo!(),
            // SystemEvent::Sound { state } => todo!(),
            // SystemEvent::Unpaused => todo!(),
            // SystemEvent::View { view } => todo!(),
            _ => todo!(),
        }

        Ok(())
    }
}

impl SimConnect {
    pub fn new(
        simconnect_receiver: Receiver<SimConnectPacket>,
        socket_sender: Sender<SimConnectPacket>,
        sender: Sender<Event>,
        receiver: Receiver<Event>,
    ) -> Self {
        Self {
            simconnect_receiver,
            socket_sender,
            sender,
            receiver,
            state: Default::default(),
        }
    }

    pub async fn run(&self) -> Result<(), anyhow::Error> {
        let mut receiver = self.receiver.resubscribe();
        let mut simconnect_receiver = self.simconnect_receiver.resubscribe();
        let state = self.state.clone();
        let mut sc: Option<SC> = None;
        loop {
            if let Ok(Event::Quit) = receiver.try_recv() {
                break;
            }

            if let Ok(packet) = simconnect_receiver.try_recv() {
                match packet {
                    SimConnectPacket::AtcID(value) => {
                        state.write().await.atc_id = value.clone();
                        if let Some(client) = sc.as_mut() {
                            if let Ok(id) = client.get_object_id::<AtcID>() {
                                tracing::info!("Updating ATC_ID: {}", &value);
                                client.set_data_on_sim_object_with_id(id, &mut atc_id(&value)?)?;
                            }
                        }
                    }
                    SimConnectPacket::Fuel(_) => unreachable!(), // Outgoing packet, not handled here (yet) we probably need to set fuel state somehow
                    SimConnectPacket::Airplane(_) => unreachable!(),
                    SimConnectPacket::Gps(_) => unreachable!(), // Outgoing packet, not handled here (yet) we probably need to set position somehow
                    SimConnectPacket::OnGround(_) => unreachable!(), // Outgoing packet, not handled here
                    SimConnectPacket::IsParked(_) => unreachable!(), // Outgoing packet, not handled here
                }
            }

            if sc.is_none() {
                if let Ok(simconnect) = SC::new("Avii's Virtual Airline Manager") {
                    sc = Some(simconnect);
                    tracing::info!("Connected");
                }
            }

            if let Some(client) = sc.as_mut() {
                if self.handle(client).await.is_break() {
                    tracing::info!("Disconnected");
                    sc = None;
                }
            }

            sleep(Duration::from_millis(100)).await;
        }

        Ok(())
    }

    async fn handle(&self, client: &mut SC) -> ControlFlow<(), ()> {
        if let Ok(Some(event)) = client.get_next_dispatch() {
            if let Err(e) = match event {
                Notification::Open => self.open(client).await,
                Notification::Object(ref object) => self.object(client, object).await,
                Notification::SystemEvent(ref system_event) => {
                    self.system_event(client, system_event).await
                }
                // Notification::ClientEvent(ref client_event) => todo!(),
                // Notification::AirportList(ref vec) => todo!(),
                // Notification::WaypointList(ref vec) => todo!(),
                // Notification::NdbList(ref vec) => todo!(),
                // Notification::VorList(ref vec) => todo!(),
                Notification::Quit => {
                    let _ = self.sender.send(Event::SimDisconnected);
                    return ControlFlow::Break(());
                }
                _ => todo!(),
            } {
                tracing::error!("Error handling SimConnect::Notification: {:#?}", e);
            };
        }
        ControlFlow::Continue(())
    }
}

pub async fn start(
    simconnect_receiver: Receiver<SimConnectPacket>,
    socket_sender: Sender<SimConnectPacket>,
    sender: Sender<Event>,
    receiver: Receiver<Event>,
) -> Result<(), anyhow::Error> {
    SimConnect::new(simconnect_receiver, socket_sender, sender, receiver)
        .run()
        .await?;

    Ok(())
}
