using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Collections;
using System.Threading;
using FTD2XX_NET;

namespace Data_Scan
{
    public partial class FormDataScan : Form
    {
        public FormDataScan()
        {
            InitializeComponent();
        }

        #region constant definitions

        const string COMMAND_LMS_STATUS = "02000100311512";
        const string RESPONSE_LMS_STATUS = "0602809A00B15830312E313420";

        const string COMMAND_LMS_BAUDRATE_9600 = "0200020020425208";
        const string COMMAND_LMS_BAUDRATE_19200 = "0200020020415108";
        const string COMMAND_LMS_BAUDRATE_38400 = "0200020020405008";
        const string COMMAND_LMS_BAUDRATE_500000 = "0200020020485808";

        const string RESPONSE_LMS_BAUDRATE_9600 = "0602800300A00010160A";
        const string RESPONSE_LMS_BAUDRATE_19200 = "0602800300A00010160A";
        const string RESPONSE_LMS_BAUDRATE_38400 = "0602800300A00010160A";
        const string RESPONSE_LMS_BAUDRATE_500000 = "0602800300A00010160A";

        const string COMMAND_LMS_RESOLUTION_100_1 = "020005003B640064001D0F";       // 0-100 stupnu - rozliseni 1.00
        const string COMMAND_LMS_RESOLUTION_100_5 = "020005003B64003200B159";       // 0-100 stupnu - rozliseni 0.50
        const string COMMAND_LMS_RESOLUTION_100_25 = "020005003B64001900E772";      // 0-100 stupnu - rozliseni 0.25
        const string COMMAND_LMS_RESOLUTION_180_1 = "020005003BB40064009749";       // 0-180 stupnu - rozliseni 1.00
        const string COMMAND_LMS_RESOLUTION_180_5 = "020005003BB40032003B1F";       // 0-180 stupnu - rozliseni 0.50

        const string RESPONSE_LMS_RESOLUTION_100_1 = "0602800700BB0164006400104FBD";
        const string RESPONSE_LMS_RESOLUTION_100_5 = "0602800700BB0164003200101710";
        const string RESPONSE_LMS_RESOLUTION_100_25 = "0602800700BB016400190010BB46";
        const string RESPONSE_LMS_RESOLUTION_180_1 = "0602800700BB01B4006400105B30";
        const string RESPONSE_LMS_RESOLUTION_180_5 = "0602800700BB01B400320010039D";

        const string COMMAND_LMS_SETTINGS_MODE = "02000A0020005349434B5F4C4D53BEC5";   // prikaz pro vstup do "settings mode" pro nastavovani mericiho modu
        const string COMMAND_LMS_MEASUREMENT_MODE_MM = "02002100770000000000000100000202000000000000000000000000000000000000000000FC7E";
        const string COMMAND_LMS_MEASUREMENT_MODE_CM = "02002100770000000000000000000202000000000000000000000000000000000000000000E872";

        const string RESPONSE_LMS_SETTINGS_MODE = "0602800300A00010160A";
        const string RESPONSE_LMS_MEASUREMENT_MODE_MM = "0602802500F7000000460000000100000202000000000000000000000000000000000000000000027E10240B";
        const string RESPONSE_LMS_MEASUREMENT_MODE_CM = "0602802500F70000004600000000000002020000000000000000000000000000000000000000000272109C67";

        const string COMMAND_LMS_START_CONTINUOUS_DATA_OUTPUT = "0200020020243408"; // start continuous data output                                                           
        const string COMMAND_LMS_STOP_CONTINUOUS_DATA_OUTPUT = "0200020020253508";  // stop continuous data output

        const string RESPONSE_START_CONTINUOUS_DATA_OUTPUT = "0602800300A00010160A";
        const string RESPONSE_STOP_CONTINUOUS_DATA_OUTPUT = "0602800300A00010160A";

        const string RESPONSE_STARTUP = "02801700904C4D533239313B5330353036333B5830312E31342010E03C";

        const string RESPONSE_HEADER_100_1 = "0280CE00B065";
        const string RESPONSE_HEADER_100_5 = "02809601B0C9"; 
        const string RESPONSE_HEADER_100_25 = "02802603B091";
        const string RESPONSE_HEADER_180_1 = "02806E01B0B5";
        const string RESPONSE_HEADER_180_5 = "0280D602B069";

        //toto jsou cele hlavicky, ale jelikoz se predposledni cislo lisi (bud 0 nebo 4)
        //podle toho jestli jsou nastavene jednotky na cm nebo mm tak je lepsi pouzivat 
        //zkracenou verzi (nahore)
        //const string RESPONSE_HEADER_100_1 = "0280CE00B06500";
        //const string RESPONSE_HEADER_100_5 = "02809601B0C900"; 
        //const string RESPONSE_HEADER_100_25 = "02802603B09101";
        //const string RESPONSE_HEADER_180_1 = "02806E01B0B500";
        //const string RESPONSE_HEADER_180_5 = "0280D602B06901";

