using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows;
using System.Windows.Input;
using AIM.AutoId;
using DemoLib.Model;
using Demo1.Properties;
using DemoLib.Service;
using Demo1.View;
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.CommandWpf;
using GalaSoft.MvvmLight.Messaging;
using UnifiedAutomation.UaBase;
using System.ComponentModel;
using System.Diagnostics;

namespace Demo1.ViewModel
{
    public class MainViewModel : ViewModelBase
    {
        public MainViewModel()
        {
            ConnectCommand = new RelayCommand(ConnectCommandExecute, CanConnectCommand);
            DisconnectCommand = new RelayCommand(DisconnectCommandExecute, CanDisconnectCommand);
            WindowCloseingCommand = new RelayCommand<CancelEventArgs>(WindowCloseingCommandExecute);
            ScanCommand = new RelayCommand<bool>(ScanCommandExecute, CanScanCommand);
            ScanStartCommand = new RelayCommand(ScanStartCommandExecute, CanScanStartCommand);
            ScanStopCommand = new RelayCommand(ScanStopCommandExecute, CanScanStopCommand);
            ReadTagCommand = new RelayCommand(ReadTagCommandExecute, CanReadTagCommand);
            WriteTagCommand = new RelayCommand(WriteTagCommandExecute, CanWriteTagCommand);
            RandomBytesCommand = new RelayCommand(RandomBytesCommandExecute, CanRandomBytesCommand);
            ClearTagsCommand = new RelayCommand(ClearTagsCommandExecute, CanClearTagsCommand);
            ClearOutputsCommand = new RelayCommand(ClearOutputsCommandExecute, CanClearOutputsCommand);
            ClearEventsCommand = new RelayCommand(ClearEventsCommandExecute, CanClearEventsCommand);
            ShowReaderInfoCommand = new RelayCommand(ShowReaderInfoCommandExecute, CanShowReaderInfoCommand);
            ShowBusModeCommand = new RelayCommand(ShowBusModeCommandExecute, CanShowBusModeCommand);
            EditReaderNameCommand = new RelayCommand(EditReaderNameCommandExecute, CanEditReaderNameCommand);
            RefreshReaderListCommand = new RelayCommand(RefreshReaderListCommandExecute, CanRefreshReaderListCommand);

            // Load user settings
            Server = Settings.Default.Server;
            Port = Settings.Default.Port.ToString();
            Cycles = Settings.Default.Cycles;
            Duration = Settings.Default.Duration;
            TagDataOffset = Settings.Default.Offset;
            TagDataLength = Settings.Default.Length;

            ReaderList = new ObservableCollection<Reader>();
            Tags = new ObservableCollection<RfidTag>();
            Outputs = new ObservableCollection<string>();
            ReaderEvents = new ObservableCollection<ReaderEvent>();
            WriteDataTag = "";
            RunningConnect = false;
        }

        #region Commands

        #region Connect

        public RelayCommand ConnectCommand { get; }

        private async void ConnectCommandExecute()
        {
            RunningConnect = true;
            var time = new Stopwatch();
            time.Start();
            try
            {
                if (await OpcUaService.Instance.ConnectAsync(Server, int.Parse(Port)))
                {
                    // Save user settings
                    Settings.Default.Server = Server;
                    Settings.Default.Port = int.Parse(Port);
                    Settings.Default.Save();
                    Connected = true;
                    RunningScanStart = false;

                    RefreshReaderListCommandExecute();
                    OpcUaService.Instance.DataChange += Monitored_DataChange;
                    OpcUaService.Instance.NewEvent += Monitored_OnNewEvent;

                    CommandManager.InvalidateRequerySuggested();
                }
                else
                {
                    Connected = false;
                }
            }
            catch (Exception e)
            {
                Outputs.Insert(0, $"Connect error: {e.Message}");
                CommandManager.InvalidateRequerySuggested();
            }

            time.Stop();
            WatchResultTime = time.Elapsed;
            RunningConnect = false;
        }

        public bool CanConnectCommand()
        {
            return !String.IsNullOrEmpty(Server) && !String.IsNullOrEmpty(Port) && !Connected && !RunningConnect;
        }

        #endregion Connect

        #region Disconnect

        public RelayCommand DisconnectCommand { get; }

        private void DisconnectCommandExecute()
        {
            OpcUaService.Instance.Disconnect();
            Connected = false;
            Tags.Clear();
            Outputs.Clear();
            ReaderEvents.Clear();
            ReaderStatus = "";
            OpcUaService.Instance.DataChange -= Monitored_DataChange;
            OpcUaService.Instance.NewEvent -= Monitored_OnNewEvent;
        }

