﻿using UnityEngine;
using System.Collections.Generic;
using System.Net;
using System.Threading;
using System.Net.Sockets;
using System;
using System.Collections;


// Canvas か、専用に作ったGameObjectにアタッチして使うのがよいでしょう。
public class TcpAppServer : MonoBehaviour
{
    public static TcpAppServer instance; // 自身のインスタンスを記憶

    public delegate void OnTcpServerStartDlegate(TcpAppServer server);// サーバ起動で実行するメソッド
    private static void dummyTcpServerStart(TcpAppServer server) { Debug.Log($"onTcpServerStart"); }
    public static OnTcpServerStartDlegate onTcpServerStart = dummyTcpServerStart;

    public delegate void OnAcceptTcpDelegate(TcpCommunication com);// 接続許可で実行するメソッド
    private static void dummyAcceptTcp(TcpCommunication com) { Debug.Log($"onAcceptTcp:[{com.netRemoteString}]"); }
    public static OnAcceptTcpDelegate onAcceptTcp = dummyAcceptTcp;

    public delegate void OnReceiveDelegate(TcpCommunication com, BinPacket messaeg);// 受信で実行するメソッド
    private static void dummyOnReceive(TcpCommunication com, BinPacket msg) { Debug.Log($"[{com.netRemoteString}]:{msg}"); }
    public static OnReceiveDelegate onReceive = dummyOnReceive;

    static string host = System.Net.Dns.GetHostName();  //使用しているマシンのhost名を取得
    public static IPAddress ipAdrs = null;       // サーバIPアドレス

    public TcpListener tcpListener = null; //サーバー用オブジェクト(接続待機)

    // 接続相手のTcpCommunicationの集合を管理
    public List<TcpCommunication> clientlist = new List<TcpCommunication>();
    public Dictionary<string, TcpCommunication> clientmap = new Dictionary<string, TcpCommunication>();

    Thread startServerThread;//using System.Threading;が必要　（クライアントの接続待ち）
    bool isStartServer = false;

    SynchronizationContext context; // 別スレッドからUnitのGUIやGameObjectを操作する時にこれを介す。

    IPHostEntry ipHostEntry = null; //このホストの情報取得                                                             //IPアドレスのリストを取得
    IPAddress[] adrList = null;

    public static bool onGUI_flag = true;// このOnGUIの接続用GUIを利用しない場合にfalse
    public static bool onGUI_flagClient = true;// クライアント接続用のボタン表示
    public static bool onGUI_flagServer = true;// サーバー起動用のボタン表示
    public static float onGUI_startY = 20;// OnGUI部品を並べ始める縦の基準座標
    public static float onGUI_stepY = 40;// OnGUI部品を並べる縦の間隔のパラメタ
    public static float onGUI_height = 60;// OnGUI部品高さのパラメタ
    public static float onGUI_startX = 10;// OnGUI部品を並べ始める横の基準座標
    public static float onGUI_width = 700;// OnGUI部品幅のパラメタ
    public static int onGUI_LabelFontSize = 26;// OnGUI部品文字サイスのパラメタ
    public static int onGUI_ButtonFontSize = 36;// OnGUI部品文字サイスのパラメタ
    public static int onGUI_TextFieldFontSize = 36;// OnGUI部品文字サイスのパラメタ   
    public static string onGUI_message = "";// onGUI 用のメッセージ表示

    void Start()
    {
        instance = this;//自身のインスタンス
        this.context = SynchronizationContext.Current;
        TcpCommunication.context = SynchronizationContext.Current;

        // このホストがIPアドレス情報
        this.ipHostEntry = Dns.GetHostEntry(host); //このホストの情報取得                                                             //IPアドレスのリストを取得
        this.adrList = ipHostEntry.AddressList;

        if(this.adrList == null || adrList.Length == 0)
        {
            onGUI_message = "ネットワーク環境が使えません。";
            return;
        }

        int defalt_net_idx = 0;
        for (int idx = 0; idx < adrList.Length; idx++)// サーバで使えるIPアドレスボタン列挙
        {
            string ip = adrList[idx].ToString();
            if (ip.IndexOf('.') != -1) defalt_net_idx = idx; // 列挙最後IPv4
            Debug.Log($"{ip}");//IPアドレスのボタン
        }
        if (TcpCommunication.target_IP == "")// 未設定であれば列挙最後IPv4をデフォルトにする。
        {
            TcpCommunication.target_IP = this.adrList[defalt_net_idx].ToString(); ;
        }
    }

