using System;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.IO.Ports;
using System.IO;
using System.Diagnostics;
using System.Windows.Forms.DataVisualization.Charting;
using System.Runtime.InteropServices;
using System.Net;

namespace WindowsFormsApp1
{
  public partial class MainForm : Form
  {
    public MainForm()
    {
      InitializeComponent();

      foreach (var s in chart_Graph.Series) s.XValueType = ChartValueType.DateTime;
      chart_Graph.ChartAreas[0].AxisX.LabelStyle.Format = "mm:ss";

      chart_Graph.ChartAreas[0].AxisX.Minimum = DateTime.Now.ToOADate();
      chart_Graph.ChartAreas[0].AxisX.Maximum = DateTime.Now.AddSeconds(trackBar_GraphX_Down.Value).ToOADate();
      chart_Graph.ChartAreas[0].AxisX.IntervalAutoMode = IntervalAutoMode.FixedCount;
      chart_Graph.ChartAreas[0].AxisX.Interval = (new DateTime(1, 1, 1, 0, 0, 1)).ToOADate();

      for (int a = 0; a < checkedListBox_Series.Items.Count; a++) checkedListBox_Series.SetItemChecked(a, true);

      PIDs[0] = numericUpDown_PID_Min; PIDs[1] = numericUpDown_PID_Max;

      PIDs[2] = numericUpDown_PID_P_Min; PIDs[3] = numericUpDown_PID_P_Max; PIDs[4] = numericUpDown_PID_P_Value;
      PIDs[5] = numericUpDown_PID_I_Min; PIDs[6] = numericUpDown_PID_I_Max; PIDs[7] = numericUpDown_PID_I_Value;
      PIDs[8] = numericUpDown_PID_D_Min; PIDs[9] = numericUpDown_PID_D_Max; PIDs[10] = numericUpDown_PID_D_Value;

      PIDs[11] = numericUpDown_Yaw_P_Min; PIDs[12] = numericUpDown_Yaw_P_Max; PIDs[13] = numericUpDown_Yaw_P_Value;
      PIDs[14] = numericUpDown_Yaw_I_Min; PIDs[15] = numericUpDown_Yaw_I_Max; PIDs[16] = numericUpDown_Yaw_I_Value;
      PIDs[17] = numericUpDown_Yaw_D_Min; PIDs[18] = numericUpDown_Yaw_D_Max; PIDs[19] = numericUpDown_Yaw_D_Value;

    }

    private byte[] getStructBytes(RecvHead str)
    {
      int size = Marshal.SizeOf(str);
      byte[] arr = new byte[size];

      IntPtr ptr = IntPtr.Zero;
      try
      {
        ptr = Marshal.AllocHGlobal(size);
        Marshal.StructureToPtr(str, ptr, true);
        Marshal.Copy(ptr, arr, 0, size);
      }
      finally
      {
        Marshal.FreeHGlobal(ptr);
      }
      return arr;
    }

    private object fromBytestoStruct(byte[] arr)
    {
      RecvHead str = new RecvHead();

      int size = Marshal.SizeOf(str);
      IntPtr ptr = IntPtr.Zero;
      try
      {
        ptr = Marshal.AllocHGlobal(size);

        Marshal.Copy(arr, 0, ptr, size);

        str = (RecvHead)Marshal.PtrToStructure(ptr, str.GetType());
      }
      finally
      {
        Marshal.FreeHGlobal(ptr);
      }
      return str;
    }

    private bool CheckCRC8(byte crc1, byte crc2)
    {
      bool ret = crc1 == crc2;
      if (ret)
      {
        RecvCounts++;
        textBox_Count.Text = RecvCounts.ToString();
      }
      else
      {
        RecvErrors++;
        textBox_Error.Text = RecvErrors.ToString();
      }

      RecvMain.Mode = RecvModeEnum.Begin;

      return ret;
    }

    void OperationResult(bool res)
    {
      if (res)
      {
        RecvCounts++;
        textBox_Count.Text = RecvCounts.ToString();
      }
      else
      {
        RecvErrors++;
        textBox_Error.Text = RecvErrors.ToString();
      }
      RecvMain.Mode = RecvModeEnum.Begin;
    }

