using System;
using System.Configuration;
using System.Drawing;
using System.Globalization;
using System.IO;
using System.IO.Ports;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Windows.Forms;

namespace Modbus.Common
{
    public partial class BaseForm : Form
    {
        private DisplayFormat _displayFormat = DisplayFormat.Integer;
        private CommunicationMode _communicationMode = CommunicationMode.TCP;
        protected Socket _socket;
        protected readonly UInt16[] _registerData;
        protected int _displayCtrlCount;
        private bool _logPaused = false;

        #region Form 

        public BaseForm()
        {
            InitializeComponent();
            _registerData = new UInt16[65600];
        }
        
        private void BaseFormLoading(object sender, EventArgs e)
        {
            comboBoxBaudRate.SelectedIndex = 4;
            FillRTUDropDownLists();
            CurrentTab.RegisterData = _registerData;
            if (_registerData == null)
            {
                throw new ApplicationException("Failed to allocate 128k block");
            }
            LoadUserData();
            CurrentTab.DisplayFormat = DisplayFormat;
            RefreshData();
        }

        private void BaseFormClosing(object sender, FormClosingEventArgs e)
        {
            SaveUserData();
        }

        private void FillRTUDropDownLists()
        {
            comboBoxSerialPorts.Items.Clear();
            foreach (var port in SerialPort.GetPortNames())
            {
                comboBoxSerialPorts.Items.Add(port);
            }
            if (comboBoxSerialPorts.Items.Count > 0)
                comboBoxSerialPorts.SelectedIndex = 0;
            comboBoxParity.Items.Clear();
            comboBoxParity.Items.Add(Parity.None.ToString());
            comboBoxParity.Items.Add(Parity.Odd.ToString());
            comboBoxParity.Items.Add(Parity.Even.ToString());
            comboBoxParity.Items.Add(Parity.Mark.ToString());
            comboBoxParity.Items.Add(Parity.Space.ToString());
        }

        private void LoadUserData()
        {
            CommunicationMode mode;
            if (Enum.TryParse(Properties.Settings.Default.CommunicationMode, out mode))
                CommunicationMode = mode;
            DisplayFormat format;
            if (Enum.TryParse(Properties.Settings.Default.DisplayFormat, out format))
                DisplayFormat = format;
            IPAddress ipAddress;
            if (IPAddress.TryParse(Properties.Settings.Default.IPAddress, out ipAddress))
                IPAddress = ipAddress;
            TCPPort = Properties.Settings.Default.TCPPort;
            PortName = Properties.Settings.Default.PortName;
            Baud = Properties.Settings.Default.Baud;
            Parity = Properties.Settings.Default.Parity;
            StartAddress = Properties.Settings.Default.StartAddress;
            DataLength = Properties.Settings.Default.DataLength;
            SlaveId = Properties.Settings.Default.SlaveId;
            SlaveDelay = Properties.Settings.Default.SlaveDelay;
            DataBits = Properties.Settings.Default.DataBits;
            StopBits = Properties.Settings.Default.StopBits;
        }

        private void SaveUserData()
        {
            Properties.Settings.Default.CommunicationMode = CommunicationMode.ToString();
            Properties.Settings.Default.IPAddress = IPAddress.ToString();
            Properties.Settings.Default.DisplayFormat = DisplayFormat.ToString();
            Properties.Settings.Default.TCPPort = TCPPort;
            Properties.Settings.Default.PortName = PortName;
            Properties.Settings.Default.Baud = Baud;
            Properties.Settings.Default.Parity = Parity;
            Properties.Settings.Default.StartAddress = StartAddress;
            Properties.Settings.Default.DataLength = DataLength;
            Properties.Settings.Default.SlaveId = SlaveId;
            Properties.Settings.Default.SlaveDelay = SlaveDelay;
            Properties.Settings.Default.DataBits = DataBits;
            Properties.Settings.Default.StopBits = StopBits;
            Properties.Settings.Default.Save();
        }

        #endregion

        #region Import - Export