        public bool CanDisconnectCommand()
        {
            return Connected;
        }

        #endregion Disconnect

        #region WindowCloseing

        public RelayCommand<CancelEventArgs> WindowCloseingCommand { get; private set; }

        private void WindowCloseingCommandExecute(CancelEventArgs e)
        {
            if (Connected)
            {
                DisconnectCommandExecute();
            }
        }

        #endregion WindowCloseing

        #region Scan

        public RelayCommand<bool> ScanCommand { get; }

        private async void ScanCommandExecute(bool dataAviarible)
        {
            RunningScan = true;
            Tags.Clear();
            try
            {
                if (dataAviarible)
                {
                    var time = new Stopwatch();
                    time.Start();
                    var tuple = await OpcUaService.Instance.ScanAsync(SelectedReader, true);
                    time.Stop();
                    WatchResultTime = time.Elapsed;

                    if (tuple.Item2.IsGood)
                    {
                        Tags = tuple.Item1.Tags;
                        if (!string.IsNullOrEmpty(tuple.Item1.Status))
                        {
                            Outputs.Insert(0, $"Scan result status: {tuple.Item1.Status}");
                        }
                    }
                    ScanStatus = tuple.Item2.Name;
                }
                else
                {
                    if ((Duration / 1000) <= 60 && Cycles <= 60 && (Duration / 1000) * Cycles <= 60)
                    {
                        var time = new Stopwatch();
                        time.Start();
                        var tuple = await OpcUaService.Instance.ScanAsync(SelectedReader, Duration, Cycles);
                        time.Stop();
                        WatchResultTime = time.Elapsed;

                        if (tuple.Item2.IsGood)
                        {
                            Tags = tuple.Item1.Tags;
                            if (!string.IsNullOrEmpty(tuple.Item1.Status))
                            {
                                Outputs.Insert(0, $"Scan result status: {tuple.Item1.Status}");
                            }

                            // Save user settings
                            Settings.Default.Duration = Duration;
                            Settings.Default.Cycles = Cycles;
                            Settings.Default.Save();
                        }
                        ScanStatus = tuple.Item2.Name;
                    }
                    else
                    {
                        MessageBox.Show("Duration or cycle or both together exceeds 60 seconds. Please adjust the input.");
                    }
                }
            }
            catch (OpcUaServiceException e)
            {
                ScanStatus = e.Message;
            }
            RunningScan = false;

            CommandManager.InvalidateRequerySuggested();
        }

        public bool CanScanCommand(bool dataAviarible)
        {
            return Connected && SelectedReader != null && !RunningScan && !RunningScanStart;
        }

        #endregion Scan

        #region ScanStart

        public RelayCommand ScanStartCommand { get; private set; }

        private void ScanStartCommandExecute()
        {
            try
            {
                var result = OpcUaService.Instance.ScanStart(SelectedReader);
                if (result.IsGood)
                {
                    RunningScanStart = true;
                    if (!OpcUaService.Instance.HasMonitoredAutoIdScanEvent(SelectedReader))
                    {
                        OpcUaService.Instance.AddMonitoredAutoIdScanEvent(SelectedReader);
                    }
                }

                ScanStatus = result.Name;
            }
            catch (OpcUaServiceException e)
            {
                Outputs.Insert(0, $"Error: {e.Message}");
            }
        }

        public bool CanScanStartCommand()
        {
            return Connected && SelectedReader != null && !RunningScan && !RunningScanStart;
        }

        #endregion ScanStart

        #region ScanStop

        public RelayCommand ScanStopCommand { get; private set; }

        private void ScanStopCommandExecute()
        {
            try
            {
                var result = OpcUaService.Instance.ScanStop(SelectedReader);
                if (result.IsGood)
                {
                    RunningScanStart = false;
                    OpcUaService.Instance.RemoveAutoIdScanEvent(SelectedReader);
                }

                ScanStatus = result.Name;
            }
            catch (OpcUaServiceException e)
            {
                Outputs.Insert(0, $"Error: {e.Message}");
            }
        }

        public bool CanScanStopCommand()
        {
            return Connected && SelectedReader != null && !RunningScan && RunningScanStart;
        }

        #endregion ScanStop

        #region ReadTag

        public RelayCommand ReadTagCommand { get; }

