using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.IO;
using System.IO.Ports;
using System.IO.Pipes;
using System.Threading;

namespace WiresharkPipe
{
    public partial class Form1 : Form
    {
        static NamedPipeServerStream wspipe;
        static BinaryWriter ws;
        static SerialPort p;
        const uint BUFSIZE = 2048;
        static byte[] b = new byte[BUFSIZE];
        static uint wr_idx = 0; // zapisovaci index
        static uint rd_idx = 0; // cteci index
        static byte len; // delka ramce
        static FSM state;
        static bool file_write = false;
        static byte byte_ctr;
        static String port;
        string pipe_name; // jmeno roury
        

        enum FSM
        {
            START_CAPTURE,
            PACKET_CAPTURE
        }

        const int PACKET_FCS = 2;
        const int PACKET_LEN = 1;
        //const bool DEBUG_PRINT = true;
        
        
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            string[] names = SerialPort.GetPortNames();
            this.port_comboBox.DataSource = names;
            sniffer_radioButton.Enabled = false;
            idle_radioButton.Enabled = false;
            mod_ok_button.Enabled = false;
            kanal_comboBox.Enabled = false;
            pipe_textBox.Enabled = false;
            
            
        }

        private void nastaveni_ok_button_Click(object sender, EventArgs e)
        {
            if(nastaveni_ok_button.Text.Equals("Připojit")) 
            {
                sniffer_radioButton.Enabled = true;
                idle_radioButton.Enabled = true;
                mod_ok_button.Enabled = true;
                kanal_comboBox.Enabled = true;
                pipe_textBox.Enabled = true;
            port_comboBox.Enabled = false;
            nastaveni_ok_button.Text = ("Odpojit");
            baudrate_textBox.Enabled = false;
            int bd = Convert.ToInt32(baudrate_textBox.Text);
            port = port_comboBox.SelectedItem.ToString();
            
            label6.Text = "COM port připojen";
            try
            {
                p = new SerialPort(port, bd, Parity.None, 8, StopBits.One); // nastaveni pripojeni na seriovy port
                p.Open(); // otevri pripojeni na tento port
                
            }
            catch (IOException)
            {

                Environment.Exit(0);
            }
            }
            else if (nastaveni_ok_button.Text.Equals("Odpojit"))
            {
                baudrate_textBox.Enabled = true;
                port_comboBox.Enabled = true;
                nastaveni_ok_button.Text = ("Připojit");
                p.Close(); // uzavre port
                sniffer_radioButton.Enabled = false;
                idle_radioButton.Enabled = false;
                mod_ok_button.Enabled = false;
                kanal_comboBox.Enabled = false;
                pipe_textBox.Enabled = false;
                
                label6.Text = "COM port odpojen";
            }
        }

