using Microscopic_Traffic_Simulator.ViewModels.Messages;
using Microscopic_Traffic_Simulator___Model.CellularTopologyObjects;
using Microscopic_Traffic_Simulator___Model.CellularTopologyObjects.GeneralParameters;
using Microscopic_Traffic_Simulator___Model.GeometricObjects;
using Microscopic_Traffic_Simulator___Model.SimulationControl;
using System;
using System.Windows.Input;

namespace Microscopic_Traffic_Simulator.ViewModels
{
    /// <summary>
    /// Viewmodel for simulation control.
    /// </summary>
    class SimulationControlViewModel : ViewModelBase
    {
        /// <summary>
        /// Class for sending message with built cellular topology.
        /// </summary>
        internal class CellularTopologyMessage : ParameterizedMessage<CellularTopology> { }

        /// <summary>
        /// Class for sending message about switching top panel to construction panel.
        /// </summary>
        internal class SwitchToConstructionModeMessage : Message { }

        /// <summary>
        /// Reference to interactions with user.
        /// </summary>
        private IInteractions interactions;

        /// <summary>
        /// Reference to user settings.
        /// </summary>
        private ISettings settings;

        /// <summary>
        /// Current cellular topology.
        /// </summary>
        private CellularTopology cellularTopology;
        /// <summary>
        /// Current cellular topology.
        /// </summary>
        internal CellularTopology CellularTopology { get { return cellularTopology; } }

        /// <summary>
        /// Simulation speed.
        /// </summary>        
        public double SimulationSpeed
        {
            get { return cellularTopology.Simulation.SimulationSpeed; }
            private set
            {
                cellularTopology.Simulation.SimulationSpeed = value;
            }
        }

        /// <summary>
        /// Seed for generator of random numbers.
        /// </summary>
        public int Seed { get { return cellularTopology.Simulation.UsedSeed; } }

        /// <summary>
        /// Seed for generator of random numbers to be set.
        /// </summary>        
        public int CustomSeed { get; set; }

        /// <summary>
        /// Determines whether there is previous seed available from any previous simulation.
        /// </summary>
        public bool IsPreviousSeedAvailable
        {
            get { return !settings.PreviousSeedNull; }
            set
            {
                settings.PreviousSeedNull = !value;
                if (settings.PreviousSeedNull)
                {
                    IsPreviousSeedUsed = false;
                }
                OnPropertyChanged("IsPreviousSeedAvailable");
            }
        }

        /// <summary>
        /// Determines whether to use the last used seed in previous simulation.
        /// </summary>
        public bool IsPreviousSeedUsed
        {
            get { return settings.IsPreviousSeedUsed; }
            set
            {
                settings.IsPreviousSeedUsed = value;
                settings.Save();
                OnPropertyChanged("IsPreviousSeedUsed");
            }
        }

        /// <summary>
        /// Determines whether to use custom seed.
        /// </summary>
        public bool IsSeedSettingAutomatic
        {
            get { return settings.IsSeedSettingAutomatic; }
            set
            {
                settings.IsSeedSettingAutomatic = value;
                settings.Save();
            }
        }

        /// <summary>
        /// Number of simulation steps
        /// </summary>
        public ulong SimulationSteps
        {
            get { return cellularTopology.SimulationSteps; }
        }

        /// <summary>
        /// The number of simulation steps when the simulation control will pause the simulation.
        /// </summary>
        public ulong SimulationStepsToPause
        {
            get { return cellularTopology.SimulationStepsToPause; }
            set
            {
                cellularTopology.SimulationStepsToPause = value;
                OnPropertyChanged("SimulationStepsToPause");
            }
        }

        /// <summary>
        /// The number of simulation steps to pause the simulation by simulation control to be set.
        /// </summary>
        public ulong SimulationStepsToPauseToSet { get; set; }

        /// <summary>
        /// Simulation model time.
        /// </summary>
        public TimeSpan ModelTime
        {
            get { return cellularTopology.Simulation.CurrentModelTime; }
        }

        /// <summary>
        /// Simulation model time when the simulation will be paused.
        /// </summary>
        public TimeSpan ModelTimeToPause
        {
            get { return cellularTopology.Simulation.ModelTimeToPause; }
            set
            {
                cellularTopology.Simulation.ModelTimeToPause = value;
                OnPropertyChanged("ModelTimeToPause");
            }
        }

        /// <summary>
        /// Simulation model time to pause to be set when command for setting the model time to 
        /// pause is executed.
        /// </summary>
        public TimeSpan ModelTimeToPauseToSet { get; set; }

