using System.Net.Sockets;
using System.Net;
using System.Collections.Concurrent;
using System.Net.NetworkInformation;
using System.Text.RegularExpressions;
using System.IO;
using System.Text;
using Microsoft.VisualBasic;
using System.Diagnostics;
using Microsoft.VisualBasic.Devices;
using System.Threading;
using System.Runtime.InteropServices;
using static System.Net.WebRequestMethods;
using System.Reflection;

[StructLayout(LayoutKind.Sequential)]
public struct DeviceInfo
{
    public string SSID;
    public string MacAdrs;
    public string Mode;
    public string Version;
    public string IPAdrs;
}

namespace RS_WF62_terminal_CS
{
    public partial class RSWF62 : Form
    {
        private NetworkStream mStream;
        private TcpClient mClient;
        private Thread LEDMsgThread;
        private Thread LEDThread;
        private Thread DeviceScanMsgThread;
        private Thread DeviceScanThread;
        public static UdpClient rcvClient;
        public FrmScan FrmScanMsg;
        public FrmLED FrmLEDMsg;
        public static EventWaitHandle WaitHandle;


        /// MACアドレス・IPアドレス・バージョンを保管するリスト
        public static ConcurrentDictionary<int, DeviceInfo> foundList = new ConcurrentDictionary<int, DeviceInfo>();

        delegate void EnableBtnCallback(System.Windows.Forms.Button Btn, bool fEnable);
        private void EnableBtn(System.Windows.Forms.Button Btn, bool fEnable)
        {
            // 呼び出し元のコントロールのスレッドが異なるか確認をする
            if (Btn.InvokeRequired)
            {
                // 同一メソッドへのコールバックを作成する
                EnableBtnCallback delegateMethod = new EnableBtnCallback(EnableBtn);

                // コントロールの親のInvoke()メソッドを呼び出すことで、呼び出し元の
                // コントロールのスレッドでこのメソッドを実行する
                this.BeginInvoke(delegateMethod, new object[] { Btn, fEnable });
            }
            else
            {
                // コントロールを直接呼び出す
                Btn.Enabled = fEnable;
            }
        }

        delegate void SetLstCallback(ListView Lst, string str0, string str1, string str2, string str3, string str4);
        private void SetLst(ListView Lst, string str0, string str1, string str2, string str3, string str4)
        {
            // 呼び出し元のコントロールのスレッドが異なるか確認をする
            if (Lst.InvokeRequired)
            {
                // 同一メソッドへのコールバックを作成する
                SetLstCallback delegateMethod = new SetLstCallback(SetLst);

                // コントロールの親のInvoke()メソッドを呼び出すことで、呼び出し元の
                // コントロールのスレッドでこのメソッドを実行する
                this.BeginInvoke(delegateMethod, new object[] { Lst, str0, str1, str2, str3, str4 });
            }
            else
            {
                // コントロールを直接呼び出す
                var lvitem = Lst.Items.Add(str0);
                lvitem.Tag = str1;
                lvitem.ToolTipText = str1;
                lvitem.SubItems.Add(str2);
                lvitem.SubItems.Add(str3);
                lvitem.SubItems.Add(str4);
            }
        }

        delegate void ClearLstCallback(ListView Lst);
        private void ClearLst(ListView Lst)
        {
            // 呼び出し元のコントロールのスレッドが異なるか確認をする
            if (Lst.InvokeRequired)
            {
                // 同一メソッドへのコールバックを作成する
                ClearLstCallback delegateMethod = new ClearLstCallback(ClearLst);

                // コントロールの親のInvoke()メソッドを呼び出すことで、呼び出し元の
                // コントロールのスレッドでこのメソッドを実行する
                this.BeginInvoke(delegateMethod, new object[] { Lst });
            }
            else
            {
                // コントロールを直接呼び出す
                Lst.Items.Clear();
            }
        }