        private async void ReadTagCommandExecute()
        {
            RunningReadWrite = true;
            var selectedTag = SelectedTag;
            try
            {
                var time = new Stopwatch();
                time.Start();
                var result = await OpcUaService.Instance.ReadTagAsync(SelectedReader, selectedTag, TagDataOffset, TagDataLength);
                time.Stop();
                WatchResultTime = time.Elapsed;

                if (selectedTag != null)
                {
                    Outputs.Insert(0, selectedTag.IdentiferString + ": " + BitConverter.ToString(result.Item1));
                }
                else
                {
                    Outputs.Insert(0, "read tag data: " + BitConverter.ToString(result.Item1));
                }

                // Save user settings
                Settings.Default.Offset = TagDataOffset;
                Settings.Default.Length = TagDataLength;
                Settings.Default.Save();
            }
            catch (OpcUaServiceException e)
            {
                Outputs.Insert(0, "Read tag: " + selectedTag?.IdentiferString + " Error: " + e.Message);
            }
            RunningReadWrite = false;

            CommandManager.InvalidateRequerySuggested();
        }

        public bool CanReadTagCommand()
        {
            return Connected && SelectedReader != null && !RunningReadWrite;
        }

        #endregion ReadTag

        #region WriteTag

        public RelayCommand WriteTagCommand { get; }

        private async void WriteTagCommandExecute()
        {
            RunningReadWrite = true;
            if (WriteDataTag.Length != TagDataLength * 2)
            {
                Outputs.Insert(0, "Error write tag: Data length does not match the specified length.");
                RunningReadWrite = false;
                return;
            }

            byte[] data = Enumerable.Range(0, WriteDataTag.Length)
                .Where(x => x % 2 == 0)
                .Select(x => Convert.ToByte(WriteDataTag.Substring(x, 2), 16))
                .ToArray();

            try
            {
                var time = new Stopwatch();
                time.Start();
                await OpcUaService.Instance.WriteTagAsync(SelectedReader, SelectedTag, TagDataOffset, data);
                time.Stop();
                WatchResultTime = time.Elapsed;

                // Save user settings
                Settings.Default.Offset = TagDataOffset;
                Settings.Default.Length = TagDataLength;
                Settings.Default.Save();

                Outputs.Insert(0, "Write tag: " + SelectedTag?.IdentiferString + " successfully");
            }
            catch (OpcUaServiceException e)
            {
                Outputs.Insert(0, "Write tag: " + SelectedTag?.IdentiferString + " Error: " + e.Message);
            }
            RunningReadWrite = false;

            CommandManager.InvalidateRequerySuggested();
        }

        public bool CanWriteTagCommand()
        {
            return Connected && SelectedReader != null && WriteDataTag.Length > 0 && !RunningReadWrite;
        }

        #endregion WriteTag

        #region RandomBytes

        public RelayCommand RandomBytesCommand { get; }

        private async void RandomBytesCommandExecute()
        {
            var writeData = "";
            Random random = new Random();

            for (int i = 0; i < TagDataLength * 2; i++)
            {
                writeData += random.Next(0, 9).ToString();
            }
            WriteDataTag = writeData;
            CommandManager.InvalidateRequerySuggested();
        }

        public bool CanRandomBytesCommand()
        {
            return Connected && SelectedReader != null;
        }

        #endregion RandomBytes

        #region ClearOutputs

        public RelayCommand ClearOutputsCommand { get; private set; }

        private void ClearOutputsCommandExecute()
        {
            Outputs.Clear();
        }

        public bool CanClearOutputsCommand()
        {
            return Connected;
        }

        #endregion ClearOutputs

        #region ClearTags

        public RelayCommand ClearTagsCommand { get; private set; }

        private void ClearTagsCommandExecute()
        {
            Tags.Clear();
        }

        public bool CanClearTagsCommand()
        {
            return Connected && !RunningScan;
        }

        #endregion ClearTags

        #region ClearEvents

        public RelayCommand ClearEventsCommand { get; private set; }

        private void ClearEventsCommandExecute()
        {
            ReaderEvents.Clear();
        }

        public bool CanClearEventsCommand()
        {
            return Connected;
        }

        #endregion ClearEvents

        #region ShowReaderInfo

        public RelayCommand ShowReaderInfoCommand { get; private set; }

        private void ShowReaderInfoCommandExecute()
        {
            var view = new ReaderInfoView();
            view.Owner = Application.Current.MainWindow;

            Messenger.Default.Send(SelectedReader);

            view.ShowDialog();
        }