        /// <summary>
        /// Custom simulation speed to set
        /// </summary>
        private double customSimulationSpeed;
        /// <summary>
        /// Custom simulation speed to set
        /// </summary>
        public double CustomSimulationSpeed
        {
            get { return customSimulationSpeed; }
            set { customSimulationSpeed = value; OnPropertyChanged("CustomSimulationSpeed"); }
        }

        /// <summary>
        /// Determine if simulation is in non-running status.
        /// </summary>        
        private bool IsNotRunning
        {
            get { return cellularTopology.Simulation.SimulationState != SimulationState.Running; }
        }

        /// <summary>
        /// Determine if simulation can be paused.
        /// </summary>
        private bool CanPause
        {
            get { return cellularTopology.Simulation.SimulationState == SimulationState.Running; }
        }

        /// <summary>
        /// Determine if simulation can be stopped.
        /// </summary>        
        private bool SimulationStarted
        {
            get
            {
                return CanPause ||
                    cellularTopology.Simulation.SimulationState == SimulationState.Paused;
            }
        }

        /// <summary>
        /// Determine whether the maximum simulation speed is set by user.
        /// </summary>
        private bool isMaxSimulationSpeed;
        /// <summary>
        /// Determine whether the maximum simulation speed is set by user.
        /// </summary>        
        public bool IsMaxSimulationSpeed
        {
            get { return isMaxSimulationSpeed; }
            set
            {
                if (isMaxSimulationSpeed != value)
                {
                    isMaxSimulationSpeed = value;
                    cellularTopology.Simulation.IsMaxSimulationSpeed = isMaxSimulationSpeed;
                    OnPropertyChanged("IsNotMaxSimulationSpeed");
                }
            }
        }
        /// <summary>
        /// Negation of isMaxSimulationSpeed
        /// </summary>
        public bool IsNotMaxSimulationSpeed { get { return !isMaxSimulationSpeed; } }

        /// <summary>
        /// Command to switch to construction mode.
        /// </summary>
        private ICommand switchToConstructionModeCommand;
        /// <summary>
        /// Command to switch to construction mode.
        /// </summary>
        public ICommand SwitchToConstructionModeCommand
        {
            get
            {
                if (switchToConstructionModeCommand == null)
                {
                    switchToConstructionModeCommand = new ObservableRelayCommand(
                        i => SwitchToConstructionMode());
                }
                return switchToConstructionModeCommand;
            }
        }

        /// <summary>
        /// Command to run simulation.
        /// </summary>
        private ICommand forwardCommand;
        /// <summary>
        /// Command to run simulation.
        /// </summary>
        public ICommand ForwardCommand
        {
            get
            {
                if (forwardCommand == null)
                {
                    forwardCommand = new ObservableRelayCommand(i => Run(), i => IsNotRunning);
                }
                return forwardCommand;
            }
        }

        /// <summary>
        /// Command to perform one step in simulation.
        /// </summary>
        private ICommand forwardStepCommand;
        /// <summary>
        /// Command to perform one step in simulation.
        /// </summary>
        public ICommand ForwardStepCommand
        {
            get
            {
                if (forwardStepCommand == null)
                {
                    forwardStepCommand = new ObservableRelayCommand(i => Step(), i => IsNotRunning);
                }
                return forwardStepCommand;
            }
        }

        /// <summary>
        /// Command to stop simulation.
        /// </summary>
        private ICommand stopCommand;
        /// <summary>
        /// Command to stop simulation.
        /// </summary>
        public ICommand StopCommand
        {
            get
            {
                if (stopCommand == null)
                {
                    stopCommand = new ObservableRelayCommand(i => Stop(), i => SimulationStarted);
                }
                return stopCommand;
            }
        }

        /// <summary>
        /// Command to pause simulation.
        /// </summary>
        private ICommand pauseCommand;
        /// <summary>
        /// Command to pause simulation.
        /// </summary>
        public ICommand PauseCommand
        {
            get
            {
                if (pauseCommand == null)
                {
                    pauseCommand = new ObservableRelayCommand(i => Pause(), i => CanPause);
                }
                return pauseCommand;
            }
        }

        /// <summary>
        /// Command to inrease speed of the simulation.
        /// </summary>
        private ICommand fasterCommand;
        /// <summary>
        /// Command to inrease speed of the simulation.
        /// </summary>
        public ICommand FasterCommand
        {
            get
            {
                if (fasterCommand == null)
                {
                    fasterCommand = new ObservableRelayCommand(i =>
                        {
                            SimulationSpeed *= 2.0;
                            OnPropertyChanged("SimulationSpeed");
                            CustomSimulationSpeed = cellularTopology.Simulation.SimulationSpeed;
                        }, i => IsNotMaxSimulationSpeed);
                }
                return fasterCommand;
            }
        }