    // 起動時の操作やデバック用のユーザインターフェイス（onGUI_flag、onGUI_flagClient、onGUI_flagServerで利用範囲指定）
    private void OnGUI()
    {
        if (onGUI_flag == false) return;
        float yPos = onGUI_startY;
        GUI.backgroundColor = Color.cyan;// new Color(0.9f,0.9f,0.9f);
        GUIStyle lableStyle = GUI.skin.GetStyle("label");   // ---Labelスタイル設定
        lableStyle.fontSize = onGUI_LabelFontSize;
        GUIStyle buttonStyle = GUI.skin.GetStyle("button"); // ---Buttonスタイル設定
        buttonStyle.fontSize = onGUI_ButtonFontSize;
        GUI.skin.textField.fontSize = onGUI_TextFieldFontSize; // ---TextFieldスタイル設定
        if (onGUI_flagClient)
        {
            GUI.color = Color.black;//文字色設定
            GUI.Label(new Rect(onGUI_startX, yPos, onGUI_width / 2, onGUI_height), "クライアント名");
            GUI.color = Color.yellow;//文字色設定
            TcpCommunication.clientId = GUI.TextField(
                new Rect(onGUI_width / 2, yPos, onGUI_width / 2, onGUI_height), TcpCommunication.clientId, 20, GUI.skin.textField);
            yPos += onGUI_stepY * 2;

            GUI.color = Color.black;//文字色設定
            GUI.Label(new Rect(onGUI_startX, yPos, onGUI_width, onGUI_height), "以下が接続先サーバのIPアドレス（編集可能）");
            yPos += onGUI_stepY;
            GUI.color = Color.yellow;//文字色設定
            TcpCommunication.target_IP = GUI.TextField(
                new Rect(onGUI_startX, yPos, onGUI_width, onGUI_height), TcpCommunication.target_IP, 50, GUI.skin.textField);
            yPos += onGUI_stepY * 2;
        }
        if (onGUI_flagClient || onGUI_flagServer)
        {
            GUI.color = Color.black;//文字色設定
            GUI.Label(new Rect(onGUI_startX, yPos, onGUI_width / 2, onGUI_height), "TCP接続のポート番号");
            GUI.color = Color.yellow;//文字色設定
            string str_tcp_port = TcpCommunication.tcp_port + "";
            str_tcp_port = GUI.TextField(
                new Rect(onGUI_width / 2, yPos, onGUI_width / 2, onGUI_height), str_tcp_port, 6, GUI.skin.textField);
            try
            {
                TcpCommunication.tcp_port = int.Parse(str_tcp_port);
            }
            catch (Exception) { }
            yPos += onGUI_stepY * 1.5f;
        }
        if (onGUI_flagClient)
        {
            GUI.color = Color.black;//文字色設定
            GUI.Label(new Rect(onGUI_startX, yPos, onGUI_width, onGUI_height), "上記サーバのクライアントとして始める場合は、");
            yPos += onGUI_stepY;
            GUI.Label(new Rect(onGUI_startX, yPos, onGUI_width, onGUI_height), "次の接続ボタンをクリックしてください。");
            yPos += onGUI_stepY;
            GUI.color = Color.yellow;//文字色設定
            if (GUI.Button(new Rect(onGUI_startX, yPos, onGUI_width, onGUI_height), "サーバに接続する"))//IPアドレスのボタン
            {
                TcpCommunication.tcpClientButton();
            }
            yPos += onGUI_stepY * 3;
        }
        if (onGUI_flagServer)
        {
            GUI.color = Color.black;//文字色設定
            GUI.Label(new Rect(onGUI_startX, yPos, onGUI_width, onGUI_height), "サーバーとして始める場合は、以下のIPアドレスボタンから");
            yPos += onGUI_stepY;
            GUI.Label(new Rect(onGUI_startX, yPos, onGUI_width, onGUI_height), "使用するアドレスを選んで、クリックしてください。");
            yPos += onGUI_stepY;

            GUI.color = Color.yellow;//文字色設定
            for (int idx = 0; idx < adrList.Length; idx++, yPos += onGUI_stepY * 1.8f)// サーバで使えるIPアドレスボタン列挙
            {
                if (GUI.Button(new Rect(onGUI_startX, yPos, onGUI_width, onGUI_height), adrList[idx].ToString()))//IPアドレスのボタン
                {
                    ipAdrs = adrList[idx];// サーバに使うIPアドレスを記憶
                    TcpCommunication.target_IP = adrList[idx].ToString();
                    StartServer();//サーバーの起動
                }
            }
        }

        GUI.color = Color.black;//文字色設定
        //this.msgError = "起動しました。";
        if (onGUI_message!="") GUI.Label(new Rect(onGUI_startX, yPos, onGUI_width, onGUI_height), $"Message:{ onGUI_message }");
        yPos += onGUI_stepY*2;

        if (this.clientlist.Count > 0)
        {
            GUI.Label(new Rect(onGUI_startX, yPos, onGUI_width, onGUI_height), $"接続数:{ this.clientlist.Count }");
            yPos += onGUI_stepY;
            TcpCommunication com = this.clientlist[this.clientlist.Count - 1];
            GUI.Label(new Rect(onGUI_startX, yPos, onGUI_width, onGUI_height), $"最後に接続した相手{com.netRemoteString} ");
        }
    }