        private void ButtonImportClick(object sender, EventArgs e)
        {
            openFileDialog.AddExtension = true;
            openFileDialog.DefaultExt = ".csv";
            openFileDialog.Multiselect = false;
            if (openFileDialog.ShowDialog() == DialogResult.OK)
            {
                using (var s = new FileStream(openFileDialog.FileName, FileMode.Open, FileAccess.Read))
                {
                    using (var r = new StreamReader(s))
                    {
                        var rec = r.ReadToEnd();
                        var sets = rec.Split(',');
                        var first = true;
                        foreach (var s1 in sets)
                        {
                            DisplayFormat fmt;
                            var v = s1.Split(':');
                            var address = int.Parse(v[0]);
                            ushort data;
                            if (v[1].StartsWith("0x", StringComparison.CurrentCultureIgnoreCase))
                            {
                                fmt = DisplayFormat.Hex;
                                var sub = v[1].Substring(2);
                                ushort.TryParse(sub, NumberStyles.HexNumber, CultureInfo.CurrentCulture, out data);
                            }
                            else if (v[1].Length > 6) //must be binary
                            {
                                fmt = DisplayFormat.Binary;
                                data = Convert.ToUInt16(v[1], 2);
                            }
                            else
                            {
                                fmt = DisplayFormat.Integer;
                                data = Convert.ToUInt16(v[1], 10);
                            }
                            if (address < _registerData.Length)
                            {
                                _registerData[address] = data;
                            }
                            if (first)
                            {
                                SetFunction(fmt);
                                first = false;
                                StartAddress = UInt16.Parse(v[0]);
                            }
                        }
                        r.Close();
                        DataLength = Convert.ToUInt16(sets.Length);
                        // display data
                    }
                    s.Close();
                }
                RefreshData();
            }
        }

        public delegate void SetFunctionDelegate(DisplayFormat log);

        protected void SetFunction(DisplayFormat fmt)
        {
            if (InvokeRequired)
            {
                BeginInvoke(new SetFunctionDelegate(SetFunction), new object[] { fmt });
                return;
            }
            DisplayFormat = fmt;
            switch (fmt)
            {
                case DisplayFormat.Integer:
                    radioButtonInteger.Checked = true;
                    break;
                case DisplayFormat.Binary:
                    radioButtonBinary.Checked = true;
                    break;
                case DisplayFormat.Hex:
                    radioButtonHex.Checked = true;
                    break;
                case DisplayFormat.LED:
                    radioButtonLED.Checked = true;
                    break;
            }
        }

        private void ButtonExportClick(object sender, EventArgs e)
        {
            var startAddress = StartAddress;
            var length = DataLength;
            string suffix = "-";
            switch (DisplayFormat)
            {
                case DisplayFormat.Integer:
                    suffix = "_Decimal_";
                    break;
                case DisplayFormat.Hex:
                    suffix = "_HEX_";
                    break;
                case DisplayFormat.Binary:
                    suffix = "_Binary_";
                    break;
                case DisplayFormat.LED:
                    suffix = "_LED_";
                    break;
            }
            var filename = "ModbusExport_" + startAddress + suffix + DateTime.Now.ToString("yyyyMMddHHmm") + ".csv";
            saveFileDialog.AddExtension = true;
            saveFileDialog.DefaultExt = ".csv";
            saveFileDialog.FileName = filename;
            saveFileDialog.OverwritePrompt = true;
            if (saveFileDialog.ShowDialog() == DialogResult.OK)
            {
                using (var s = saveFileDialog.OpenFile())
                {
                    using (var w = new StreamWriter(s))
                    {
                        for (var x = 0; x < length; x++)
                        {
                            w.Write(startAddress++);
                            w.Write(':');
                            var data = _registerData[StartAddress + x];
                            switch (DisplayFormat)
                            {
                                case DisplayFormat.Integer:
                                    w.Write(string.Format("{0}", data));
                                    break;
                                case DisplayFormat.Hex:
                                    w.Write(string.Format("0x{0:x4}", data));
                                    break;
                                case DisplayFormat.Binary:
                                case DisplayFormat.LED:
                                    w.Write(Convert.ToString(data, 2).PadLeft(16, '0'));
                                    break;
                            }
                            if (x < length - 1)
                                w.Write(',');
                        }
                        w.Flush();
                        w.Close();
                    }
                    s.Close();
                }
            }
        }

        #endregion

        #region Radion button check handlers

        private void RadioButtonModeChanged(object sender, EventArgs e)
        {
            SetMode();
        }

        protected void SetMode()
        {
            if (radioButtonTCP.Checked)
            {
                _communicationMode = CommunicationMode.TCP;
                groupBoxTCP.Enabled = true;
                groupBoxRTU.Enabled = false;
            }
            if (radioButtonRTU.Checked)
            {
                _communicationMode = CommunicationMode.RTU;
                groupBoxTCP.Enabled = false;
                groupBoxRTU.Enabled = true;
            }
            if (radioButtonUDP.Checked)
            {
                _communicationMode = CommunicationMode.UDP;
                groupBoxTCP.Enabled = true;
                groupBoxRTU.Enabled = false;
            }
        }

        private void RadioButtonDisplayFormatCheckedChanged(object sender, EventArgs e)
        {
            if (sender is RadioButton)
            {
                var rb = (RadioButton)sender;
                if (rb.Checked)
                {
                    DisplayFormat.TryParse(rb.Tag.ToString(), true, out _displayFormat);
                    CurrentTab.DisplayFormat = DisplayFormat;
                    RefreshData();
                }
            }
        }

        #endregion

        #region properties