    private byte GetCRC8(byte[] arr, int size)
    {
      byte crc = 0;
      for (int a = 0; a < Math.Min(arr.Length, size); a++) crc ^= arr[a];
      return crc;
    }

    private bool CheckCRC8(byte[] arr, int size, byte crc2)
    {
      byte crc1 = GetCRC8(arr, size);
      return CheckCRC8(crc1, crc2);
    }

    private void SendData(RecvModeEnum mode, byte[] data)
    {
      
      if (data == null) data = new byte[0];
      RecvHead head = new RecvHead(mode, data.Length, GetCRC8(data, data.Length));
      byte[] h = getStructBytes(head);

      byte[] send = new byte[1 + h.Length + data.Length];
      send[0] = 0xAA;
      Buffer.BlockCopy(h, 0, send, 1, h.Length);
      Buffer.BlockCopy(data, 0, send, 1 + h.Length, data.Length);

      serialPort_COM.Write(send, 0, send.Length);
    }


    string RecordsFolder = "";
    BinaryWriter RecordWriter = null;

    private object[] PIDs = new object[20];

    private const int RecvCount = 512;
    private byte[] RecvData = new byte[RecvCount];
    private int RecvSize = 0;

    private enum RecvModeEnum : byte { Begin, Head, Titles, Data, Auto, SetPID, GetPID, Done };

    private int TimerStop = 0;
    private int RecvErrors = 0;
    private int RecvCounts = 0;

    private DateTime GraphLast = DateTime.Now;

    private struct RecvHead
    {
      public RecvHead(RecvModeEnum mode, int size, byte crc8)
      {
        Mode = mode;
        DataSize = (byte)size;
        DataCRC8 = crc8;
        CRC8 = (byte)(((byte)Mode) ^ DataSize ^ DataCRC8);
      }

      public RecvModeEnum Mode;
      public byte DataSize;
      public byte DataCRC8;

      public byte CRC8;

      public bool Check()
      {
        return CRC8 == (byte)(((byte)Mode) ^ DataSize ^ DataCRC8);
      }
    }

    private RecvHead RecvMain;

    private void DrawGraph(byte[] data, int from, int size)
    {
      if (checkBox_Track.Checked)
      {
        chart_Graph.ChartAreas[0].AxisX.Minimum = DateTime.Now.AddSeconds(-trackBar_GraphX_Down.Value).ToOADate();
        chart_Graph.ChartAreas[0].AxisX.Maximum = DateTime.Now.ToOADate();
      }
      else
      {
        TimeSpan shift = DateTime.Now.Subtract(GraphLast);
        if (shift.TotalSeconds > trackBar_GraphX_Down.Value)
        {
          GraphLast = DateTime.Now;
          for (int a = 0; a < chart_Graph.Series.Count; a++) chart_Graph.Series[a].Points.Clear();
          chart_Graph.ChartAreas[0].AxisX.Minimum = DateTime.Now.ToOADate();
          chart_Graph.ChartAreas[0].AxisX.Maximum = DateTime.Now.AddSeconds(trackBar_GraphX_Down.Value).ToOADate();
        }
      }

      RecordWriter?.Write(Encoding.UTF8.GetBytes(DateTime.Now.ToOADate().ToString()));

      short[] lines = new short[size / 2];
      Buffer.BlockCopy(data, from, lines, 0, size);

      for (int a = 0; a < Math.Min(lines.Length, chart_Graph.Series.Count); a++)
      {
        chart_Graph.Series[a].Points.AddXY(DateTime.Now, lines[a]);

        if (checkBox_Track.Checked)
        {
          while (true)
          {
            if ((DateTime.Now - DateTime.FromOADate(chart_Graph.Series[a].Points[0].XValue)).TotalSeconds > trackBar_GraphX_Down.Value + 1)
              chart_Graph.Series[a].Points.RemoveAt(0);
            else break;
          }
        }

        RecordWriter?.Write(Encoding.UTF8.GetBytes("|" + lines[a].ToString()));
      }

      RecordWriter?.Write(Encoding.UTF8.GetBytes("\r\n"));
      RecordWriter?.Flush();
    }

    private void RecvPID(byte[] data)
    {
      for (int a = 0; a < 20; a++)
      {
        ((NumericUpDown)PIDs[a]).Value = (decimal)BitConverter.ToSingle(data, a * 4);
      }
    }

