use std::borrow::Cow;

use lotus_script::prelude::{message_type, send_message, MessageTarget};
use serde::{Deserialize, Serialize};

use lotus_script::time::delta;

//===================================================================
// Vehicle number
//===================================================================

///This is/should be sent via broadcast to the own vehicle and all modules when sending. There is no
/// transmission by bus via the coupler.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct VehNumber<'a> {
    pub value: Cow<'a, str>,
}

message_type!(VehNumber<'_>, "Std", "Vehiclenumber");

impl<'a> VehNumber<'a> {
    pub fn new(value: &'a str) -> Self {
        Self {
            value: Cow::Borrowed(value),
        }
    }

    pub fn send(&self) {
        send_message(
            &(super::std::VehNumber::new(&self.value)),
            [MessageTarget::Broadcast {
                across_couplings: false,
                include_self: true,
            }],
        );
    }
}

//===================================================================
// Batterymainswitch & Batteryvoltage
//===================================================================

///We have decided to merge these two pieces of information. Rust offers the option of variants of
/// an enum carrying a data payload. We want to use this to transfer both pieces of information together.
/// As you can see in the code snippet for enum Batteryvoltage, a float number is also transmitted
/// with the `On` state. This represents the normalised battery voltage (i.e. 0.0 = no voltage, 1.0 = default voltage).
/// In the `Off` state, no voltage needs to be transmitted as the battery is disconnected.
///
///This message replaces the previous `BATTERYSWITCH` (integer) and `VOLTAGE` (single).
///
/// As with the car number, this information is only sent to all recipients in a single vehicle.
///
/// Another important thing to note is that Battery = Off is the default for all objects.
/// If a vehicle is spawned with the battery switched on at the start, this information must be sent once initially.
#[derive(Default, Debug, Copy, Clone, Serialize, Deserialize)]
pub enum Batteryvoltage {
    On(f32), // Battery is on, with voltage as f32
    #[default]
    Off, // Battery is off, no voltage needed
}

impl Batteryvoltage {
    // Custom PartialEq to only compare state type, not inner values
    pub fn on_off_equal(&self, other: &Self) -> bool {
        matches!(
            (self, other),
            (Batteryvoltage::On(_), Batteryvoltage::On(_))
                | (Batteryvoltage::Off, Batteryvoltage::Off)
        )
    }

    pub fn is_on(&self) -> bool {
        match self {
            Batteryvoltage::On(_) => true,
            Batteryvoltage::Off => false,
        }
    }

    pub fn is_off(&self) -> bool {
        !self.is_on()
    }
}

//---------------------------

message_type!(Batteryvoltage, "Std", "Batteryvoltage");

#[derive(Debug)]
pub struct BatteryvoltageSender {
    targets: Vec<MessageTarget>,
    state_last: bool,
    value_last: f32,
}

impl BatteryvoltageSender {
    pub fn new(targets: impl IntoIterator<Item = MessageTarget>) -> Self {
        Self {
            targets: targets.into_iter().collect::<Vec<_>>(),
            state_last: false,
            value_last: 0.0,
        }
    }

    // This function only sends updates if the values have changed
    pub fn send(&mut self, state: bool, value: f32) {
        // Sends message if state or value changed significantly
        if (value - self.value_last).abs() > 0.01 || self.state_last != state {
            send_message(
                &(match state {
                    true => super::std::Batteryvoltage::On(value),
                    false => super::std::Batteryvoltage::Off,
                }),
                self.targets.clone(),
            );
            self.value_last = value;
            self.state_last = state;
        }
    }
}

impl Default for BatteryvoltageSender {
    fn default() -> Self {
        BatteryvoltageSender::new(vec![MessageTarget::Broadcast {
            across_couplings: false,
            include_self: true,
        }])
    }
}

//===================================================================
// Power Signal
//===================================================================

#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum PowerSignalCabin {
    A,  // Cabin A (Index = 0)
    B,  // Cabin B (Index = 1)
    No, // A other Cab (not in this Car)
}