        private ushort _startAddress;
        protected ushort StartAddress
        {
            get
            {
                return _startAddress;
            }
            set
            {
                CurrentTab.StartAddress = value;
                var tab = tabControl1.SelectedTab;
                tab.Text = value.ToString();
                _startAddress = value;
            }
        }

        private ushort _dataLength;
        protected ushort DataLength
        {
            get
            {
                return _dataLength;
            }
            set
            {
                _dataLength = value;
                CurrentTab.DataLength =value;
            }
        }

        public bool ShowDataLength { get; set; }

        protected IPAddress IPAddress
        {
            get
            {
                return IPAddress.Parse(txtIP.Text);
            }
            set
            {
                txtIP.Text = value.ToString();
            }
        }

        protected int TCPPort
        {
            get
            {
                return Int32.Parse(textBoxPort.Text);
            }
            set
            {
                textBoxPort.Text = Convert.ToString(value);

            }
        }

        protected byte SlaveId
        {
            get
            {
                return Byte.Parse(textBoxSlaveID.Text);
            }
            set
            {
                textBoxSlaveID.Text = Convert.ToString(value);
            }
        }

        protected int SlaveDelay
        {
            get
            {
                return int.Parse(textBoxSlaveDelay.Text);
            }
            set
            {
                textBoxSlaveDelay.Text = Convert.ToString(value);
            }
        }

        protected string PortName
        {
            get
            {
                return comboBoxSerialPorts.Text;
            }
            set
            {
                comboBoxSerialPorts.Text = value;
            }
        }

        protected int Baud
        {
            get
            {
                return Int32.Parse(comboBoxBaudRate.Text);
            }
            set
            {
                comboBoxBaudRate.SelectedItem = Convert.ToString(value);
            }
        }

        protected Parity Parity
        {
            get
            {
                var parity = Parity.None;
                if (comboBoxParity.SelectedItem.Equals(Parity.None.ToString()))
                {
                    parity = Parity.None;
                }
                else if (comboBoxParity.SelectedItem.Equals(Parity.Odd.ToString()))
                {
                    parity = Parity.Odd;
                }
                else if (comboBoxParity.SelectedItem.Equals(Parity.Even.ToString()))
                {
                    parity = Parity.Even;
                }
                else if (comboBoxParity.SelectedItem.Equals(Parity.Mark.ToString()))
                {
                    parity = Parity.Mark;
                }
                else if (comboBoxParity.SelectedItem.Equals(Parity.Space.ToString()))
                {
                    parity = Parity.Space;
                }
                return parity;
            }
            set
            {
                comboBoxParity.SelectedItem = Convert.ToString(value);
            }
        }

        protected int DataBits
        {
            get
            {
                int bits = 0;
                switch (comboBoxDataBits.SelectedIndex)
                {
                    case 0:
                        bits = 7;
                        break;
                    case 1:
                        bits = 8;
                        break;
                }
                return bits;
            }
            set
            {
                switch (value)
                {
                    case 7:
                        comboBoxDataBits.SelectedIndex = 0;
                        break;
                    case 8:
                        comboBoxDataBits.SelectedIndex = 1;
                        break;
                }
            }
        }

        protected StopBits StopBits
        {
            get
            {
                StopBits bits = StopBits.None;
                switch (comboBoxStopBits.SelectedIndex)
                {
                    case 0:
                        bits = StopBits.None;
                        break;
                    case 1:
                        bits = StopBits.One;
                        break;
                    case 2:
                        bits = StopBits.OnePointFive;
                        break;
                    case 3:
                        bits = StopBits.Two;
                        break;
                }
                return bits;
            }
            set
            {
                switch (value)
                {
                    case StopBits.None:
                        comboBoxStopBits.SelectedIndex = 0;
                        break;
                    case StopBits.One:
                        comboBoxStopBits.SelectedIndex = 1;
                        break;
                    case StopBits.OnePointFive:
                        comboBoxStopBits.SelectedIndex = 2;
                        break;
                    case StopBits.Two:
                        comboBoxStopBits.SelectedIndex = 3;
                        break;
                }
            }
        }

        protected DisplayFormat DisplayFormat
        {
            get { return _displayFormat; }
            set
            {
                switch (value)
                {
                    case DisplayFormat.LED:
                        radioButtonLED.Checked = true;
                        break;
                    case DisplayFormat.Binary:
                        radioButtonBinary.Checked = true;
                        break;
                    case DisplayFormat.Hex:
                        radioButtonHex.Checked = true;
                        break;
                    case DisplayFormat.Integer:
                        radioButtonInteger.Checked = true;
                        break;
                }
                _displayFormat = value;
                CurrentTab.DisplayFormat = DisplayFormat;
                RefreshData();
            }
        }