    private byte[] SendPID()
    {
      byte[] send = new byte[20 * 4];
      float[] data = new float[20];

      for (int a = 0; a < 20; a++)
      {
        float v = (float)((NumericUpDown)PIDs[a]).Value;
        data[a] = v;
      }

      Buffer.BlockCopy(data, 0, send, 0, send.Length);

      return send;
    }

    private void button_Connect_Click(object sender, EventArgs e)
    {
      if (button_Connect.Text == "Connect")
      {
        try { serialPort_COM.PortName = comboBox_Port.Text; } catch { MessageBox.Show("Error to set port", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); return; }
        try
        { 
          serialPort_COM.Open();
          serialPort_COM.DiscardInBuffer();
          SendData(RecvModeEnum.Titles, null);
        } 
        catch { MessageBox.Show("Error to connect", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); return; }
        RecvErrors = 0;
        RecvCounts = 0;
        textBox_Error.Text = "0";
        textBox_Count.Text = "0";
        timer_Tick.Enabled = true;
        button_Connect.Text = "Disonnect";
        checkBox_Save_CheckedChanged(null, null);
      }
      else
      {
        serialPort_COM.Close();
        timer_Tick.Enabled = false;
        button_Connect.Text = "Connect";
        panel_PID.Enabled = false;
        RecordWriter?.Close();
        RecordWriter = null;
      }
    }

    private void timer_Tick_Tick(object sender, EventArgs e)
    {
      switch (RecvMain.Mode)
      {
        case RecvModeEnum.Begin:
          {
            TimerStop = 20;

            byte[] recv = new byte[1];
            int len;
            try { len = serialPort_COM.Read(recv, 0, 1); } catch { break; }
            if (recv[0] == 0xAA) RecvMain.Mode = RecvModeEnum.Head;
            break;
          }

        case RecvModeEnum.Head:
          {
            int size = Marshal.SizeOf(RecvMain);
            int len;
            try { len = serialPort_COM.Read(RecvData, RecvSize, size - RecvSize); } catch { break; }
            RecvSize += len;
            if (RecvSize < size) break;
            RecvSize = 0;

            RecvHead head = (RecvHead)fromBytestoStruct(RecvData);
            if (!head.Check()) { OperationResult(false); break; }

            RecvMain = head;

            break;
          }

        case RecvModeEnum.Titles:
          {
            int len;
            try { len = serialPort_COM.Read(RecvData, RecvSize, RecvMain.DataSize - RecvSize); } catch { break; }
            RecvSize += len;
            if (RecvSize < RecvMain.DataSize) break;
            RecvSize = 0;

            if (!CheckCRC8(RecvData, RecvMain.DataSize, RecvMain.DataCRC8)) break;

            string titles = Encoding.UTF8.GetString(RecvData, 0, len);
            string[] tit = titles.Split('|');

            chart_Graph.Series.Clear();
            checkedListBox_Series.Items.Clear();

            foreach (var t in tit)
            {
              var s = chart_Graph.Series.Add(t);
              s.ChartType = SeriesChartType.FastLine;
              s.XValueType = ChartValueType.DateTime;
              int i = checkedListBox_Series.Items.Add(t);
              checkedListBox_Series.SetItemChecked(i, true);
            }

            break;
          }

        case RecvModeEnum.Data:
          {
            int len;
            try { len = serialPort_COM.Read(RecvData, RecvSize, RecvMain.DataSize - RecvSize); } catch { break; }
            RecvSize += len;
            if (RecvSize < RecvMain.DataSize) break;
            RecvSize = 0;

            if (!CheckCRC8(RecvData, len, RecvMain.DataCRC8)) break;

            if (chart_Graph.Series.Count == 0) break;

            DrawGraph(RecvData, 0, RecvMain.DataSize);

            break;
          }

        case RecvModeEnum.GetPID:
          {
            int len;
            try { len = serialPort_COM.Read(RecvData, RecvSize, RecvMain.DataSize - RecvSize); } catch { break; }
            RecvSize += len;
            if (RecvSize < RecvMain.DataSize) break;
            RecvSize = 0;

            if (!CheckCRC8(RecvData, len, RecvMain.DataCRC8)) break;

            RecvPID(RecvData);
            label_PID_State.Text = "Done";

            for (int a = 0; a < 20; a++) ((NumericUpDown)PIDs[a]).BackColor = Color.White;
            panel_PID.Enabled = true;

            break;
          }

        case RecvModeEnum.Done:
          {
            for (int a = 0; a < 20; a++) ((NumericUpDown)PIDs[a]).BackColor = Color.White;
            label_PID_State.Text = "Done";
            RecvMain.Mode = RecvModeEnum.Begin;
            break;
          }

        default:
          {
            RecvMain.Mode = RecvModeEnum.Begin;
            break;
          }
      }
      TimerStop--;
      if (TimerStop <= 0)
      {
        RecvErrors++;
        textBox_Error.Text = RecvErrors.ToString();
        RecvMain.Mode = RecvModeEnum.Begin;
      }
    }