        #endregion constant definitions

        #region variable definitions

        UInt32 USBBaudRate;
        UInt32 LMSBaudRate;

        string selectedDeviceSerialNumber;  // ser. cislo zvoleneho zarizeni
        string selectedDeviceDescription;   // popis zvoleneho zarizeni

        UInt32 ftdiDeviceCount = 0;                     // Pocet pripojenych zarizeni
        FTDI.FT_STATUS ftStatus = FTDI.FT_STATUS.FT_OK; // status pripojeneho zarizeni
        FTDI myFtdiDevice = new FTDI();                 // Nova instance FTDI device class

        int scansCount = 0;                 // pocet skenu    
        Scan newScan;                   // aktualni sken

        double startVerticalAngle;      // pocatecni uhel
        double stopVerticalAngle;       // koncovy uhel
        double currentVerticalAngle;    // aktualni uhel
        double previousVerticalAngle;   // predchozi uhel 
        double resolutionVerticalAngle; // vertikalni rozliseni (1° nebo 0.5°)

        bool scanRunning;               // je scan spusten?

        string readBuffer;              // promenna pro ukladani nactenych dat
        string dataHeader;              // hlavicka telegramu dat
        string measurementMode = "cm";  // merici mod, prednastaven na centimetry
        int dataValuesCount;            // pocet naskenovanych bodu v jednom datovem telegramu


        #endregion variable definitions

        #region Structures and collections

        // struktura pro ukladani dat jednotlivych scanu
        struct Data
        {
            public double verticalAngle;
            public string STX, ADR, LEN, CMD, DATALEN, DATA, STATUS, CRC;
        }

        // struktura pro ukladani jednotlivych scanu
        class Scan
        {
            public int scanNo;
            public double startAngle;
            public double stopAngle;
            public int rows;
            public int dataCount;
            public string mode;

            // Kolekce (pole) pro ukladani struktur Data 
            public ArrayList dataArray = new ArrayList();
        }

        // Kolekce (pole) pro ukladani scanu
        ArrayList scans = new ArrayList();

        #endregion

        #region Math and Hex & Byte functions

        // prevod Hexoveho retezce na pole Baytu
        private byte[] HexToByteArray(string hexString)
        {
            int numberChars = hexString.Length;
            byte[] bytes = new byte[numberChars / 2];

            for (int i = 0; i < numberChars; i += 2)
            {
                bytes[i / 2] = Convert.ToByte(hexString.Substring(i, 2), 16);
            }
            return bytes;
        }

        // prevod pole bytu na hexovy retezec
        private string ByteArrayToHex(byte[] byteArray)
        {
            string hex = "";

            foreach (byte b in byteArray)
            {
                hex += b.ToString("X2");
            }
            return hex;
        }

        // prevod Hexoveho retezce na integer
        private int HexToInt(string hexString)
        {
            int number = Convert.ToInt32(hexString, 16);
            return number;
        }

        // prevod stupnu na radiany
        public double ToRad(double angle)
        {
            double radAngle = Math.PI * angle / 180;
            return radAngle;
        }

        #endregion

        #region StatusLog button and function

        // vycisteni status log okna
        private void btnStatusLogClear_Click(object sender, EventArgs e)
        {
            richTextBoxStatus.Clear();
        }

        // vypise zpravu do status log okna
        private void StatusMessage(string message, string status)
        {
            switch (status)
            {
                case "good":
                    richTextBoxStatus.SelectionColor = Color.Green;
                    break;

                case "bad":
                    richTextBoxStatus.SelectionColor = Color.Red;
                    break;

                case "normal":
                    richTextBoxStatus.SelectionColor = Color.Black;
                    break;

                default:
                    richTextBoxStatus.SelectionColor = Color.Black;
                    break;
            }

            richTextBoxStatus.AppendText(message + "\r\n");
            richTextBoxStatus.ScrollToCaret();
        }

        #endregion

        #region USB devices list functions & buttons

        // po otevreni formulare zjistime zda mame pripojena nejake zarizeni a vlozime je do seznamu
        private void FormDataScan_Load(object sender, EventArgs e)
        {
            ListViewItem listItem;
            listViewUSB.Items.Clear();

            ftStatus = myFtdiDevice.GetNumberOfDevices(ref ftdiDeviceCount);
            FTDI.FT_DEVICE_INFO_NODE[] ftdiDeviceList = new FTDI.FT_DEVICE_INFO_NODE[ftdiDeviceCount];
            ftStatus = myFtdiDevice.GetDeviceList(ftdiDeviceList);

            if (ftStatus == FTDI.FT_STATUS.FT_OK)
            {
                for (int i = 0; i < ftdiDeviceCount; i++)
                {
                    listItem = listViewUSB.Items.Add("Device " + i.ToString());
                    listItem.SubItems.Add(ftdiDeviceList[i].SerialNumber.ToString());
                    listItem.SubItems.Add(ftdiDeviceList[i].Description.ToString());
                }
            }
        }