        public bool CanShowReaderInfoCommand()
        {
            return Connected && SelectedReader != null;
        }

        #endregion ShowReaderInfo

        #region ShowBusMode

        public RelayCommand ShowBusModeCommand { get; private set; }

        private void ShowBusModeCommandExecute()
        {
            var view = new BusModeView();
            view.Owner = Application.Current.MainWindow;

            Tuple<Reader, NodeId> data = new Tuple<Reader, NodeId>(_selectedReader, _busConfigurationNodeId);
            Messenger.Default.Send(data);

            int newAntenna = -1;
            Messenger.Default.Register<int>(this, (antenna) =>
            {
                Messenger.Default.Unregister<int>(this);
                newAntenna = antenna;
            });

            if (newAntenna != -1)
            {
                if (OpcUaService.Instance.SetAntenna(SelectedReader, newAntenna))
                {
                    RefreshReaderListCommandExecute();
                }
            }

            view.ShowDialog();
        }

        public bool CanShowBusModeCommand()
        {
            return Connected && SelectedReader != null && BusConfigurationNodeId != null && SelectedReaderBusModeActive;
        }

        #endregion ShowBusMode

        #region EditReaderName

        public RelayCommand EditReaderNameCommand { get; private set; }

        private void EditReaderNameCommandExecute()
        {
            var view = new ReaderEditNameView();
            view.Owner = Application.Current.MainWindow;

            Messenger.Default.Send(SelectedReader.Name);
            string newName = null;
            Messenger.Default.Register<string>(this, (name) =>
            {
                Messenger.Default.Unregister<string>(this);
                newName = name;
            });

            view.ShowDialog();

            if (newName != null)
            {
                if (OpcUaService.Instance.SetReaderName(SelectedReader, newName))
                {
                    RefreshReaderListCommandExecute();
                }
            }
        }

        public bool CanEditReaderNameCommand()
        {
            return Connected && SelectedReader != null;
        }

        #endregion EditReaderName

        #region RefreshReaderList

        public RelayCommand RefreshReaderListCommand { get; private set; }

        private void RefreshReaderListCommandExecute()
        {
            SelectedReader = null;
            ReaderStatus = "";
            ReaderList.Clear();
            try
            {
                ReaderList = OpcUaService.Instance.GetAllReader();
                BusConfigurationNodeId = OpcUaService.Instance.GetReaderBusConfiguration();
            }
            catch (OpcUaServiceException e)
            {
                Outputs.Insert(0, e.Message);
            }
        }

        public bool CanRefreshReaderListCommand()
        {
            return Connected;
        }

        #endregion RefreshReaderList

        #endregion Commands

        #region Properties

        private bool _connected;

        public bool Connected
        {
            get => _connected;
            private set => Set(ref _connected, value);
        }

        private string _server;

        public string Server
        {
            get => _server;
            set => Set(ref _server, value);
        }

        private string _port;

        public string Port
        {
            get => _port;
            set => Set(ref _port, value);
        }

        private int _duration;

        public int Duration
        {
            get => _duration;
            set => Set(ref _duration, value);
        }

        private int _cycles;

        public int Cycles
        {
            get => _cycles;
            set => Set(ref _cycles, value);
        }

        private int _tagDataOffset;

        public int TagDataOffset
        {
            get => _tagDataOffset;
            set => Set(ref _tagDataOffset, value);
        }

        private int _tagDataLength;

        public int TagDataLength
        {
            get => _tagDataLength;
            set => Set(ref _tagDataLength, value);
        }

        private bool _runningConnect;

        public bool RunningConnect
        {
            get => _runningConnect;
            private set => Set(ref _runningConnect, value);
        }

        private string _scanStatus;

        public string ScanStatus
        {
            get
            {
                return _scanStatus;
            }
            set
            {
                Set(ref _scanStatus, value);
            }
        }

        private bool _runningScan;

        public bool RunningScan
        {
            get => _runningScan;
            private set => Set(ref _runningScan, value);
        }

        private bool _runningScanStart;

        public bool RunningScanStart
        {
            get => _runningScanStart;
            private set => Set(ref _runningScanStart, value);
        }

        private bool _runningReadWrite;

        public bool RunningReadWrite
        {
            get => _runningReadWrite;
            private set => Set(ref _runningReadWrite, value);
        }

        private string _writeDataTag;

        public string WriteDataTag
        {
            get => _writeDataTag;
            set => Set(ref _writeDataTag, value);
        }

        private ObservableCollection<string> _outputs;