    private void comboBox_Port_DropDown(object sender, EventArgs e)
    {
      comboBox_Port.Items.Clear();
      foreach (string p in SerialPort.GetPortNames()) comboBox_Port.Items.Add(p);
    }

    private void numericUpDown_Min_ValueChanged(object sender, EventArgs e)
    {
      trackBar_GraphX_Down.Maximum = (int)numericUpDown_Min.Value;
      if (sender == numericUpDown_Min) trackBar_GraphX_Down.Value = trackBar_GraphX_Down.Maximum;
      else foreach (var s in chart_Graph.Series) s.Points.Clear();
      GraphLast = DateTime.Now;
      chart_Graph.ChartAreas[0].AxisX.Minimum = DateTime.Now.ToOADate();
      chart_Graph.ChartAreas[0].AxisX.Maximum = DateTime.Now.AddSeconds(trackBar_GraphX_Down.Value).ToOADate();
      chart_Graph.ChartAreas[0].AxisX.Interval = (new DateTime(1, 1, 1, 0, 0, 0).AddSeconds(((double)trackBar_GraphX_Down.Value) / 10)).ToOADate();
      if (trackBar_GraphX_Down.Value < 10) chart_Graph.ChartAreas[0].AxisX.LabelStyle.Format = "mm:ss.f";
      else chart_Graph.ChartAreas[0].AxisX.LabelStyle.Format = "mm:ss";
    }

    private void button_PID_Read_Click(object sender, EventArgs e)
    {
      label_PID_State.Text = "Read";

      SendData(RecvModeEnum.GetPID, null);
    }

    private void button_PID_Write_Click(object sender, EventArgs e)
    {
      if (!panel_PID.Enabled) return;
      label_PID_State.Text = "Write";

      byte[] data = SendPID();
      SendData(RecvModeEnum.SetPID, data);
    }

    private void button_Color_Click(object sender, EventArgs e)
    {
      if (colorDialog_Color.ShowDialog() == DialogResult.Cancel) return;
      if (checkedListBox_Series.SelectedIndex == -1) return;
      //---
      int index = checkedListBox_Series.SelectedIndex;
      chart_Graph.Series[index].Color = colorDialog_Color.Color;
      button_Color.BackColor = colorDialog_Color.Color;
    }

    private void label_PID_State_TextChanged(object sender, EventArgs e)
    {
      if (label_PID_State.Text != "Done") label_PID_State.BackColor = Color.Tomato;
      else label_PID_State.BackColor = Color.LightGreen;
    }

    private void numericUpDown_PID_Min_ValueChanged(object sender, EventArgs e)
    {
      ((NumericUpDown)sender).BackColor = Color.Tomato;
    }