        delegate void SetCmbCallback(ComboBox Cmb, string str1);
        private void SetCmb(ComboBox Cmb, string str1)
        {
            // 呼び出し元のコントロールのスレッドが異なるか確認をする
            if (Cmb.InvokeRequired)
            {
                // 同一メソッドへのコールバックを作成する
                SetCmbCallback delegateMethod = new SetCmbCallback(SetCmb);

                // コントロールの親のInvoke()メソッドを呼び出すことで、呼び出し元の
                // コントロールのスレッドでこのメソッドを実行する
                this.BeginInvoke(delegateMethod, new object[] { Cmb, str1 });
            }
            else
            {
                // コントロールを直接呼び出す
                Cmb.Items.Add(str1);
                // 選択デバイス
                Cmb.SelectedIndex = 0;
            }
        }

        delegate void ClearCmbCallback(ComboBox Cmb);
        private void ClearCmb(ComboBox Cmb)
        {
            // 呼び出し元のコントロールのスレッドが異なるか確認をする
            if (Cmb.InvokeRequired)
            {
                // 同一メソッドへのコールバックを作成する
                ClearCmbCallback delegateMethod = new ClearCmbCallback(ClearCmb);

                // コントロールの親のInvoke()メソッドを呼び出すことで、呼び出し元の
                // コントロールのスレッドでこのメソッドを実行する
                this.BeginInvoke(delegateMethod, new object[] { Cmb });
            }
            else
            {
                // コントロールを直接呼び出す
                Cmb.Items.Clear();
            }
        }


        public RSWF62()
        {
            InitializeComponent();
        }

        // 初期設定
        private void RSWF62_Load(object sender, EventArgs e)
        {
            // イベントハンドル
            WaitHandle = new EventWaitHandle(false, EventResetMode.ManualReset);
            WaitHandle.Reset();

            // スキャン
            ReScan();
        }

        // 終了処理
        private void RSWF62_FormClosing(object sender, FormClosingEventArgs e)
        {
            if ((FrmScanMsg != null) && (!FrmScanMsg.IsDisposed))
            {
                // 検索メッセージ終了
                FrmScanMsg.Close();
                FrmScanMsg.Dispose();
            }
            if ((FrmLEDMsg != null) && (!FrmLEDMsg.IsDisposed))
            {
                // LEDメッセージ終了
                FrmLEDMsg.Close();
                FrmLEDMsg.Dispose();
            }

            // イベントクローズ
            WaitHandle.Close();
        }

        // 再スキャン
        private void BtnReScan_Click(object sender, EventArgs e)
        {
            // スキャン
            ReScan();
        }

        // 終了
        private void BtnExit_Click(object sender, EventArgs e)
        {
            // クローズ
            this.Close();
        }

        // LED確認
        private void BtnLED_Click(object sender, EventArgs e)
        {
            int timeout = 3000;

            if (LstDevice.Items.Count > 0)
            {
                // 何も選択してないなら終了
                if (LstDevice.SelectedIndices.Count == 0)
                {
                    MessageBox.Show("デバイスを選択していません");
                    return;
                }

                // ボタン無効
                EnableBtn(BtnReScan, false);
                EnableBtn(BtnLED, false);
                EnableBtn(BtnTCPIP, false);
                EnableBtn(BtnComPort, false);
                EnableBtn(BtnExit, false);

                IPAddress ip = IPAddress.Parse(LstDevice.SelectedItems[0].SubItems[1].Text);
                int port = 5074;

                // IPアドレスとポート番号からソケット・アドレスを作成
                mClient = new TcpClient();
                mClient.ReceiveTimeout = mClient.SendTimeout = timeout;

                var result = mClient.BeginConnect(ip, port, null, null);
                var success = result.AsyncWaitHandle.WaitOne(TimeSpan.FromMilliseconds(timeout));

                if (!success)
                {
                    // ボタン有効
                    EnableBtn(BtnReScan, true);
                    EnableBtn(BtnLED, true);
                    EnableBtn(BtnTCPIP, true);
                    EnableBtn(BtnComPort, true);
                    EnableBtn(BtnExit, true);

                    // 失敗、例外を投げる
                    return;
                }

                // ソケットへの接続
                // ソケットへ接続中
                if (mClient.Connected)
                {
                    //return;
                }

                // 出力ストリーム取得
                if (mStream == null)
                {
                    mStream = mClient.GetStream();
                }

                // LEDスレッド
                LEDThread = new Thread(LEDProc);
                LEDThread.Start();
            }
            else
            {
                MessageBox.Show("デバイスがありません");
            }
        }