        /// <summary>
        /// Command to inrease speed of the simulation.
        /// </summary>
        private ICommand slowerCommand;
        /// <summary>
        /// Command to inrease speed of the simulation.
        /// </summary>
        public ICommand SlowerCommand
        {
            get
            {
                if (slowerCommand == null)
                {
                    slowerCommand = new ObservableRelayCommand(i =>
                        {
                            SimulationSpeed /= 2.0;
                            OnPropertyChanged("SimulationSpeed");
                            CustomSimulationSpeed = cellularTopology.Simulation.SimulationSpeed;
                        }, i => IsNotMaxSimulationSpeed);
                }
                return slowerCommand;
            }
        }

        /// <summary>
        /// Command to setting custom simulation speed.
        /// </summary>
        private ICommand customSimulationSpeedCommand;
        /// <summary>
        /// Command to setting custom simulation speed.
        /// </summary>        
        public ICommand CustomSimulationSpeedCommand
        {
            get
            {
                if (customSimulationSpeedCommand == null)
                {
                    customSimulationSpeedCommand = new ObservableRelayCommand(i =>
                    {
                        SimulationSpeed = customSimulationSpeed;
                        OnPropertyChanged("SimulationSpeed");
                    }, i => IsNotMaxSimulationSpeed);
                }
                return customSimulationSpeedCommand;
            }
        }

        /// <summary>
        /// Command for restarting simulation.
        /// </summary>
        private ICommand restartCommand;
        /// <summary>
        /// Command for restarting simulation.
        /// </summary>        
        public ICommand RestartCommand
        {
            get
            {
                if (restartCommand == null)
                {
                    restartCommand = new ObservableRelayCommand(i =>
                    {
                        Stop();
                        Run();
                    }, i => SimulationStarted);
                }
                return restartCommand;
            }
        }

        /// <summary>
        /// Command for setting model time alarm.
        /// </summary>
        private ICommand applyTimeAlarmCommand;
        /// <summary>
        /// Command for setting model time alarm.
        /// </summary>        
        public ICommand ApplyTimeAlarmCommand
        {
            get
            {
                if (applyTimeAlarmCommand == null)
                {
                    applyTimeAlarmCommand = new RelayCommand(
                        i => ModelTimeToPause = ModelTimeToPauseToSet);
                }
                return applyTimeAlarmCommand;
            }
        }

        /// <summary>
        /// Command for setting simulation steps alarm.
        /// </summary>
        private ICommand applySimulationStepsAlarmCommand;
        /// <summary>
        /// Command for setting simulation steps alarm.
        /// </summary>        
        public ICommand ApplySimulationStepsAlarmCommand
        {
            get
            {
                if (applySimulationStepsAlarmCommand == null)
                {
                    applySimulationStepsAlarmCommand = new RelayCommand(
                        i => SimulationStepsToPause = SimulationStepsToPauseToSet);
                }
                return applySimulationStepsAlarmCommand;
            }
        }

        /// <summary>
        /// Command for resetting model time alarm.
        /// </summary>
        private ICommand resetModelTimeAlarmCommand;
        /// <summary>
        /// Command for resetting model time alarm.
        /// </summary>        
        public ICommand ResetModelTimeAlarmCommand
        {
            get
            {
                if (resetModelTimeAlarmCommand == null)
                {
                    resetModelTimeAlarmCommand = new RelayCommand(i => ModelTimeToPause = TimeSpan.MaxValue);
                }
                return resetModelTimeAlarmCommand;
            }
        }

        /// <summary>
        /// Command for resetting simulation steps alarm.
        /// </summary>
        private ICommand resetSimulationStepsAlarmCommand;
        /// <summary>
        /// Command for resetting simulation steps alarm.
        /// </summary>        
        public ICommand ResetSimulationStepsAlarmCommand
        {
            get
            {
                if (resetSimulationStepsAlarmCommand == null)
                {
                    resetSimulationStepsAlarmCommand = new RelayCommand(i => SimulationStepsToPause = ulong.MaxValue);
                }
                return resetSimulationStepsAlarmCommand;
            }
        }