    private void checkBox_Save_CheckedChanged(object sender, EventArgs e)
    {
      if (!checkBox_Save.Checked)
      {
        RecordWriter?.Close();
        RecordWriter = null;
        return;
      }

      DateTime dt = DateTime.Now;
      string file = Path.Combine(RecordsFolder, "record=" + dt.ToString("yyyy-MM-dd+HH_mm_ss") + ".tel");
      try
      {
        RecordWriter = new BinaryWriter(File.OpenWrite(file));
        string head = "Telem v1\r\nTime";
        foreach (var s in chart_Graph.Series) head += "|" + s.Name;
        head += "\r\n";
        RecordWriter.Write(Encoding.UTF8.GetBytes(head));
        RecordWriter.Flush();
      }
      catch
      {
        MessageBox.Show("Incorrect save file!", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
        checkBox_Save.Checked = false;
      }
    }

    private void Form1_Load(object sender, EventArgs e)
    {
      try
      {
        string folder = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
        string specificFolder = Path.Combine(folder, "Telem");
        //---
        RecordsFolder = Path.Combine(specificFolder, "Records");
        Directory.CreateDirectory(RecordsFolder);

        openFileDialog_Open.InitialDirectory = RecordsFolder;
      }
      catch { }

      string[] input = Environment.GetCommandLineArgs();

      if (input.Length > 1)
      {
        string file = input[1];
        byte[] record = File.ReadAllBytes(file);
        short[] data = new short[record.Length / 2];

        Buffer.BlockCopy(record, 0, data, 0, record.Length);

        for (int a = 0; a < data.Length; a++)
        {
          chart_Graph.Series[a % 9].Points.AddXY(a / 9, data[a]);
        }

        chart_Graph.ChartAreas[0].AxisX.Maximum = double.NaN;

        groupBox_Connect.Enabled = false;
      }
    }

    private void Form1_FormClosing(object sender, FormClosingEventArgs e)
    {
      RecordWriter?.Close();
      RecordWriter = null;
    }

    private void openToolStripMenuItem_Click(object sender, EventArgs e)
    {
      if (openFileDialog_Open.ShowDialog() != DialogResult.OK) return;

      Record w = new Record();
      w.RecordFile = openFileDialog_Open.FileName;
      w.Show();
    }

    private void folderToolStripMenuItem_Click(object sender, EventArgs e)
    {
      try { Process.Start(RecordsFolder); }
      catch { MessageBox.Show("No records!", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); }
    }

    private void numericUpDown_Graph_MaxY_ValueChanged(object sender, EventArgs e)
    {
      chart_Graph.ChartAreas[0].AxisY.Minimum = (double)numericUpDown_Graph_MinY.Value;
      chart_Graph.ChartAreas[0].AxisY.Maximum = (double)numericUpDown_Graph_MaxY.Value;
      trackBar_GraphY_Left.Maximum = (int)numericUpDown_Graph_MaxY.Value;
      trackBar_GraphY_Left.Minimum = (int)numericUpDown_Graph_MinY.Value;
      trackBar_GraphY_Right.Maximum = (int)numericUpDown_Graph_MaxY.Value;
      trackBar_GraphY_Right.Minimum = (int)numericUpDown_Graph_MinY.Value;
    }

    private void checkBox_AutoY_CheckedChanged(object sender, EventArgs e)
    {
      if (checkBox_AutoY.Checked)
      {
        chart_Graph.ChartAreas[0].AxisY.Minimum = double.NaN;
        chart_Graph.ChartAreas[0].AxisY.Maximum = double.NaN;
        numericUpDown_Graph_MinY.Enabled = false;
        numericUpDown_Graph_MaxY.Enabled = false;
        panel_GraphY.Enabled = false;
      }
      else
      {
        numericUpDown_Graph_MaxY_ValueChanged(null, null);
        numericUpDown_Graph_MinY.Enabled = true;
        numericUpDown_Graph_MaxY.Enabled = true;
        panel_GraphY.Enabled = true;
        trackBar_GraphY_Left.Value = trackBar_GraphY_Left.Minimum;
        trackBar_GraphY_Right.Value = trackBar_GraphY_Right.Maximum;
      }
    }

    private void trackBar_GraphY_Left_Scroll(object sender, EventArgs e)
    {
      int min = trackBar_GraphY_Left.Value;
      int max = trackBar_GraphY_Right.Value;
      if (min > max)
      {
        int sub = min;
        min = max;
        max = sub;
      }

      if (max == min) max += 1;

      chart_Graph.ChartAreas[0].AxisY.Minimum = min;
      chart_Graph.ChartAreas[0].AxisY.Maximum = max;
    }

    private void checkedListBox_Series_SelectedIndexChanged(object sender, EventArgs e)
    {
      if (checkedListBox_Series.SelectedIndex == -1) return;
      int index = checkedListBox_Series.SelectedIndex;
      button_Color.BackColor = chart_Graph.Series[index].Color;
      //---
      for (int a = 0; a < checkedListBox_Series.Items.Count; a++)
      {
        chart_Graph.Series[a].Enabled = checkedListBox_Series.GetItemChecked(a);
      }
    }
  }
}