        // po kliknuti do seznamu pripojenych zarizeni si ulozime seriove cislo a popis zvoleneho zarizeni
        private void listView_USB_Click(object sender, EventArgs e)
        {
            ListViewItem listItem;

            if (myFtdiDevice.IsOpen) return;

            if (listViewUSB.SelectedItems[0] != null)
            {
                listItem = listViewUSB.SelectedItems[0];
                btnUSBOpen.Enabled = true;
                selectedDeviceSerialNumber = listItem.SubItems[1].Text;
                selectedDeviceDescription = listItem.SubItems[2].Text;
            }
            else
            {
                btnUSBOpen.Enabled = false;
            }
        }

        // tlacitko pro otevreni zvoleneho zarizeni na zaklade ulozeneho serioveho cisla z predchozi fce.
        private void btnUSBOpen_Click(object sender, EventArgs e)
        {
            ftStatus = myFtdiDevice.OpenBySerialNumber(selectedDeviceSerialNumber);

            if (ftStatus == FTDI.FT_STATUS.FT_OK)
            {
                myFtdiDevice.SetBaudRate(9600);

                btnUSBOpen.Enabled = false;
                btnUSBRefresh.Enabled = false;
                btnUSBClose.Enabled = true;
                btnSetUSBBaudRate.Enabled = true;
                btnLMSDetect.Enabled = true;               

                StatusMessage("Device with serial number " + selectedDeviceSerialNumber + " opened.", "normal");
                labelUSBBaudRate.ForeColor = Color.Green;
                labelUSBBaudRate.Text = "9600 Baud";
            }
        }

        // tlacitko pro uzavreni otevreho zarizeni 
        private void btnUSBClose_Click(object sender, EventArgs e)
        {
            ftStatus = myFtdiDevice.Close();

            if (ftStatus == FTDI.FT_STATUS.FT_OK)
            {
                btnUSBOpen.Enabled = true;
                btnUSBRefresh.Enabled = true;
                btnUSBClose.Enabled = false;
                StatusMessage("Device with serial number " + selectedDeviceSerialNumber + " closed.", "normal");

                labelUSBBaudRate.ForeColor = Color.Red;
                labelUSBBaudRate.Text = "Not connected";
                labelLMSBaudRate.ForeColor = Color.Red;
                labelLMSBaudRate.Text = "Not connected";
                labelResolution.ForeColor = Color.Red;
                labelResolution.Text = "Not connected";
                labelMode.ForeColor = Color.Red;
                labelMode.Text = "Not connected";

                btnLMSDetect.Enabled = false;
                btnSetUSBBaudRate.Enabled = false;
                btnSetLMSBaudRate.Enabled = false;
                btnSetResolution.Enabled = false;
                btnSetMode.Enabled = false;                
            }
        }

        // tlacitko pro znovunacteni pripojenych zarizeni
        private void btnUSBRefresh_Click(object sender, EventArgs e)
        {
            ListViewItem listItem;
            listViewUSB.Items.Clear();

            ftStatus = myFtdiDevice.GetNumberOfDevices(ref ftdiDeviceCount);
            FTDI.FT_DEVICE_INFO_NODE[] ftdiDeviceList = new FTDI.FT_DEVICE_INFO_NODE[ftdiDeviceCount];
            ftStatus = myFtdiDevice.GetDeviceList(ftdiDeviceList);

            if (ftStatus == FTDI.FT_STATUS.FT_OK)
            {
                for (int i = 0; i < ftdiDeviceCount; i++)
                {
                    listItem = listViewUSB.Items.Add("Device " + i.ToString());
                    listItem.SubItems.Add(ftdiDeviceList[i].SerialNumber.ToString());
                    listItem.SubItems.Add(ftdiDeviceList[i].Description.ToString());
                }
            }            
        }
        #endregion

        #region Settings buttons and functions

        // tlacitko pro detekci a ziskani LMS nastaveni
        private void btnLMSStatus_Click(object sender, EventArgs e)
        {
            //odesleme prikaz
            if (!SendCommand(COMMAND_LMS_STATUS))
            {
                StatusMessage("Failed to sent LMS Status Command!!!", "bad");
                return;
            }

            //pokud dosla odpoved tak zanalyzujem
            if (!ResponseReceived(RESPONSE_LMS_STATUS, 2000) || !AnalyzeStatus())
            {
                StatusMessage("Failed to get LMS Status !!!", "bad");

                labelLMSBaudRate.ForeColor = Color.Red;
                labelResolution.ForeColor = Color.Red;
                labelMode.ForeColor = Color.Red;
                labelLMSBaudRate.Text = "Not detected";
                labelResolution.Text = "Not detected";
                labelMode.Text = "Not detected";
                return;
            }

            StatusMessage("LMS detected.", "good");
            btnSetLMSBaudRate.Enabled = true;
            btnSetResolution.Enabled = true;
            btnSetMode.Enabled = true;
            btnStartScan.Enabled = true;
        }