impl From<i32> for PowerSignalCabin {
    fn from(val: i32) -> Self {
        match val {
            0 => PowerSignalCabin::A,
            1 => PowerSignalCabin::B,
            _ => PowerSignalCabin::No,
        }
    }
}

///This message will replace and extend the old message `MAINSWITCH` (integer).
/// We want to extend the information content as follows.
/// Firstly, for each switch-on/switch-off message there is information
/// on where the status has just been changed. For example, central on-board
/// computers that are technically included in an IBIS terminal can also
/// switch on and off correctly. The values `ACab` and `BCab` indicate
/// whether one of the two driver's cabs has been activated in the respective vehicle.
/// With the `NoCab` status, the modules in the attached wagons also receive
/// the information that a driver's cab has now been activated in the train set.
/// The other change is a Boolean parameter in the switch-on message. This
/// can be used to transmit the ‘Quickstart’ command (value = true).
/// As a result, modules should skip the boot process and run directly in steady operation.
/// One of the advantages of this is that you don't have to wait for
/// long start procedures when debugging the module and it also ensures that the module is
/// directly available in the active driver's cab if the
/// ‘Place vehicle’ start option ‘Ready to drive’ is selected. Previously, you had to cheat a little here.
///
/// The included information about the driver's cab means that this message is also sent
/// directly to all modules (and the driver's own vehicle) via broadcast.
/// The possibility of modules knowing in future which driver's cab they belong to means
/// that they can react appropriately. The message is also NOT transmitted via the coupling
/// and must be generated independently by each vehicle for its modules.
/// Here, the default status is switched off for all objects, too.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum PowerSignalState {
    On {
        // Cabin is activated
        // quickstart: true = do quickstart, false = do regular start
        // For cabin_id see PowerSignalCabin
        quickstart: bool,
        cabin_id: PowerSignalCabin,
    },
    Off {
        // Cabin is deactivated
        // For cabin_id see PowerSignalCabin
        cabin_id: PowerSignalCabin,
    },
}

impl PowerSignalState {
    pub fn is_on(&self) -> bool {
        match self {
            PowerSignalState::On {
                quickstart: _,
                cabin_id: _,
            } => true,
            PowerSignalState::Off { cabin_id: _ } => false,
        }
    }

    pub fn is_off(&self) -> bool {
        !self.is_on()
    }
}

impl Default for PowerSignalState {
    fn default() -> Self {
        PowerSignalState::Off {
            cabin_id: PowerSignalCabin::No,
        }
    }
}

//---------------------------

message_type!(PowerSignalState, "Std", "PowerSignal");

#[derive(Debug)]
pub struct PowerSignalSender {
    targets: Vec<MessageTarget>,
    state_last: super::std::PowerSignalState,
}

impl PowerSignalSender {
    pub fn new(targets: impl IntoIterator<Item = MessageTarget>) -> Self {
        Self {
            targets: targets.into_iter().collect::<Vec<_>>(),
            state_last: super::std::PowerSignalState::default(),
        }
    }

    // Send quickstart for cabin_id, only if cabin_activ
    pub fn quickstart(&mut self, cabin_activ: bool, cabin_id: super::std::PowerSignalCabin) {
        if cabin_activ {
            send_message(
                &(super::std::PowerSignalState::On {
                    quickstart: true,
                    cabin_id,
                }),
                self.targets.clone(),
            );
            self.state_last = super::std::PowerSignalState::On {
                quickstart: true,
                cabin_id,
            };
        }
    }

    // Sends state to cabin_id
    // This function only sends updates if the values have changed
    pub fn send(&mut self, state: bool, cabin_id: super::std::PowerSignalCabin) {
        let value = match state {
            true => super::std::PowerSignalState::On {
                quickstart: false,
                cabin_id,
            },
            false => super::std::PowerSignalState::Off { cabin_id },
        };

        let equal = match (value, self.state_last) {
            (
                super::std::PowerSignalState::On {
                    quickstart: _,
                    cabin_id: cab1,
                },
                super::std::PowerSignalState::On {
                    quickstart: _,
                    cabin_id: cab2,
                },
            ) => cab1 == cab2,
            (
                super::std::PowerSignalState::Off { cabin_id: cab1 },
                super::std::PowerSignalState::Off { cabin_id: cab2 },
            ) => cab1 == cab2,
            _ => false,
        };

        if !equal {
            self.state_last = value;
            send_message(&(value), self.targets.clone());
        }
    }
}

