using System;
using System.Collections.Generic;
using OpenBveApi.Runtime;

namespace OpenbveFcmbTrainPlugin
{
    /// <summary>Represents a train that is simulated by this plugin.</summary>
    internal class Train
    {
        /// <summary>Represents the driver's handles.</summary>
        internal class DriverHandles
        {
            internal int Reverser;
            internal int PowerNotch;
            internal int BrakeNotch;
        }

        /// <summary>The train panel variables.</summary>
        internal int[] Panel;

        /// <summary>The specifications of the train.</summary>
        internal VehicleSpecs Specs { get; private set; }

        /// <summary>The current state of the train.</summary>
        internal VehicleState State { get; private set; }

        /// <summary>The current state of the train doors.</summary>
        internal DoorStates DoorState { get; private set; }

        /// <summary>The previous state of the train doors.</summary>
        internal DoorStates PreviousDoorState { get; private set; }

        /// <summary>The latest initialization mode of the train.</summary>
        internal InitializationModes InitializationMode { get; private set; }

        /// <summary>The devices equipped in the train.</summary>
        private List<Device> Devices;

        /// <summary>The physical handles of the train.</summary>
        internal DriverHandles PhysicalHandles { get; private set; }

        /// <summary>Whether the train's vigilance device is overridden.</summary>
        internal bool VigilanceOverride;

        /// <summary>Whether the train is using a continuous protection system instead of an intermittent protection system.</summary>
        internal bool ContinuousProtection;

        /// <summary>The service brake notch for ATC.</summary>
        internal int ServiceBrakeNotch { get; private set; }

        /// <summary>The current acceleration of the train.</summary>
        internal double Acceleration { get; private set; }

        /// <summary>The time when acceleration was calculated.</summary>
        private Time AccelerationTime = new Time(0);

        /// <summary>The speed when acceleration was calculated.</summary>
        private Speed AccelerationSpeed = new Speed(0);

        /// <summary>Creates a new train with the device configuration provided.</summary>
        /// <param name="panel">The array of panel variables.</pa
        internal Train(int[] panel)
        {
            Specs = new VehicleSpecs(0, BrakeTypes.ElectromagneticStraightAirBrake, 0, false, 0);
            State = new VehicleState(0.0, new Speed(0.0), 0.0, 0.0, 0.0, 0.0, 0.0);
            Panel = panel;
            PhysicalHandles = new DriverHandles();
            Devices = new List<Device>();
            Devices.Add(new Doors(false, 5, 15, 10));
            Devices.Add(new Deadman(5.0, false));
            Devices.Add(new TrainStop());
            Devices.Add(new AtcBombardier(30, 60, 20, 500));
        }

        /// <summary>Is called when the train should initialize.</summary>
        /// <param name="mode">The initialization mode.</param>
        internal void Initialize(InitializationModes mode)
        {
            InitializationMode = mode;
        }

        /// <summary>Is called after loading to inform the plugin about the specifications of the train.</summary>
        /// <param name="specs">The specifications of the train.</param>
        internal void SetVehicleSpecs(VehicleSpecs specs)
        {
            Specs = specs;

            // Set the service brake notch for ATC
            ServiceBrakeNotch = specs.BrakeNotches > 1 ? specs.BrakeNotches - 1 : 1;
        }

        /// <summary>Is called every frame.</summary>
        /// <param name="data">The data.</param>
        /// <param name="route">The route data.</param>
        internal void Elapse(ElapseData data, Route route)
        {
            State = data.Vehicle;

            // Reset data to be passed to the simulator
            data.DoorInterlockState = DoorInterlockStates.Unlocked;
            data.Handles.PowerNotch = data.Handles.PowerNotch;
            data.Handles.BrakeNotch = data.Handles.BrakeNotch;

            // Calculate acceleration
            if (OpenbveFcmbTrainPlugin.Initializing)
            {
                AccelerationTime = data.TotalTime;
            }
            if (data.TotalTime.Milliseconds - AccelerationTime.Milliseconds > 200)
            {
                Acceleration = Math.Round((State.Speed.MetersPerSecond - AccelerationSpeed.MetersPerSecond) / (data.TotalTime.Seconds - AccelerationTime.Seconds), 4);
                AccelerationTime = data.TotalTime;
                AccelerationSpeed = State.Speed;
            }

            // Retrieve data from all devices
            foreach (Device dev in Devices)
            {
                dev.Update(this, route, OpenbveFcmbTrainPlugin.Initializing, data.ElapsedTime);
                data.DoorInterlockState |= dev.RequestedDoorInterlock;
                if (dev.RequestedPowerNotch != -1) data.Handles.PowerNotch = dev.RequestedPowerNotch;
                if (dev.RequestedBrakeNotch != -1) data.Handles.BrakeNotch = dev.RequestedBrakeNotch;
            }
        }

        /// <summary>Is called when the driver changes the reverser.</summary>
        /// <param name="reverser">The new reverser position.</param>
        internal void SetReverser(int reverser)
        {
            PhysicalHandles.Reverser = reverser;
        }

        /// <summary>Is called when the driver changes the power notch.</summary>
        /// <param name="powerNotch">The new power notch.</param>
        internal void SetPower(int powerNotch)
        {
            PhysicalHandles.PowerNotch = powerNotch;
        }

        /// <summary>Is called when the driver changes the brake notch.</summary>
        /// <param name="brakeNotch">The new brake notch.</param>
        internal void SetBrake(int brakeNotch)
        {
            PhysicalHandles.BrakeNotch = brakeNotch;
        }

        /// <summary>Is called when a key is pressed.</summary>
        /// <param name="key">The key.</param>
        internal void KeyDown(VirtualKeys key)
        {
            foreach (Device dev in Devices)
            {
                dev.KeyChange(key, true, this);
            }
        }

        /// <summary>Is called when a key is released.</summary>
        /// <param name="key">The key.</param>
        internal void KeyUp(VirtualKeys key)
        {
            foreach (Device dev in Devices)
            {
                dev.KeyChange(key, false, this);
            }
        }

        /// <summary>Is called when the state of the doors changes.</summary>
        /// <param name="oldState">The old state of the doors.</param>
        /// <param name="newState">The new state of the doors.</param>
        internal void DoorChange(DoorStates oldState, DoorStates newState)
        {
            PreviousDoorState = oldState;
            DoorState = newState;
            foreach (Device dev in Devices)
            {
                dev.DoorChange(oldState, newState);
            }
        }

        /// <summary>Is called when a horn is played or when the music horn is stopped.</summary>
        /// <param name="type">The type of horn.</param>
        internal void HornBlow(HornTypes type) { }

        /// <summary>Is called to inform about signals.</summary>
        /// <param name="signal">The signal data.</param>
        internal void SetSignal(SignalData[] signal) { }

        /// <summary>Is called when a beacon is passed.</summary>
        /// <param name="beacon">The beacon data.</param>
        internal void SetBeacon(BeaconData beacon)
        {
            foreach (Device dev in Devices)
            {
                dev.SetBeacon(beacon);
            }
        }

        /// <summary>Is called when the plugin should perform the AI.</summary>
        /// <param name="data">The AI data.</param>
        /// <param name="route">The route data.</param>
        internal void PerformAI(AIData data, Route route)
        {
            foreach (Device dev in Devices)
            {
                dev.PerformAI(data, this, route);
            }
        }
    }
}