        private void mod_ok_button_Click(object sender, EventArgs e)
        {
                   
            if (sniffer_radioButton.Checked == true)
            {
                byte[] uns_ch = new byte[4];
                bool unsupp_chan = true;

                pipe_name = pipe_textBox.Text;
                string cislo_kanalu = Convert.ToString(kanal_comboBox.SelectedItem);
                string chan = "chan " + cislo_kanalu + "\n";
                if (p.IsOpen == false) p.Open();
                p.Write(chan);
                //p.Write("sniff\n");
                
                                
                Thread.Sleep(100); // čas potřebný na příjem všech bajtů z analyzátoru do bufferu PC

                    if (p.BytesToRead != 0) //pokud jsou v bufferu data tak...
                    {
                        for (uint idx = 0; idx < 4; idx++)
                        {
                            uns_ch[idx] = (byte)p.ReadByte(); //..ulož do pole bajtů
                        }
                    }
                /*analyzátor v případě nepodporovaného kanálu vrací string "0x4e"*/
                    if (uns_ch[0] == 0x30 || uns_ch[1] == 0x78 || uns_ch[2] == 0x34 || uns_ch[3]== 0x65)
                    {
                        
                        unsupp_chan = true;
                        /*chybova hlaska "Kanal neni podporovan modulem"*/
                        const string message = "Kanál není podporován modulem!";
                        const string caption = "Chyba";
                        var result = MessageBox.Show(message, caption, MessageBoxButtons.OK, MessageBoxIcon.Error);
                    }
                    else unsupp_chan = false;
                p.DiscardInBuffer(); //vyprázdní buffer
                
                if (mod_ok_button.Text.Equals("Zruš rouru"))
                {
                    p.Write("idle\n");
                    //p.DiscardInBuffer();
                    close();
                    
                    
                    mod_ok_button.Text = ("Vytvoř rourou");
                }

                else if (unsupp_chan == false)
                {
                    p.Write("sniff\n");
                    p.DiscardInBuffer();
                    mod_ok_button.Text = ("Zruš rouru");
                    //vytvor rouru
                    try
                    {
                        wspipe = new NamedPipeServerStream(pipe_name, PipeDirection.Out);
                    }
                    catch (IOException)
                    {
                        
                        p.Close();
                        Environment.Exit(0);
                    }
                    label6.Text = "Čekám na připojení Wiresharku";
                    wspipe.WaitForConnection(); //pockej na pripojeni wiresharku
                    label6.Text = "Wireshark připojen";

                    // propoji binarni zapisovac do roury, aby do ni mohla byt zapsana data
                    ws = new BinaryWriter(wspipe);

                    // vytvori zachytavani dat ze seriove linky
                    p.DataReceived += new SerialDataReceivedEventHandler(serialPort_DataReceived);
                    state = FSM.START_CAPTURE;

                    // volani globalni hlavicky
                    write_global_hdr();


                }

            }
           
            else if (idle_radioButton.Checked==true) 
            {
                if (p.IsOpen == false) p.Open();
                p.Write("idle\n");
                
            }
        }

        private void sniffer_radioButton_CheckedChanged(object sender, EventArgs e)
        {
            kanal_comboBox.Enabled = true;
            pipe_textBox.Enabled = true;
            mod_ok_button.Text = "Vytvoř rouru";
        }

        private void idle_radioButton_CheckedChanged(object sender, EventArgs e)
        {
            kanal_comboBox.Enabled = false;
            pipe_textBox.Enabled = false;
            mod_ok_button.Text = "OK";
        }

        // rutina pro seriovy port. spusteno kdykoliv jsou na portu dostupna data
        static void serialPort_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
        {
            uint frame_len, bytes_in_buf;

            // pracuje vy smycce dokud neni buffer prazdny
            //p.Open();
            while (p.BytesToRead != 0)
            {
                switch (state)
                {
                    case FSM.START_CAPTURE:
                        // zacina se novy ramec. prvni bajt indikuje delku ramce
                        len = (byte)p.ReadByte();
                        b[wr_idx] = len;
                        byte_ctr = 0;

                        // inkrementuj index v poli bufferu a vrat se zpatky na 0 pokud se prekroci max. hodnota buffer (BUFSIZE)
                        wr_idx = (wr_idx + 1) % BUFSIZE;
                        state = FSM.PACKET_CAPTURE;
                        break;

                    case FSM.PACKET_CAPTURE:
                        // zachytavej bajty az do velikosti len
                        b[wr_idx] = (byte)p.ReadByte();
                        wr_idx = (wr_idx + 1) % BUFSIZE;
                        byte_ctr++;

                        // zachytily se vsechny bajty ramce. rekni dalsi sekci ze jsme pripraveni zapisovat do roury
                        if (byte_ctr == (len - 1))
                        {
                            state = FSM.START_CAPTURE;
                            file_write = true;
                        }
                        break;
                }

                // pokud byl alespon jeden ramec zachycen zapis ho do roury
                while (file_write == true)
                {
                    frame_len = b[rd_idx];
                    bytes_in_buf = calc_bytes_in_buf();

                    // porovna delku ramce k poctu bajtu v bufferu. pokud pocet bajtu v bufferu je vetsi nez delka ramce,
                    // tak mame vice nez jeden ramec. cykli dokud vsechny ramce nejsou zapsany do roura
                    if (bytes_in_buf > frame_len)
                    {
                        //vice nez jeden ramec v bufferu. neindikuj konec dokud nejsou vyslany vsechny ramce do roury
                        write_frame(frame_len);
                    }
                    else if (bytes_in_buf == frame_len)
                    {
                        //pouze jeden ramec v bufferu. indikuj konec a muze se vyslat ramec do roury
                        write_frame(frame_len);
                        file_write = false;

                    }
                    else
                    {
                        // zadne ramec v bufferu? toto se nemelo stat. chyba
                        file_write = false;
                    }
                }

            }
        }
        // vraci pocet bajtu v bufferu
        static uint calc_bytes_in_buf()
        {
            if (rd_idx > wr_idx)
            {
                // jelikoz je pouzit kruhovy buffer, je mozne ze zapisovaci index je mensi nez cteci index. oprav to
                return (BUFSIZE - (rd_idx - wr_idx));
            }
            else
            {
                // vypocitej pocet bajtu v bufferu
                return (wr_idx - rd_idx);
            }
        }