        protected CommunicationMode CommunicationMode
        {
            get { return _communicationMode; }
            set
            {
                switch (value)
                {
                    case CommunicationMode.TCP:
                        radioButtonTCP.Checked = true;
                        break;
                    case CommunicationMode.UDP:
                        radioButtonUDP.Checked = true;
                        break;
                    case CommunicationMode.RTU:
                        radioButtonRTU.Checked = true;
                        break;
                }
                _communicationMode = value;
            }
        }

        #endregion

        #region Logging

        public delegate void AppendLogDelegate(String log);

        protected void ButtonClearLogClick(object sender, EventArgs e)
        {
            listBoxCommLog.Items.Clear();
        }

        private void buttonPauseLog_Click(object sender, EventArgs e)
        {
            _logPaused = !_logPaused;
            buttonPauseLog.Text = _logPaused ? "Resume" : "Pause";
        }

        protected void DriverIncommingData(byte[] data)
        {
            if (_logPaused)
                return;
            var hex = new StringBuilder(data.Length * 2);
            foreach (byte b in data)
                hex.AppendFormat("{0:x2} ", b);
            AppendLog(String.Format("RX: {0}", hex));
        }

        protected void DriverOutgoingData(byte[] data)
        {
            if (_logPaused)
                return;
            var hex = new StringBuilder(data.Length * 2);
            foreach (byte b in data)
                hex.AppendFormat("{0:x2} ", b);
            AppendLog(String.Format("TX: {0}", hex));
        }

        protected void AppendLog(String log)
        {
            if (_logPaused)
                return;
            if (InvokeRequired)
            {
                BeginInvoke(new AppendLogDelegate(AppendLog), new object[] { log });
                return;
            }
            var now = DateTime.Now;
            var tmpStr = ">" + now.ToLongTimeString() + ": " + log;
            listBoxCommLog.Items.Add(tmpStr);
            listBoxCommLog.SelectedIndex = listBoxCommLog.Items.Count - 1;
            listBoxCommLog.SelectedIndex = -1;
        }

        #endregion

        #region Data Table

        protected void ButtonDataClearClick(object sender, EventArgs e)
        {
            ClearRegisterData();
        }

        protected void ClearRegisterData()
        {
            for (int i = 0; i < _registerData.Length; i++)
            {
                _registerData[i] = 0;
            }
            RefreshData();
        }

        protected DataTab CurrentTab
        {
            get
            {
                var tab = tabControl1.SelectedTab;
                return (DataTab)tab.Controls[0];
            }
        }

        public void RefreshData()
        {
            if (InvokeRequired)
            {
                BeginInvoke(new Action(RefreshData));
                return;
            }
            CurrentTab.RefreshData();
        }

        public void UpdateDataTable()
        {
            if (InvokeRequired)
            {
                BeginInvoke(new Action(UpdateDataTable));
                return;
            }
            CurrentTab.UpdateDataTable();
        }

        #endregion

        private void tabControl1_Selected(object sender, TabControlEventArgs e)
        {
            CurrentTab.RegisterData = _registerData;
            CurrentTab.DisplayFormat = DisplayFormat;
            var tab = tabControl1.SelectedTab;
            if (tab.Text.Equals("...") && tabControl1.TabPages.Count < 20)
            {
                DataTab dataTab = new DataTab();
                dataTab.DataLength = ((ushort)(256));
                dataTab.DisplayFormat = DisplayFormat.Integer;
                dataTab.Location = new Point(3, 3);
                dataTab.Name = "dataTab" + (tabControl1.TabPages.Count+1);
                dataTab.RegisterData = _registerData;
                dataTab.ShowDataLength = ShowDataLength;
                dataTab.Size = new Size(839, 406);
                dataTab.StartAddress = ((ushort)(0));
                dataTab.TabIndex = 0;
                dataTab.OnApply += dataTab_OnApply;
                TabPage tabPage = new TabPage();
                tabPage.Controls.Add(dataTab);
                tabPage.Location = new Point(4, 22);
                tabPage.Name = "tabPage" + (tabControl1.TabPages.Count + 1);
                tabPage.Padding = new Padding(3);
                tabPage.Size = new Size(851, 411);
                tabPage.TabIndex = tabControl1.TabPages.Count;
                tabPage.Text = "...";
                tabPage.UseVisualStyleBackColor = true;
                tabControl1.Controls.Add(tabPage);
            }
            var address = CurrentTab.StartAddress;
            tab.Text = address.ToString();
            _startAddress = address;
            _dataLength = CurrentTab.DataLength;
        }

        void dataTab_OnApply(object sender, EventArgs e)
        {
            var tab = tabControl1.SelectedTab;
            var address = CurrentTab.StartAddress;
            tab.Text = address.ToString();
            _startAddress = address;
            _dataLength = CurrentTab.DataLength;
        }
    }
}
