using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;
using System.Windows.Threading;
using BoFilTest.Domain;
using BoFilTest.UI;
using BoFilTest.Utils;
using Optional;
using Optional.Unsafe;
using Unity;

namespace BoFilTest.UI
{
    public delegate void Pulasable();
    
    class TestSpecificationAdaptor : ITestSpecification
    {
        private readonly Form form;

        public TestSpecificationAdaptor(Form form)
        {
            this.form = form;
        }

        public TimeSpan IntermediateDemoisturizingTime => form.IntermediateDemoisturizationTime;
        public int WashingPassCount => form.WashingEnabled ? form.WashingPassCount : 0;
        public TimeSpan Pressing => form.PressEnabled ? form.PressTime : TimeSpan.Zero;
        public TimeSpan Vaporization => form.VaporizationEnabled ? form.VaporizationTime : TimeSpan.Zero;
        public double DemoisturizationRatio => form.DemoisturizationRatio;
        public bool UseGasBreakthrough => form.UseGasBreakthrough;
    }

    /// <summary>
    /// Interaction logic for RunningWindow.xaml
    /// </summary>
    public partial class RunningPage : Page
    {
        private TestProcess testProcess;
        private readonly IUnityContainer container;
        private readonly SubscriberList subscribers;
        private readonly DataModel dataModel;
        
        private RunningModel ViewModel => (RunningModel)this.DataContext;

        public RunningPage(IUnityContainer container, EventBus eventBus, DataModel dataModel)
        {
            DataContext = new RunningModel(dataModel.Config.ExtraMeasure().Enabled());
            this.subscribers = new SubscriberList(eventBus);
            this.container = container;
            this.dataModel = dataModel;

            InitializeComponent();

            AllTextBlocks.TranslateForRoot(grid);

            Loaded += RunningPage_Loaded;   
            Unloaded += RunningWindow_Unloaded;
        }

        private void RunningPage_Loaded(object sender, RoutedEventArgs e)
        {
            UpdateTime("", false);
            ViewModel.Clear();
            this.stateDisplay.Text = Translated.FromKey("TEST_RUNNING");

            SetCurrentValues(MeasuredValues.CreateEmpty());

            this.subscribers.Subscribe<MeasurementEntry>(entry =>
            {
                UpdateMeasuredValues(entry.RelativeRunningTime, entry.Values);
            });

            this.subscribers.Subscribe<NewValuesWithTare>(message =>
            {
                SetCurrentValues(message.Values);
            });

            this.subscribers.Subscribe<StepChanged>(message =>
            {
                UpdateForStep(message.Step);
                UpdateProcessStatus(message.Step, message.All);
            });

            this.subscribers.Subscribe<TimeDisplay>(message =>
            {
                UpdateTime(message.Text, message.Warn);
            });

            this.subscribers.Subscribe<TestFinished>(message =>
            {
                this.stateDisplay.Text = Translated.FromKey("TEST_FINISHED");
                this.stepDescription.Text = "";
                this.recordState.Text = Translated.FromKey("RECORDING_INACTIVE");
                UpdateProcessStatus(null, message.All);
            });

            this.subscribers.Subscribe<KeyPressed>(message =>
            {
                this.KeyPressed();
            });

            var list = TestStepList.Build(new TestSpecificationAdaptor(dataModel.Form));
            var hardwareControl = container.Resolve<HardwareControlWithFailSafe>();
            this.testProcess = new TestProcess(subscribers.EventBus(), list, dataModel.Form.AcquisitionInterval, hardwareControl);
            QueuePulse();
        }

        private void RunningWindow_Unloaded(object sender, RoutedEventArgs e)
        {
            this.subscribers.UnsubscribeAll();
        }

        private string CurrentLabelText<T, R>(string key, Option<T> value, Func<T, R> formatter)
        {
            if (!value.HasValue)
                return Translated.FromKey(key, Translated.FromKey("NOT_AVAILABLE"));

            return Translated.FromKey(key, formatter(value.ValueOrFailure()));
        }

        private void SetCurrentValues(MeasuredValues values)
        {
            this.currentWeight.Text = CurrentLabelText("CURRENT_WEIGHT", values.Weight, x => x.Value);
            this.currentPressure.Text = CurrentLabelText("CURRENT_PRESSURE", values.Pressure, x => x.Value);
            this.currentGasFlow.Text = CurrentLabelText("CURRENT_GAS_FLOW", values.Flow, x => x.Value);
            this.currentExtra.Text = CurrentLabelText("CURRENT_EXTRA", values.ExtraValue, x => x.Value);
        }

        private void UpdateMeasuredValues(TimeSpan time, MeasuredValues values)
        {
            ViewModel.UpdateMeasuredValues(time, values);
        }

        private void UpdateForStep(TestStep step)
        {
            this.stepDescription.Text = step.Description.Denomination;
            this.recordState.Text = Translated.FromKey(step.RecordValues ? "RECORDING_ACTIVE" : "RECORDING_INACTIVE");
        }

        private static Rectangle BlockForStep(TestStep step)
        {
            if (step.RecordValues)
            {
                return new Rectangle
                {
                    Height = 30,
                    Width = 60,
                    Fill = step.HasCountDown ? new SolidColorBrush(Colors.LightBlue) : new SolidColorBrush(Colors.LightGreen),
                };
                
            }
            else
            {
                return new Rectangle
                {
                    Height = 30,
                    Width = 10,
                    Fill = new SolidColorBrush(Colors.LightGray),
                };
            }
        }

        private void UpdateProcessStatus(TestStep current, List<TestStep> all)
        {
            if (all.Count==0)
                return;

            var grid = this.processStatus;
            grid.ColumnDefinitions.Clear();
            
            foreach (var step in all)
            {
                grid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Auto) });
            }

            grid.Children.Clear();

            int column=0;
            foreach (var step in all)
            {
                var r = BlockForStep(step);

                r.StrokeThickness = 2;
                if (step == current)
                {
                    r.Stroke = Brushes.Black;
                }
                else
                {
                    r.Stroke = Brushes.White;
                }

                r.SetValue(Grid.RowProperty, 0);
                r.SetValue(Grid.ColumnProperty, column);
                r.SetValue(MarginProperty, new Thickness(2));

                var sign = new TextBlock();
                sign.Text = step.Description.Sign;
                sign.TextAlignment = TextAlignment.Center;
                sign.VerticalAlignment = VerticalAlignment.Center;
                sign.FontSize = 16;
                sign.SetValue(Grid.RowProperty, 0);
                sign.SetValue(Grid.ColumnProperty, column);

                grid.Children.Add(r);
                grid.Children.Add(sign);
                ++column;
            }
            
        }

        private void UpdateTime(string formattedTime, bool warn)
        {
            this.timeDisplay.Text = formattedTime;
            this.timeDisplay.Background = warn ? Brushes.Red : Brushes.White;
        }

        private void KeyPressed()
        {
            if (testProcess.IsFinished())
            {
                NavigationService.Navigate(container.Resolve<EndPage>());
            }
            else
            {
                this.testProcess.MarkForContinue();
            }
        }

        void QueuePulse()
        {
            this.Dispatcher.BeginInvoke(DispatcherPriority.SystemIdle, new Pulasable(() =>
            {
                Pulse();
            }));
        }

        void Pulse()
        {
            if (this.testProcess.Poll())
                QueuePulse();
        }
    }
}