        /// <summary>
        /// Creates simulation control viewmodel.
        /// </summary>
        /// <param name="messenger">Messenger to communicate with other viewmodels.</param>
        internal SimulationControlViewModel(Messenger messenger, IInteractions interactions, ISettings settings)
        {
            this.messenger = messenger;
            this.interactions = interactions;
            this.settings = settings;
            messenger.GetEvent<ConstructionViewModel.BuildCellularTopologyMessage>().Subscribe(
               (topology, parameters) => BuildCellularTopology(topology, parameters));
        }

        /// <summary>
        /// Build cellular topology from geometric topology.
        /// </summary>
        /// <param name="geometricTopology">Geometric topology which cellular topology is built from.</param>
        private void BuildCellularTopology(GeometricTopology geometricTopology, Parameters parameters)
        {
            if (cellularTopology != null)
            {
                cellularTopology.Simulation.ModelTimeChanged -= Simulation_ModelTimeChanged;
                cellularTopology.SimulationStepsChanged -= cellularTopology_SimulationStepsChanged;
            }
            cellularTopology = new CellularTopology(geometricTopology, parameters);
            CustomSimulationSpeed = cellularTopology.Simulation.SimulationSpeed;
            cellularTopology.Simulation.ModelTimeChanged += Simulation_ModelTimeChanged;
            cellularTopology.SimulationStepsChanged += cellularTopology_SimulationStepsChanged;
            messenger.GetEvent<CellularTopologyMessage>().Publish(cellularTopology);
        }

        /// <summary>
        /// Switches the top panel to construction view model.
        /// </summary>
        private void SwitchToConstructionMode()
        {
            if (cellularTopology.Simulation.SimulationState == SimulationState.Running)
            {
                Stop();
            }
            cellularTopology.GeneratorsManager.DetachFromGeneratorEvents();
            messenger.GetEvent<SwitchToConstructionModeMessage>().Publish();
        }

        /// <summary>
        /// Runs simulation.        
        /// </summary>
        private void Run()
        {
            RunOrStepForward(cellularTopology.Run);
        }

        /// <summary>
        /// Perform step in simulation.
        /// </summary>
        private void Step()
        {
            RunOrStepForward(cellularTopology.StepForward);
        }

        /// <summary>
        /// Performs simulation run or step potentially with custom seed or seed from previous simulation.
        /// </summary>
        /// <param name="runOrStepForward">Reference to simulation function, either Run or StepForward function.</param>
        private void RunOrStepForward(Action<int?> runOrStepForward)
        {
            int? seedToUse = null;
            bool simulationIsInitialized = false;
            if (cellularTopology.Simulation.SimulationState == SimulationState.NotRunning)
            {
                simulationIsInitialized = true;
                if (settings.IsSeedSettingAutomatic)
                    seedToUse = null;
                else if (settings.IsPreviousSeedUsed)
                    seedToUse = settings.PreviousSeed;
                else
                    seedToUse = CustomSeed;
            }
            runOrStepForward(seedToUse);
            if (simulationIsInitialized)
            {
                settings.PreviousSeed = cellularTopology.Simulation.UsedSeed;
                IsPreviousSeedAvailable = true;
                settings.Save();
            }
            RefreshAlarmAndSeedProperties();
        }

        /// <summary>
        /// Refreshing of properties related to alarm and seed.
        /// </summary>
        private void RefreshAlarmAndSeedProperties()
        {
            RefreshAlarmProperties();
            OnPropertyChanged("Seed");
        }

        /// <summary>
        /// Stops simulation.
        /// </summary>
        private void Stop()
        {
            cellularTopology.Stop();
            RefreshAlarmProperties();
        }

        /// <summary>
        /// Pauses simulation.
        /// </summary>
        private void Pause()
        {
            cellularTopology.Pause();
        }

        /// <summary>
        /// Occurs when the model time in the simulation changes.
        /// </summary>
        /// <param name="sender">Sender object.</param>
        /// <param name="e">Unused event args.</param>
        void Simulation_ModelTimeChanged(object sender, EventArgs e)
        {
            OnPropertyChanged("ModelTime");
        }

        /// <summary>
        /// Occures when the simulation steps count changes.
        /// </summary>
        /// <param name="sender">Sender object.</param>
        /// <param name="e">Unused event args.</param>
        void cellularTopology_SimulationStepsChanged(object sender, EventArgs e)
        {
            OnPropertyChanged("SimulationSteps");
        }

        /// <summary>
        /// Method for refreshing model time to pause and simulation steps to pause properties to
        /// reset them when the alarm condition was reached.
        /// </summary>
        private void RefreshAlarmProperties()
        {
            OnPropertyChanged("ModelTimeToPause");
            OnPropertyChanged("SimulationStepsToPause");
        }
    }
}