        // globalni hlavicka, ktera uvozuje zachytavani dat ve wiresharku. toto rekne wiresharku
        // jaky protokol je pouzit na linkove vrstve, endianitu dat, verze dat...
        static void write_global_hdr()
        {
            uint magic_num = 0xa1b2c3d4;    // endianita (little-endian)
            short version_major = 2;        // verze
            short version_minor = 4;        // verze
            int thiszone = 0;               // casova zona (nepouzito)
            uint sigfigs = 0;               // (nepouziti)
            uint snaplen = 65535;           // max. delka ramce
            uint network = 195;             // Data Link Type (DLT): protokol linkove vrstvy

            try
            {
                // zapis toto do roury
                ws.Write(magic_num);
                ws.Write(version_major);
                ws.Write(version_minor);
                ws.Write(thiszone);
                ws.Write(sigfigs);
                ws.Write(snaplen);
                ws.Write(network);
            }
            catch
            {
                
                close();
            }
        }

        // hlavicka ramce v libpcap/winpcap formatu. obsahuje casove razitko a delku dat
        static void write_frm_hdr(long sec, long usec, uint incl_len, uint orig_len)
        {
            try
            {
                // zapis do roury
                ws.Write((uint)sec);
                ws.Write((uint)usec);
                ws.Write(incl_len);
                ws.Write(orig_len);
            }
            catch
            {
                
                close();
            }
        }

        // zapis ramec do roury. 
        static void write_frame(uint frame_len)
        {
            uint incl_len, orig_len;
            int sec, usec;
            double tstamp;
            long t_sec, t_usec;

            // vypocita delku ramce 802.15.4. bez FCS a casoveho razitka
            incl_len = (uint)frame_len - PACKET_FCS - 8 ;
            orig_len = frame_len;

            //zmeni endianitu bajtů casoveho razitka
            sec = ((int)b[(rd_idx + 4) % BUFSIZE] << 24 | (int)b[(rd_idx + 3) % BUFSIZE] << 16 | (int)b[(rd_idx + 2) % BUFSIZE] << 8 | (int)b[(rd_idx + 1) % BUFSIZE]);
            usec = ((int)b[(rd_idx + 8) % BUFSIZE] << 24 | (int)b[(rd_idx + 7) % BUFSIZE] << 16 | (int)b[(rd_idx + 6) % BUFSIZE] << 8 | (int)b[(rd_idx + 5) % BUFSIZE]);
            
            //prepocita na sekundy a mikrosekundy
            tstamp = (((double)sec * 65536) + usec) / 8000000;
            t_sec = (long)tstamp;
            t_usec = (long)((tstamp - t_sec) * 1.0e6);

            // inkrementace cteciho indexu pres casove razitko a delku
            rd_idx = (rd_idx + 9) % BUFSIZE;

            // zapis hlavicku ramce
            write_frm_hdr(t_sec, t_usec, incl_len, orig_len);

            // cykli dokud se nedosahne posledniho bajtu
            for (int i = 0; i < incl_len; i++)
            {
                try
                {
                    ws.Write(b[rd_idx]);
                }
                catch
                {
                    
                    close();
                }
                rd_idx = (rd_idx + 1) % BUFSIZE;
            }
            rd_idx = (rd_idx + 1) % BUFSIZE;
        }

        // uzavre vsechny metody
        static void close()
        {
            p.Close();
            wspipe.Close();
            ws.Close();
           
        }

        



    }
}