impl Default for PowerSignalSender {
    fn default() -> Self {
        PowerSignalSender::new(vec![MessageTarget::Broadcast {
            across_couplings: false,
            include_self: true,
        }])
    }
}

//---------------------------

pub fn send_mainswitch(
    value: super::std::PowerSignalState,
    targets: impl IntoIterator<Item = MessageTarget>,
) {
    send_message(&(value), targets);
}

//===================================================================
// Light
//===================================================================

///Here again we have less to suggest. We are replacing the previous broadcast `LIGHT` (single).
/// This is now a value of type f32.
/// The value range should be the normalised brightness, i.e. 0.0 = light off & 1.0 = default brightness.
///
/// In principle, all modules should receive this information. A deviation is also possible if different
/// modules are connected to different lighting circuits, for example.
/// The message is explicitly NOT transmitted via the coupling and must be sent in/for each vehicle in the train itself.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Light {
    pub value: f32,
}

message_type!(Light, "Std", "Light");

#[derive(Debug)]
pub struct LightSender {
    targets: Vec<MessageTarget>,
    value_last: f32,
}

impl LightSender {
    pub fn new(targets: impl IntoIterator<Item = MessageTarget>) -> Self {
        Self {
            targets: targets.into_iter().collect::<Vec<_>>(),
            value_last: 0.0,
        }
    }

    // Sends value, if changed significantly, to all defined targets
    pub fn send(&mut self, value: f32) {
        if (value - self.value_last).abs() > 0.01 {
            send_message(&(super::std::Light { value }), self.targets.clone());
            self.value_last = value;
        }
    }
}

impl Default for LightSender {
    fn default() -> Self {
        LightSender::new(vec![MessageTarget::Broadcast {
            across_couplings: false,
            include_self: true,
        }])
    }
}

//===================================================================
// Stop Request
//===================================================================

///The broadcast `STOPREQUEST` (single) was previously used here. In our proposal, the function is retained,
/// only the data type is changed to Boolean.
///
/// As with the previous messages, this is NOT transmitted via the coupler.
/// However, it should otherwise reach all modules in the vehicle.
/// If there are several stop request circuits in a vehicle (e.g. a bus in the front vehicle and in the rear vehicle),
/// messages can of course be sent with different receivers.
/// Vehicles without a stop request (e.g. historic trains or underground trains) do not have to provide this information.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct StopRequest {
    pub value: bool,
}

message_type!(StopRequest, "Std", "StopRequest");

#[derive(Debug)]
pub struct StopRequestSender {
    targets: Vec<MessageTarget>,
    value_last: bool,
}

impl StopRequestSender {
    pub fn new(targets: impl IntoIterator<Item = MessageTarget>) -> Self {
        Self {
            targets: targets.into_iter().collect::<Vec<_>>(),
            value_last: false,
        }
    }

    // Sends value to all moduls defined targets
    // This function only sends updates if the values have changed
    pub fn send(&mut self, value: bool) {
        if value != self.value_last {
            send_message(&(super::std::StopRequest { value }), self.targets.clone());
            self.value_last = value;
        }
    }
}

impl Default for StopRequestSender {
    fn default() -> Self {
        StopRequestSender::new(vec![MessageTarget::Broadcast {
            across_couplings: false,
            include_self: true,
        }])
    }
}

//===================================================================
// Velocity
//===================================================================

///We have been thinking about this topic for a while.
/// We are taking up the idea of the message `VELOCITY` (single), which has so far only reached
/// the on-board computer. We also considered the idea of using the distance travelled
/// or a fixed distance pulse instead of the vehicle's speed. However, we abandoned these ideas,
/// as speed is simply the most versatile and also provides the most information.
///
/// The value is sent in metres per second. The +/- sign represents the direction of travel.
/// This message is also NOT sent via the coupling, but must be sent in every vehicle.
/// It is important here that different modules also receive different speeds
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Velocity {
    pub value: f32,
}