        // LED
        private void LEDProc()
        {
            byte[] writeBuf;
            byte[] resultBuf;

            // LEDメッセージスレッド
            LEDMsgThread = new Thread(LEDMsgProc);
            LEDMsgThread.Start();

            try
            {
                // コマンドモードに変更
                writeBuf = new byte[3 + 2];
                //writeBuf = Encoding.ASCII.GetBytes("$$$\r\n");
                writeBuf[0] = 0x24;// '$'
                writeBuf[1] = 0x24;// '$'
                writeBuf[2] = 0x24;// '$'
                writeBuf[3] = 0x0d;
                writeBuf[4] = 0x0a;
                // 送信
                writeCommand(mStream, writeBuf);
                // レスポンスデータの受信
                resultBuf = readResponse(mStream);

                // 3回リトライ
                for (int iii = 0; iii < 3; iii++)
                {
                    writeBuf = new byte[12 + 3 + 2];
                    writeBuf[0] = 0x73;// 's'
                    writeBuf[1] = 0x65;// 'e'
                    writeBuf[2] = 0x74;// 't'
                    writeBuf[3] = 0x20;// ' '
                    writeBuf[4] = 0x73;// 's'
                    writeBuf[5] = 0x79;// 'y'
                    writeBuf[6] = 0x73;// 's'
                    writeBuf[7] = 0x20;// ' '
                    writeBuf[8] = 0x6C;// 'l'
                    writeBuf[9] = 0x65;// 'e'
                    writeBuf[10] = 0x64;// 'd'
                    writeBuf[11] = 0x20;// ' '
                    writeBuf[12] = 0x30;// 3つで
                    writeBuf[13] = 0x78;// [0x2]
                    writeBuf[14] = 0x32;// とする
                    writeBuf[15] = 0x0d;
                    writeBuf[16] = 0x0a;
                    // 送信
                    writeCommand(mStream, writeBuf);
                    Thread.Sleep(500);
                    // レスポンスデータの受信
                    resultBuf = readResponse(mStream);

                    Thread.Sleep(1000);

                    writeBuf = new byte[12 + 3 + 2];
                    writeBuf[0] = 0x73;// 's'
                    writeBuf[1] = 0x65;// 'e'
                    writeBuf[2] = 0x74;// 't'
                    writeBuf[3] = 0x20;// ' '
                    writeBuf[4] = 0x73;// 's'
                    writeBuf[5] = 0x79;// 'y'
                    writeBuf[6] = 0x73;// 's'
                    writeBuf[7] = 0x20;// ' '
                    writeBuf[8] = 0x6C;// 'l'
                    writeBuf[9] = 0x65;// 'e'
                    writeBuf[10] = 0x64;// d'
                    writeBuf[11] = 0x20;// ' '
                    writeBuf[12] = 0x30;// 3つで
                    writeBuf[13] = 0x78;// [0x3]
                    writeBuf[14] = 0x33;// とする
                    writeBuf[15] = 0x0d;
                    writeBuf[16] = 0x0a;
                    // 送信
                    writeCommand(mStream, writeBuf);
                    Thread.Sleep(500);
                    // レスポンスデータの受信
                    resultBuf = readResponse(mStream);

                    Thread.Sleep(1000);
                }

                // モードを戻す
                writeBuf = new byte[4 + 2];
                //writeBuf = Encoding.ASCII.GetBytes("exit\r\n");
                writeBuf[0] = 0x65;// 'e'
                writeBuf[1] = 0x78;// 'x'
                writeBuf[2] = 0x69;// 'i'
                writeBuf[3] = 0x74;// 't'
                writeBuf[4] = 0x0d;
                writeBuf[5] = 0x0a;
                // 送信
                writeCommand(mStream, writeBuf);
                // レスポンスデータの受信
                resultBuf = readResponse(mStream);

                // ストリームクローズ
                if (mStream != null)
                {
                    mStream.Dispose();
                    mStream = null;
                }
                // ソケットクローズ
                if (mClient != null)
                {
                    mClient.Dispose();
                    mClient = null;
                }

                // ボタン有効
                EnableBtn(BtnReScan, true);
                EnableBtn(BtnLED, true);
                EnableBtn(BtnTCPIP, true);
                EnableBtn(BtnComPort, true);
                EnableBtn(BtnExit, true);

                // LEDメッセージ終了
                FrmLEDMsg.Close();
                FrmLEDMsg.Dispose();

                // LEDメッセージスレッド終了
                LEDMsgThread.Interrupt();
                LEDMsgThread.Join();
            }
            catch (Exception ex)
            {
                // ボタン有効
                EnableBtn(BtnReScan, true);
                EnableBtn(BtnLED, true);
                EnableBtn(BtnTCPIP, true);
                EnableBtn(BtnComPort, true);
                EnableBtn(BtnExit, true);

                // LEDメッセージ終了
                FrmLEDMsg.Close();
                FrmLEDMsg.Dispose();

                // LEDメッセージスレッド終了
                LEDMsgThread.Interrupt();
                LEDMsgThread.Join();
                return;
            }
        }