        public ObservableCollection<string> Outputs
        {
            get => _outputs;
            set => Set(ref _outputs, value);
        }

        private Reader _selectedReader;

        public Reader SelectedReader
        {
            get => _selectedReader;
            set
            {
                Tags.Clear();

                if (value != null)
                {
                    if (SelectedReader != null)
                    {
                        OpcUaService.Instance.RemoveMonitoredItem(SelectedReader, "DeviceStatus");
                    }
                    RunningScanStart = false;
                    OpcUaService.Instance.AddMonitoredItem(value, "DeviceStatus");

                    SelectedReaderBusModeActive = OpcUaService.Instance.IsBusModeActive(value, BusConfigurationNodeId);
                }
                Set(ref _selectedReader, value);
            }
        }

        private NodeId _busConfigurationNodeId = null;

        public NodeId BusConfigurationNodeId
        {
            get
            {
                return _busConfigurationNodeId;
            }
            set
            {
                Set(ref _busConfigurationNodeId, value);
            }
        }

        private string _readerStatus;

        public string ReaderStatus
        {
            get
            {
                return _readerStatus;
            }
            set
            {
                Set(ref _readerStatus, value);
            }
        }

        private bool _selectedReaderBusModeActive;

        public bool SelectedReaderBusModeActive
        {
            get
            {
                return _selectedReaderBusModeActive;
            }
            set
            {
                Set(ref _selectedReaderBusModeActive, value);
            }
        }

        private ObservableCollection<Reader> _readerList;

        public ObservableCollection<Reader> ReaderList
        {
            get => _readerList;
            set => Set(ref _readerList, value);
        }

        private RfidTag _selectedTag;

        public RfidTag SelectedTag
        {
            get => _selectedTag;
            set => Set(ref _selectedTag, value);
        }

        private ObservableCollection<RfidTag> _tagsCollection;

        public ObservableCollection<RfidTag> Tags
        {
            get => _tagsCollection;
            set => Set(ref _tagsCollection, value);
        }

        private ReaderEvent _selectedEvent;

        public ReaderEvent SelectedEvent
        {
            get => _selectedEvent;
            set
            {
                Set(ref _selectedEvent, value);
                Tags.Clear();
                if (_selectedEvent?.Tag != null)
                {
                    Tags.Add(_selectedEvent.Tag);
                }
            }
        }

        private ObservableCollection<ReaderEvent> _readerEvents;

        public ObservableCollection<ReaderEvent> ReaderEvents
        {
            get => _readerEvents;
            set => Set(ref _readerEvents, value);
        }

        private TimeSpan _watchResultTime;

        public TimeSpan WatchResultTime
        {
            get
            {
                return _watchResultTime;
            }
            set
            {
                Set(ref _watchResultTime, value);
            }
        }

        #endregion Properties

        #region Private Functions

        private void Monitored_DataChange(string dataName, DataValue dataValue)
        {
            if (dataName.Equals("DeviceStatus"))
            {
                DeviceStatusEnumeration state = (DeviceStatusEnumeration)Enum.Parse(typeof(DeviceStatusEnumeration), dataValue.Value.ToString());
                switch (state)
                {
                    case DeviceStatusEnumeration.Idle:
                        ReaderStatus = "Idle";
                        break;

                    case DeviceStatusEnumeration.Error:
                        ReaderStatus = "Error";
                        break;

                    case DeviceStatusEnumeration.Scanning:
                        ReaderStatus = "Scanning...";
                        break;

                    case DeviceStatusEnumeration.Busy:
                        ReaderStatus = "Busy";
                        break;
                }

                if (state == DeviceStatusEnumeration.Scanning && !RunningScanStart) // Scanning
                {
                    RunningScanStart = true;
                    if (!OpcUaService.Instance.HasMonitoredAutoIdScanEvent(SelectedReader))
                    {
                        OpcUaService.Instance.AddMonitoredAutoIdScanEvent(SelectedReader);
                    }
                    Application.Current.Dispatcher.Invoke(CommandManager.InvalidateRequerySuggested);
                }
            }
        }

        private void Monitored_OnNewEvent(Reader reader, EventInfoArgs infoArgs, RfidTag tag)
        {
            //ReaderEvent readerEvent = ;
            Application.Current.Dispatcher.Invoke(() =>
            {
                ReaderEvents.Insert(0, new ReaderEvent(infoArgs.Source, infoArgs.Time, infoArgs.Severity, infoArgs.Message, tag));
            });
        }

        #endregion Private Functions
    }
}