        // fce pro prenastaveni settings labelu, a globalnich promennych nastaveni (dataValuesCount, measurementMode, dataHeader)
        private void ChangeSettingsLabels(string baudRate, int angle, int resolution, int mode)
        {
            labelLMSBaudRate.ForeColor = Color.Green;
            switch (baudRate)
            {
                case "6780":
                    labelLMSBaudRate.Text = "9600 Baud";
                    break;

                case "3380":
                    labelLMSBaudRate.Text = "19200 Baud";
                    break;

                case "1980":
                    labelLMSBaudRate.Text = "38400 Baud";
                    break;

                case "0180":
                    labelLMSBaudRate.Text = "500 KBaud";
                    break;

                default:
                    labelLMSBaudRate.ForeColor = Color.Red;
                    labelLMSBaudRate.Text = "Not detected";
                    break;
            }

            labelMode.ForeColor = Color.Green;
            switch (mode)
            {
                case 0:
                    labelMode.Text = "cm mode (0..8191 cm = 81.91 m)";
                    measurementMode = "cm";
                    break;

                case 1:
                    labelMode.Text = "mm mode (0..8191 mm = 8.191 m)";
                    measurementMode = "mm";
                    break;

                default:
                    labelMode.ForeColor = Color.Red;
                    labelMode.Text = "Not detected";
                    break;
            }

            labelResolution.ForeColor = Color.Green;
            switch (angle)
            {
                case 100:
                    switch (resolution)
                    {
                        case 100:
                            labelResolution.Text = "Range: 0..100°  Resolution: 1°";
                            dataValuesCount = 101;
                            dataHeader = RESPONSE_HEADER_100_1;
                            break;

                        case 50:
                            labelResolution.Text = "Range: 0..100°  Resolution: 0.5°";
                            dataValuesCount = 201;
                            dataHeader = RESPONSE_HEADER_100_5;
                            break;

                        case 25:
                            labelResolution.Text = "Range: 0..100°  Resolution: 0.25°";
                            dataValuesCount = 401;
                            dataHeader = RESPONSE_HEADER_100_25;
                            break;

                        default:
                            labelResolution.ForeColor = Color.Red;
                            labelResolution.Text = "Not detected";
                            break;
                    }
                    break;

                case 180:
                    switch (resolution)
                    {
                        case 100:
                            labelResolution.Text = "Range: 0..180°  Resolution: 1°";
                            dataValuesCount = 181;
                            dataHeader = RESPONSE_HEADER_180_1;
                            break;

                        case 50:
                            labelResolution.Text = "Range: 0..180°  Resolution: 0.5°";
                            dataValuesCount = 361;
                            dataHeader = RESPONSE_HEADER_180_5;
                            break;

                        default:
                            labelResolution.ForeColor = Color.Red;
                            labelResolution.Text = "Not detected";
                            break;
                    }
                    break;

                default:
                    labelResolution.ForeColor = Color.Red;
                    labelResolution.Text = "Not detected";
                    break;
            }
        }

        // tlacitko pro nastaveni FT BaudRate 
        private void btnSetUSBBaudRate_Click(object sender, EventArgs e)
        {
            if (comboUSBBaudRate.SelectedItem == null)
            {
                return;
            }

            switch (comboUSBBaudRate.SelectedItem.ToString())
            {
                case "9600 Baud":
                    USBBaudRate = 9600;
                    break;

                case "19200 Baud":
                    USBBaudRate = 19200;
                    break;

                case "38400 Baud":
                    USBBaudRate = 38400;
                    break;

                case "500 KBaud":
                    USBBaudRate = 500000;
                    break;

                default:
                    USBBaudRate = 9600;
                    break;
            }

            ftStatus = myFtdiDevice.SetBaudRate(USBBaudRate);
            if (ftStatus != FTDI.FT_STATUS.FT_OK)
            {
                StatusMessage("Failed to set FT232 BaudRate!!!", "bad");
                return;
            }

            labelUSBBaudRate.ForeColor = Color.Green;
            labelUSBBaudRate.Text = comboUSBBaudRate.SelectedItem.ToString();
            StatusMessage("USB BaudRate set to " + USBBaudRate.ToString(), "good");
        }