        // LEDメッセージ
        private void LEDMsgProc()
        {
            FrmLEDMsg = new FrmLED();
            FrmLEDMsg.ShowDialog();
        }


        public static bool writeCommand(Stream mOutputStream, byte[] writeBuf)
        {
            // バッファが空でない
            if (writeBuf != null)
            {
                try
                {
                    if (mOutputStream != null)
                    {
                        // 指定されたバイト配列を出力ストリームに書き込む
                        mOutputStream.Write(writeBuf, 0, writeBuf.Length);
                        // 出力ストリームを書き込む
                        //mOutputStream.Flush();
                        return true;
                    }
                }
                catch (Exception ex)
                {
                    Debug.WriteLine(ex.Message);
                }
            }
            return false;
        }

        public static byte[] readResponse(NetworkStream mInputStream)
        {
            int TotalCount = 0;

            using (var memStream = new MemoryStream())
            {
                try
                {
                    if (mInputStream != null)
                    {
                        byte[] rcvBuf = new byte[256];
                        int readCount;
                        mInputStream.ReadTimeout = 1000;
                        while ((readCount = mInputStream.Read(rcvBuf, 0, rcvBuf.Length)) > 0)
                        {
                            //受信したデータを蓄積する
                            memStream.Write(rcvBuf, TotalCount, rcvBuf.Length);
                            TotalCount += readCount;
                        }
                        return memStream.ToArray();
                    }
                }
                catch (IOException ioe)
                {
                    // データがあれば
                    if (TotalCount > 0)
                        return memStream.ToArray();

                    Debug.WriteLine(ioe.Message);
                }
                catch (SocketException se)
                {
                    // データがあれば
                    if (TotalCount > 0)
                        return memStream.ToArray();

                    Debug.WriteLine(se.Message);
                }
                catch (Exception ex)
                {
                    // データがあれば
                    if (TotalCount > 0)
                        return memStream.ToArray();

                    Debug.WriteLine(ex.Message);
                }
                return null;
            }
        }

        // TCP/IP接続
        private void BtnTCPIP_Click(object sender, EventArgs e)
        {
            if (LstDevice.Items.Count > 0)
            {
                // 何も選択してないなら終了
                if (LstDevice.SelectedIndices.Count == 0)
                {
                    MessageBox.Show("デバイスを選択していません");
                    return;
                }


                Terminal FrmTerminal = new Terminal();
                // 非表示
                this.Visible = false;

                // TCP/IPモード
                FrmTerminal.IPAdrs = LstDevice.SelectedItems[0].SubItems[1].Text;
                FrmTerminal.connectType = 0;
                FrmTerminal.ShowDialog();

                // 表示
                this.Visible = true;
            }
            else
            {
                MessageBox.Show("デバイスがありません");
            }
        }

        // COMポート接続
        private void BtnComPort_Click(object sender, EventArgs e)
        {
            Terminal FrmTerminal = new Terminal();
            // 非表示
            this.Visible = false;

            // COMモード
            FrmTerminal.PortName = CmbComPort.GetItemText(CmbComPort.SelectedItem);
            FrmTerminal.connectType = 1;
            FrmTerminal.ShowDialog();

            // 表示
            this.Visible = true;
        }