    // サーバ再起動の準備
    public void ResetTcpServer()
    {
        for( int n=0; n < this.clientlist.Count; n++)
        {
            this.clientlist[n].ResetTcpClient();// 各クライアントオブジェクトをリセット
        }
        if (this.isStartServer)
        {
            tcpListener.Stop();
        }
        this.isStartServer = false;
        tcpListener = null;
    }

    // TCPサーバ起動関連処理（スレッド起動）
    public void StartServer()
    {
        try
        {
            tcpListener = new TcpListener(ipAdrs, TcpCommunication.tcp_port); //サーバー用オブジェクト生成
            tcpListener.Start();
            this.isStartServer = true; //LISTENING 状態
        }
        catch (Exception e)
        {
            onGUI_message = $"サーバ起動失敗:{ipAdrs}:{TcpCommunication.tcp_port}, {e.Message}";
            return;
        }
        this.startServerThread = new Thread(new ThreadStart(StartServerThread));//クライアント待機スレッド
        this.startServerThread.Start();
        onTcpServerStart(this); // サーバー起動時のdelgateメソッド
    }

    // サーバ用待ち受けスレッド
    private void StartServerThread()
    {
        isStartServer = true;
        TcpClient tcpClient = null;
        onGUI_message = $"サーバ{ipAdrs}:{TcpCommunication.tcp_port}待ち受けスレッド起動！";
        try
        {
            while (isStartServer)
            {
                tcpClient = tcpListener.AcceptTcpClient(); //ライアントからの接続要求を受け入れる（接続を待っている）
                onGUI_message = "ライアントからの接続要求を受け入れ！";
                // サーバ側の受信スレッドも起動
                TcpCommunication tcpCommunication = new TcpCommunication(tcpClient);

                this.clientlist.Add(tcpCommunication);
                clientmap.Add(tcpCommunication.netRemoteString, tcpCommunication);
                context.Post((state) =>//  ここで、GUI用の処理の追加コードを可能にする
                {
                    TcpAppServer.onAcceptTcp(tcpCommunication);// delegateメソッド起動
                }, null);
            }
        }
        catch (Exception e)
        {
            onGUI_message = $"Error: StartServerThread: { e.Message }";
            isStartServer = false;
            return;
        }
        Debug.Log("StartServerThread　End");  
    }

    void FixedUpdate()
    {   // 全てのクライアントからの受信処理を実行
        for(int i=0; i < this.clientlist.Count; i++)
        {
            TcpCommunication com = this.clientlist[i];
            BinPacket packet = QueueBuffer.GetBuff(com.queueBuffer);//キューから取り出す
            if (packet != null)
            {
                onReceive(com, packet);// デリゲート受信処理
            }
        }
    }

    public static void SendMessage(TcpCommunication com, string msg)
    {
        instance.StartCoroutine(instance._SetMessage(com, msg));// 非同期処理
    }

    IEnumerator _SetMessage(TcpCommunication com, string msg)
    {
        byte[] buf = TcpCommunication.encoding.GetBytes(msg + "\r\n");
        com.stream.Write(buf, 0, buf.Length);
        yield return null;
    }

    /// オブジェクトが破棄時に実行
    private void OnDestroy()
    {
        Debug.Log("OnDestroy");
        ResetTcpServer();
    }
}