        // tlacitko pro nastaveni LMS BaudRate
        private void btnSetLMSBaudRate_Click(object sender, EventArgs e)
        {
            String command;
            String response;

            if (comboLMSBaudRate.SelectedItem == null)
            {
                return;
            }

            switch (comboLMSBaudRate.SelectedItem.ToString())
            {
                case "9600 Baud":
                    command = COMMAND_LMS_BAUDRATE_9600;
                    response = RESPONSE_LMS_BAUDRATE_9600;
                    LMSBaudRate = 9600;
                    break;

                case "19200 Baud":
                    command = COMMAND_LMS_BAUDRATE_19200;
                    response = RESPONSE_LMS_BAUDRATE_19200;
                    LMSBaudRate = 19200;
                    break;

                case "38400 Baud":
                    command = COMMAND_LMS_BAUDRATE_38400;
                    response = RESPONSE_LMS_BAUDRATE_38400;
                    LMSBaudRate = 38400;
                    break;

                case "500 KBaud":
                    command = COMMAND_LMS_BAUDRATE_500000;
                    response = RESPONSE_LMS_BAUDRATE_500000;
                    LMSBaudRate = 500000;
                    break;

                default:
                    command = COMMAND_LMS_BAUDRATE_9600;
                    response = RESPONSE_LMS_BAUDRATE_9600;
                    LMSBaudRate = 9600;
                    break;
            }

            if (!SendCommand(command))
            {
                StatusMessage("Failed to sent LMS BaudRate Command!!!", "bad");
                return;
            }

            if (!ResponseReceived(response, 500))
            {
                StatusMessage("Failed to set LMS BaudRate!!!", "bad");
                return;
            }

            labelLMSBaudRate.ForeColor = Color.Green;
            labelLMSBaudRate.Text = comboLMSBaudRate.SelectedItem.ToString();
            StatusMessage("LMS BaudRate set to " + comboLMSBaudRate.SelectedItem.ToString(), "good");
        }

        // tlacitko pro nastaveni horizntalnich uhlu a rozliseni skenovani (0..100° - 1° atd.)
        // zaroven dojde k nastaveni promennych dataValuesCount a dataHeader
        private void btnSetResolution_Click(object sender, EventArgs e)
        {
            String command;
            String response;

            if (comboResolution.SelectedItem == null)
            {
                return;
            }

            switch (comboResolution.SelectedItem.ToString())
            {
                case "Range: 0..100°  Resolution: 1°":
                    command = COMMAND_LMS_RESOLUTION_100_1;
                    response = RESPONSE_LMS_RESOLUTION_100_1;
                    dataValuesCount = 101;
                    dataHeader = RESPONSE_HEADER_100_1;
                    break;

                case "Range: 0..100°  Resolution: 0.5°":
                    command = COMMAND_LMS_RESOLUTION_100_5;
                    response = RESPONSE_LMS_RESOLUTION_100_5;
                    dataValuesCount = 201;
                    dataHeader = RESPONSE_HEADER_100_5;
                    break;

                case "Range: 0..100°  Resolution: 0.25°":
                    command = COMMAND_LMS_RESOLUTION_100_25;
                    response = RESPONSE_LMS_RESOLUTION_100_25;
                    dataValuesCount = 401;
                    dataHeader = RESPONSE_HEADER_100_25;
                    break;

                case "Range: 0..180°  Resolution: 1°":
                    command = COMMAND_LMS_RESOLUTION_180_1;
                    response = RESPONSE_LMS_RESOLUTION_180_1;
                    dataValuesCount = 181;
                    dataHeader = RESPONSE_HEADER_180_1;
                    break;

                case "Range: 0..180°  Resolution: 0.5°":
                    command = COMMAND_LMS_RESOLUTION_180_5;
                    response = RESPONSE_LMS_RESOLUTION_180_5;
                    dataValuesCount = 361;
                    dataHeader = RESPONSE_HEADER_180_5;
                    break;

                default:
                    command = COMMAND_LMS_RESOLUTION_100_1;
                    response = RESPONSE_LMS_RESOLUTION_100_1;
                    dataValuesCount = 101;
                    dataHeader = RESPONSE_HEADER_100_1;
                    break;
            }

            if (!SendCommand(command))
            {
                StatusMessage("Failed to sent LMS Resolution Command!!!", "bad");
                return;
            }

            if (!ResponseReceived(response, 500))
            {
                StatusMessage("Failed to set LMS Resolution!!!", "bad");
                return;
            }

            labelResolution.ForeColor = Color.Green;
            labelResolution.Text = comboResolution.SelectedItem.ToString();
            StatusMessage("LMS Angular Resolution set to " + comboResolution.SelectedItem.ToString(), "good");
        }

        // tlacitko pro nastaveni modu mereni (cm, mm)
        private void btnSetMode_Click(object sender, EventArgs e)
        {
            String command;
            String response;

            if (comboMode.SelectedItem == null)
            {
                return;
            }

            switch (comboMode.SelectedItem.ToString())
            {
                case "mm mode (0..8191 mm = 8.191 m)":
                    command = COMMAND_LMS_MEASUREMENT_MODE_MM;
                    response = RESPONSE_LMS_MEASUREMENT_MODE_MM;
                    measurementMode = "mm";
                    break;

                case "cm mode (0..8191 cm = 81.91 m)":
                    command = COMMAND_LMS_MEASUREMENT_MODE_CM;
                    response = RESPONSE_LMS_MEASUREMENT_MODE_CM;
                    measurementMode = "cm";
                    break;

                default:
                    command = COMMAND_LMS_MEASUREMENT_MODE_MM;
                    response = RESPONSE_LMS_MEASUREMENT_MODE_MM;
                    measurementMode = "mm";
                    break;
            }

            if (!SendCommand(COMMAND_LMS_SETTINGS_MODE))
            {
                StatusMessage("Failed to sent LMS Settings mode Command!!!", "bad");
                return;
            }

            if (!ResponseReceived(RESPONSE_LMS_SETTINGS_MODE, 2000))
            {
                StatusMessage("Failed to enable LMS Measurement mode!!!", "bad");
                return;
            }

            if (!SendCommand(command))
            {
                StatusMessage("Failed to sent LMS Measurement mode Command!!!", "bad");
                return;
            }

            if (!ResponseReceived(response, 9000))
            {
                StatusMessage("Failed to set LMS Measurement mode!!!", "bad");
                return;
            }

            labelMode.ForeColor = Color.Green;
            labelMode.Text = comboMode.SelectedItem.ToString();
            StatusMessage("LMS Measurement mode set to " + comboMode.SelectedItem.ToString(), "good");
        }