        // スキャン準備
        private void ReScan()
        {
            // デバイス名削除
            TxtSelectDevice.Text = string.Empty;
            // ボタン無効
            EnableBtn(BtnReScan, false);
            EnableBtn(BtnLED, false);
            EnableBtn(BtnTCPIP, false);
            EnableBtn(BtnComPort, false);
            EnableBtn(BtnExit, false);

            // 検索スレッド
            DeviceScanThread = new Thread(DeviceScanProc);
            DeviceScanThread.Start();
        }

        // スキャンメッセージ
        private void DeviceScanMsgProc()
        {
            try
            {
                FrmScanMsg = new FrmScan();
                FrmScanMsg.ShowDialog();
            }
            catch (ThreadInterruptedException ex)
            {

            }
        }

        // スキャン
        private void DeviceScanProc()
        {
            // 検索メッセージスレッド
            DeviceScanMsgThread = new Thread(DeviceScanMsgProc);
            DeviceScanMsgThread.Start();

            // TCO/IP検索
            SearchDevice();
            // COM検索
            SearchCom();

            // ボタン有効
            EnableBtn(BtnReScan, true);
            EnableBtn(BtnLED, true);
            EnableBtn(BtnTCPIP, true);
            EnableBtn(BtnComPort, true);
            EnableBtn(BtnExit, true);

            // 検索メッセージ終了
            FrmScanMsg.Close();
            FrmScanMsg.Dispose();

            // 検索メッセージスレッド終了
            DeviceScanMsgThread.Interrupt();
            DeviceScanMsgThread.Join();
        }

        // TCO/IP検索
        private void SearchDevice()
        {
            bool fCheck = false;
            int timeCount;
            DeviceInfo DevInfo;
            int id;

            // リスト初期化
            ClearLst(LstDevice);
            WaitHandle.Reset();

            // 登録リストを初期化
            foundList = new ConcurrentDictionary<int, DeviceInfo>();

            // UDP受信用のクライアントを立ち上げる
            Task udpReceiveTask = Task.Run(() => udpReceive(true));

            // イベント待ち
            WaitHandle.WaitOne();

            timeCount = 0;
            while (timeCount < 10)
            {
                // UDP受信でデータ取得していれば抜ける
                foreach (KeyValuePair<int, DeviceInfo> device in foundList)
                {
                    fCheck = true;

                    id = device.Key;
                    DevInfo = device.Value;
                    // リストに追加
                    string strSSID = DevInfo.SSID;
                    string strMac = DevInfo.MacAdrs;
                    string strVer = DevInfo.Version;
                    string strMode = DevInfo.Mode;
                    string strIPAdrs = DevInfo.IPAdrs;


                    // 受信したデータ表示
                    SetLst(LstDevice, strSSID, strMac, strIPAdrs, strMode, strVer);
                }
                if (fCheck)
                    break;
                Thread.Sleep(1000);
                timeCount++;
            }
        }