message_type!(Velocity, "Std", "Velocity");

#[derive(Debug)]
pub struct VelocitySender {
    targets: Vec<MessageTarget>,
    timer: f32,
}

impl VelocitySender {
    pub fn new(targets: impl IntoIterator<Item = MessageTarget>) -> Self {
        Self {
            targets: targets.into_iter().collect::<Vec<_>>(),
            timer: 0.0,
        }
    }

    // Sends value every 0.3 seconds to the defined target
    // Call this function every tick!
    pub fn tick(&mut self, value: f32) {
        self.timer -= delta();
        if self.timer < 0.0 {
            send_message(&(super::std::Velocity { value }), self.targets.clone());
            self.timer += 0.3;
        }
    }
}

impl Default for VelocitySender {
    fn default() -> Self {
        VelocitySender::new(vec![MessageTarget::Broadcast {
            across_couplings: false,
            include_self: true,
        }])
    }
}

//===================================================================
// Door State
//===================================================================

#[derive(Debug, Default, Copy, Clone, Serialize, Deserialize, PartialEq)]
pub enum DoorSide {
    #[default]
    None, // No side set
    Right, // Doors on the right side are targeted
    Left,  // Doors on the left side are targeted
    Both,  // Doors on the both sides are targeted
}

///Now, we come to the door status information. Two different messages were used here in the previous Lotus.
///One is `ATBUSSTOP` (integer) and the other is `DOORSOPEN` (integer). We now want to merge these.
/// This will no longer only contain the information ‘door release’ yes/no,
/// but also the status of whether the doors have generally been opened and on which side the doors are released/opened.
///
/// As with the other messages before, this information is NOT transmitted via the coupling
/// so that the door side is not twisted in the case of asymmetrically coupled vehicles.
/// Of course, there can also be several circuits here, e.g. in buses for the front end and the rear end.
/// It is only important that all modules receive this information once.
#[derive(Debug, Default, Copy, Clone, Serialize, Deserialize, PartialEq)]
pub enum DoorControlTarget {
    #[default]
    Closed, // Doors are not released or opened by the door control unit. Emergency releases etc. are not taken into account here.
    Released(DoorSide), // Doors are released by the door control unit.
    Open(DoorSide),     // Doors are forced open by the door control unit.
}

//---------------------------

message_type!(DoorControlTarget, "Std", "DoorState");

pub struct DoorStateSender {
    targets: Vec<MessageTarget>,
    value_last: super::std::DoorControlTarget,
}

impl DoorStateSender {
    pub fn new(targets: impl IntoIterator<Item = MessageTarget>) -> Self {
        Self {
            targets: targets.into_iter().collect::<Vec<_>>(),
            value_last: super::std::DoorControlTarget::default(),
        }
    }

    // Sends value to all moduls defined targets
    // This function only sends updates if the values have changed
    pub fn send(&mut self, value: super::std::DoorControlTarget) {
        if value != self.value_last {
            send_message(&(value), self.targets.clone());
            self.value_last = value;
        }
    }
}

impl Default for DoorStateSender {
    fn default() -> Self {
        DoorStateSender::new(vec![MessageTarget::Broadcast {
            across_couplings: false,
            include_self: true,
        }])
    }
}

//---------------------------

pub fn send_door_control_msg(
    value: super::std::DoorControlTarget,
    targets: impl IntoIterator<Item = MessageTarget>,
) {
    send_message(&(value), targets);
}

//===================================================================
// Power Hold
//===================================================================

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct DisplayPowerHold {
    pub value: bool,
}

message_type!(DisplayPowerHold, "Std", "DisplayPowerHold", "ModulParams");

#[derive(Debug)]
pub struct DisplayPowerHoldSender {
    value_last: bool,
}

impl DisplayPowerHoldSender {
    pub fn send(&mut self, value: bool) {
        if value != self.value_last {
            send_message(
                &(super::std::DisplayPowerHold { value }),
                [MessageTarget::Broadcast {
                    across_couplings: true,
                    include_self: false,
                }],
            );
            self.value_last = value;
        }
    }
}