        #endregion

        #region Data scan buttons

        // tlacitko, pro spusteni skenu, pokud jiz nebezi
        private void btnStartScan_Click(object sender, EventArgs e)
        {
            if (!scanRunning)
            {
                StartScan();
            }
        }

        // checkbox pro manualni skenovani ( skenovani pouze jednoho radku) , pouze vypina a zapina 
        // numBox pro vertikalni uhel a comboBox pro nastaveni Vertikalniho rozliseni, ktere jsou v tomto modu nepotrebne
        private void checkBoxManualScan_CheckedChanged(object sender, EventArgs e)
        {
            if (checkBoxManualScan.Checked)
            {
                comboVerticalResolution.Enabled = false;
                numBoxStopAngle.Enabled = false;
            }
            else
            {
                comboVerticalResolution.Enabled = true;
                numBoxStopAngle.Enabled = true;
            }
        }

        // tlacitko pro ulozeni naskenovanych dat do souboru
        // prokazdy scan se ulozi jeho data
        private void btnSaveData_Click(object sender, EventArgs e)
        {
            ArrayList fileLines = new ArrayList();

            string saved_file = "";
            string line = "";

            line = scans.Count.ToString();
            fileLines.Add(line);

            foreach (Scan scan in scans)
            {
                line = scan.dataArray.Count.ToString();
                fileLines.Add(line);

                line = scan.mode;
                fileLines.Add(line);

                foreach (Data data in scan.dataArray)
                {
                    line = "#";
                    fileLines.Add(line);

                    line = data.verticalAngle.ToString();
                    fileLines.Add(line);

                    line = data.STX + data.ADR + data.LEN + data.CMD;
                    fileLines.Add(line);

                    line = data.DATALEN;
                    fileLines.Add(line);

                    line = data.DATA;
                    fileLines.Add(line);

                    line = data.STATUS + data.CRC;
                    fileLines.Add(line);
                }
            }

            saveFileDialog1.InitialDirectory = "C:";
            saveFileDialog1.Title = "Save data";
            saveFileDialog1.FileName = "";

            saveFileDialog1.Filter = "txt files (*.txt)|*.txt|All files (*.*)|*.*";

            if (saveFileDialog1.ShowDialog() == DialogResult.OK)
            {
                saved_file = saveFileDialog1.FileName;
                System.IO.StreamWriter file = new System.IO.StreamWriter(saved_file);
                foreach (string fileLine in fileLines)
                {
                    file.WriteLine(fileLine);
                }
                file.Close();
            }
        }

        // tlacitko pro smazani oznacenych dat v listViewData 
        private void btnDeleteSelected_Click(object sender, EventArgs e)
        {
            foreach (ListViewItem eachItem in listViewData.SelectedItems)
            {
                foreach (Scan scan in scans)
                {
                    if (scan.scanNo == int.Parse(eachItem.Text))
                    {
                        scans.Remove(scan);
                        break;
                    }
                }

                listViewData.Items.Remove(eachItem);
            }
        }

        // tlacitko pro smazani vsech dat
        private void btnDeleteData_Click(object sender, EventArgs e)
        {
            scans.Clear();
            listViewData.Items.Clear();
        }

        #endregion

        #region Sending commands and response receiving & analyzing