        /// UDPでデータを受信し、端末リストとしてIPアドレス・MACアドレスに格納する
        public static void udpReceive(bool getWiFiMode)
        {
            DeviceInfo DevInfo;
            int timeCount;
            int id;
            Boolean Status;
            string strdata1;
            string strdata2;
            string strdata3;
            string strdata4;
            int FirstIP = 0;

            try
            {
                // UDP設定
                rcvClient = new UdpClient(new IPEndPoint(IPAddress.Any, 5073));
                rcvClient.Client.ReceiveTimeout = 20000;

                timeCount = 0;
                while (true)
                {
                    IPEndPoint remoteEP = new IPEndPoint(IPAddress.Any, 0);
                    // リクエストを受信
                    var result = rcvClient.Receive(ref remoteEP);
                    // ポート5073の場合
                    if (remoteEP.Port == 5073)
                    {
                        byte[] rcvData = result;
                        int iii;
                        int StartPos = 0;
                        int EndPos = 0;
                        for (iii = 0; iii < rcvData.Length; iii++)
                        {
                            if (rcvData[iii] == 0x56)// 'V'
                                if (rcvData[iii + 1] == 0x65)// 'e'
                                    if (rcvData[iii + 2] == 0x72)// 'r'
                                        StartPos = iii + 3;
                        }
                        for (iii = StartPos; iii < rcvData.Length; iii++)
                        {
                            if (rcvData[iii] == 0x2C)// ','
                                EndPos = iii;
                        }
                        byte[] Verbyte = new byte[EndPos - StartPos];
                        for (iii = StartPos; iii < EndPos; iii++)
                        {
                            Verbyte[iii - StartPos] = rcvData[iii];
                        }
                        string strVer = Encoding.ASCII.GetString(Verbyte);
                        string strMode; ;
                        // 
                        if (rcvData[6] == 0x00)
                            strMode = "インフラストラクチャ";
                        else if (rcvData[6] == 0x01)
                            strMode = "アクセスポイント";
                        else
                            strMode = "Provisioning";
                        string strMac = string.Format("{0:X2}:{1:X2}:{2:X2}:{3:X2}:{4:X2}:{5:X2}:", rcvData[0], rcvData[1], rcvData[2], rcvData[3], rcvData[4], rcvData[5]);

                        string strSSID = Encoding.ASCII.GetString(rcvData, 0x6A, 32);


                        DevInfo.SSID = strSSID;
                        DevInfo.MacAdrs = strMac;
                        DevInfo.Mode = strMode;
                        DevInfo.Version = strVer;
                        DevInfo.IPAdrs = remoteEP.Address.ToString();

                        strdata1 = DevInfo.IPAdrs;
                        // 1つめ検索
                        StartPos = strdata1.IndexOf(".");
                        strdata2 = strdata1.Substring(StartPos + 1, strdata1.Length - StartPos - 1);
                        // 2つめ検索
                        StartPos = strdata2.IndexOf(".");
                        strdata3 = strdata2.Substring(StartPos + 1, strdata2.Length - StartPos - 1);
                        // 3つめ検索
                        StartPos = strdata3.IndexOf(".");
                        strdata4 = strdata3.Substring(StartPos + 1, strdata3.Length - StartPos - 1);

                        // IPAdrsの最後をIdとする
                        id = int.Parse(strdata4);

                        if (FirstIP == 0)
                            FirstIP = id;
                        else
                        {
                            // 同じIPアドレスから受信
                            if (id == FirstIP)
                                break;
                        }
                        
                        // MACアドレス/接続先IP/バージョンをリストに格納
                        Status = foundList.TryAdd(id, DevInfo);
                    }

                    timeCount++;
                    if (timeCount > 10 )
                    {
                        break;
                    }
                }

                // クローズ
                rcvClient.Close();

                // イベント設定
                WaitHandle.Set();
            }
            catch (ObjectDisposedException)
            {
                // 外部で破棄(=切断)されている場合何もしない
                // イベント設定
                WaitHandle.Set();
            }
            catch (NullReferenceException nre)
            {
                //PlusDebug.WriteLine(nre.Message);
                // イベント設定
                WaitHandle.Set();
            }
            catch (SocketException se)
            {
                //PlusDebug.WriteLine(se.Message);
                // イベント設定
                WaitHandle.Set();
            }
        }

        private void LstDevice_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (LstDevice.SelectedItems.Count > 0)
            {
                // 何も選択してないなら終了
                if (LstDevice.SelectedItems[0].Index < 0)
                    return;

                // デバイス名表示
                TxtSelectDevice.Text = LstDevice.SelectedItems[0].SubItems[0].Text;
            }
            else
            {
                // デバイス名削除
                TxtSelectDevice.Text = string.Empty;
            }
        }

        // COM検索
        private void SearchCom()
        {
            // コンボボックス初期化
            ClearCmb(CmbComPort);

            // レジストリから検索
            //キーを読み取り専用で開く
            Microsoft.Win32.RegistryKey regkey =
                Microsoft.Win32.Registry.LocalMachine.OpenSubKey("HARDWARE\\DEVICEMAP\\SERIALCOMM", false);
            //キーが存在しないときは null が返される
            if (regkey != null)
            {
                //文字列を読み込む
                string[] stringValue = regkey.GetValueNames();

                foreach (string k in stringValue)
                {
                    string strings = (string)regkey.GetValue(k);
                    // 追加
                    SetCmb(CmbComPort, strings);
                }
                // 閉じる
                regkey.Close();
            }
        }
    }
}