        // Odeslani prikazu pro pripojene zarizeni
        private bool SendCommand(string command)
        {
            UInt32 numBytesWritten = 0;
            byte[] dataToWrite = HexToByteArray(command);          
           
            ftStatus = myFtdiDevice.Write(dataToWrite, dataToWrite.Length, ref numBytesWritten);

            if (ftStatus == FTDI.FT_STATUS.FT_OK)
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        // funkce, ktera nacte data z FT Rx bufferu, prevede je na Hexovy retezec
        // a tento retezec vlozi na konec globalni promenne readBuffer. Pokud nenacte
        // zadna data vrati false.        
        private bool ReadData()
        {
            UInt32 numBytesAvailable = 0;

            ftStatus = myFtdiDevice.GetRxBytesAvailable(ref numBytesAvailable);
            if (ftStatus != FTDI.FT_STATUS.FT_OK)
            {
                return false;
            }

            if (numBytesAvailable > 0)
            {
                byte[] readDataBytes = new byte[numBytesAvailable];
                UInt32 numBytesRead = 0;

                ftStatus = myFtdiDevice.Read(readDataBytes, numBytesAvailable, ref numBytesRead);
                if (ftStatus != FTDI.FT_STATUS.FT_OK)
                {
                    return false;
                }

                readBuffer += ByteArrayToHex(readDataBytes);
                return true;
            }

            return false;
        }

        // Fce, ktera po dobu waitTime(ms) zjistuje zda byla prijata spravna odpoved(response).
        // Nejprve nacte dostupna data do promenne readBuffer a zjisti jestli obsahuje retezec 
        // response, pokud ano tak je z readBufferu smazano vse pred odpovedi i odpoved a fce
        // vrati true. 
        private bool ResponseReceived(string response, int waitTime)
        {
            int time = 0;
            progressBarSettings.Value = 0;

            while (time < 100)
            {
                if (ReadData())
                {
                    if (readBuffer.Contains(response))
                    {
                        readBuffer = readBuffer.Substring(readBuffer.IndexOf(response) + response.Length);
                        progressBarSettings.Value = 100;
                        return true;
                    }
                }

                progressBarSettings.PerformStep();  // jeden krok progress baru
                time++;
                Thread.Sleep(waitTime / 100);       //uspani cyklu
            }

            StatusMessage("Failed to get correct response bytes ", "bad");
            return false;
        }

        // Fce pro analyzu LMS statusu. Cyklus nejdrive nacita vsechny potrebne znaky (160), maximalne po dobu 2s.
        // Pote si z techto znaku vybere jen ty potrebne (uhly, rozliseni, merici mod, LMS baudrate) a odesle je 
        // funkci ChangeSettingsLabel, ktera provede jejich prenastaveni ve formulari
        private bool AnalyzeStatus()
        {
            int time = 0;
            progressBarSettings.Value = 0;

            // Potrebujeme 296 znaku abychom ziskali cely Status telegram
            // ten ma 161 bytu = 322 znaku, 26 sme jiz odstranili prijmutim
            // odpovedi RESPONSE_LMS_STATUS
            while (readBuffer.Length < 296 || time > 100)
            {
                ReadData();
                progressBarSettings.PerformStep();  // jeden krok progress baru
                time++;
                Thread.Sleep(20);                   //uspani cyklu
            }
            progressBarSettings.Value = 100;

            try
            {
                int angle = HexToInt(readBuffer.Substring(198, 2));
                int resolution = HexToInt(readBuffer.Substring(202, 2));
                int mode = HexToInt(readBuffer.Substring(228, 2));
                string baudRate = readBuffer.Substring(216, 4);

                ChangeSettingsLabels(baudRate, angle, resolution, mode);
            }
            catch (Exception ex)
            {
                return false;
            }

            return true;
        }

        // Fce, ktera nacte potrebne uhly z formulare a spusti scan,
        // pote skenuje dokud neni promena scanRunning nastavena na false. K tomu dojde po dosazeni koncoveho uhlu
        private void StartScan()
        {
            previousVerticalAngle = 91; // nazacatku si nastavime hodnotu vetsi nez muze nastat
            startVerticalAngle = (double)numBoxStartAngle.Value;
            currentVerticalAngle = startVerticalAngle;

            if (checkBoxManualScan.Checked)
            {
                stopVerticalAngle = startVerticalAngle;
            }
            else
            {
                stopVerticalAngle = (double)numBoxStopAngle.Value;
            }

            if (startVerticalAngle > stopVerticalAngle)
            {
                MessageBox.Show("Start Angle must be lower than Stop Angle");
                return;
            }

            switch (comboVerticalResolution.SelectedItem.ToString())
            {
                case "1°":
                    resolutionVerticalAngle = 1;
                    break;

                case "0.5°":
                    resolutionVerticalAngle = 0.5;
                    break;

                default:
                    resolutionVerticalAngle = 1;
                    break;
            }


            // zde bude prikaz pro motor aby najel do pocatecni polohy
            // .... 
            // po obdrzeni potvrzeni poc. polohy od motoru bude nasledovat spusteni scanu

            if (!SendCommand(COMMAND_LMS_START_CONTINUOUS_DATA_OUTPUT))
            {
                StatusMessage("Failed to sent Scan Command!!!", "bad");
                return;
            }

            if (!ResponseReceived(RESPONSE_START_CONTINUOUS_DATA_OUTPUT, 2000))
            {
                StatusMessage("Failed to start Scan!!!", "bad");
                return;
            }

            // a zde bude spusteni pohybu motoru
            //.... 
            //

            //vytvoreni nove instance scanu a pridame hodnoty
            newScan = new Scan();
            scansCount++;
            newScan.scanNo = scansCount;
            newScan.startAngle = startVerticalAngle;
            newScan.stopAngle = stopVerticalAngle;
            newScan.mode = measurementMode;

            progressBarScan.Value = 0;
            scanRunning = true;

            //dokud probiha scan nacitej data
            while (scanRunning)
            {
                GetData();
            }
            
            //hotovo, pridame zaznam do kolekce scans, a seznamu list
            StatusMessage("Scan completed.", "good");

            newScan.rows = newScan.dataArray.Count;
            newScan.dataCount = newScan.rows * dataValuesCount;

            scans.Add(newScan);            

            //pridame zaznam o scanu do ListView            
            ListViewItem listItem;
            listItem = listViewData.Items.Add(newScan.scanNo.ToString());
            listItem.SubItems.Add(newScan.startAngle.ToString() + "°");
            listItem.SubItems.Add(newScan.stopAngle.ToString() + "°");
            listItem.SubItems.Add(newScan.rows.ToString());
            listItem.SubItems.Add(newScan.dataCount.ToString());
        }

        // Nejprve se pokusime nacist dostupna data
        // pokud nactena data obsahuji hlavicku merenych dat
        // a mame nacteno dostatecny pocet znaku pro cely telegram tak telegram spracujem
        // a od motoru si zjistime aktualni uhel pro dalsi namerena data
        private void GetData()
        {            
            if (ReadData())
            {
                if (readBuffer.Contains(dataHeader))
                {
                    if ((readBuffer.Length - readBuffer.IndexOf(dataHeader)) >= (14 + (dataValuesCount * 4) + 2 + 4))
                    {
                        ProcessData();
                        GetCurrentAngle();
                    }
                }
            }
        }

        // Fce ktera zpracuje nactena data vlozi je do kolekce dat
        private bool ProcessData()
        {
            readBuffer = readBuffer.Substring(readBuffer.IndexOf(dataHeader));
            try
            {
                // pokud je predchozi a soucasny uhel stejny data vypustime
                if (currentVerticalAngle != previousVerticalAngle)
                {
                    Data data = new Data();

                    data.verticalAngle = currentVerticalAngle;
                    data.STX = readBuffer.Substring(0, 2);
                    data.ADR = readBuffer.Substring(2, 2);
                    data.LEN = readBuffer.Substring(6, 2) + readBuffer.Substring(4, 2);
                    data.CMD = readBuffer.Substring(8, 2);
                    data.DATALEN = readBuffer.Substring(13, 1) + readBuffer.Substring(10, 2);
                    data.DATA = readBuffer.Substring(14, (dataValuesCount * 4));
                    data.STATUS = readBuffer.Substring(14 + (dataValuesCount * 4), 2);
                    data.CRC = readBuffer.Substring(14 + (dataValuesCount * 4) + 2, 4);

                    newScan.dataArray.Add(data);
                }
                readBuffer = readBuffer.Substring(14 + (dataValuesCount * 4) + 2 + 4);

                // Pokud sme dosahli koncoveho uhlu tak skenovani ukoncime
                if (currentVerticalAngle >= stopVerticalAngle)
                {
                    while (scanRunning)
                    {
                        StopScan();
                    }
                }
            }
            catch (Exception ex)
            {
                StatusMessage("Error in processing data", "bad");
                return false;
            }
            return true;
        }

        // nasledujici 2 fce je nutno dopracovat az bude hotovo ovladani motoru. Zatim v kontinualnim rezimu funguji tak,
        // ze kazdy dalsi vertikal uhel je pouze inkrementaci predchoziho o nastavenou hodnotu Vertical Resolution z formu. (1 nebo 0.5)
        private void GetCurrentAngle()
        {
            previousVerticalAngle = currentVerticalAngle;
            currentVerticalAngle = MotorAngle();
        }

        private double MotorAngle()
        {
            if (stopVerticalAngle == startVerticalAngle)
            {
                progressBarScan.Value = 100;
            }
            else
            {
                double ratio = 100 / (stopVerticalAngle - startVerticalAngle);
                int progressVaule = (int)((currentVerticalAngle - startVerticalAngle) * ratio);
                if (progressVaule > 100)
                {
                    progressVaule = 100;
                }
                progressBarScan.Value = progressVaule;
            }

            // ziskani aktualni uhlu z motoru
            double motorAngle = previousVerticalAngle;
            return motorAngle += resolutionVerticalAngle;

        }

        //Zastaveni scanovani
        private bool StopScan()
        {
            if (!SendCommand(COMMAND_LMS_STOP_CONTINUOUS_DATA_OUTPUT))
            {
                StatusMessage("Error in sending Scan Stop Command", "bad");
                return false;
            }

            if (!ResponseReceived(RESPONSE_STOP_CONTINUOUS_DATA_OUTPUT, 2000))
            {
                 StatusMessage("Failed to stop Scan!!! Trying again...", "bad");
                 return false;
            }

            // zastaveni motoru
            // ...

            scanRunning = false;
            return true;
        }

        #endregion
    }
}