using System;
using System.Text;
using System.Net;
using System.IO;
using System.Net.Sockets;
using System.Threading;
using ProtoBuf;

namespace NetVision {


    /// <summary>
    /// Socket接続を管理するクラス
    /// </summary>
    public partial class Connection {


        #region メンバ

        /// <summary>
        /// サーバ側かクライアント側か
        /// </summary>
        public ServerType IsServer { get; set; }

        Encoding ecUni = Encoding.GetEncoding("utf-16");
        Encoding ecSjis = Encoding.GetEncoding("utf-8");

        //サーバー設定
        TcpClient server = null;
        //サーバーのリスナー設定
        TcpListener listener = null;
        //サーバーのセカンドスレッドの設定
        Thread threadServer = null;

        //クライアント設定
        TcpClient client = null;
        //クライアントのセカンドスレッドの設定
        Thread threadClient = null;

        /// <summary>
        /// IPアドレス
        /// </summary>
        public string IPAddr { get; set; }

        /// <summary>
        /// ポート番号
        /// </summary>
        public int Port { get; set; }


        #endregion

        #region イベント

        /// <summary>
        /// 接続に関するイベント
        /// </summary>
        public EventHandler<MessageEventArgs> SystemMessage;
        protected virtual void OnSystemMessage( MessageEventArgs e ) {
            if (SystemMessage != null) { SystemMessage(this, e); }
        }

        /// <summary>
        /// メッセージの受け取りイベント
        /// </summary>
        public EventHandler<MessageEventArgs> ReceiveMessage;
        protected virtual void OnReceiveMessage( MessageEventArgs e ) {
            if (ReceiveMessage != null) { ReceiveMessage(this, e); }
        }

        /// <summary>
        /// 接続確立イベント
        /// </summary>
        public EventHandler<MessageEventArgs> Connected;
        protected virtual void OnConnected( MessageEventArgs e ) {
            if (Connected != null) { Connected(this, e); }
        }

        /// <summary>
        /// 切断イベント
        /// </summary>
        public EventHandler<MessageEventArgs> UnConnected;
        protected virtual void OnUnConnected( MessageEventArgs e ) {
            if (UnConnected != null) { UnConnected(this, e); }
        }


        #endregion

        #region サーバ

        /// <summary>
        /// サーバ開始
        /// </summary>
        /// <param name="port">ポート番号</param>
        public bool ServerStart( int port ) {
            if (IsServer != ServerType.Undef) {
                OnSystemMessage(new MessageEventArgs("サーバを起動する必要はありません"));
                return false;
            }
            Port = port;
            IsServer = ServerType.Server;
            //TcpListenerを使用してサーバーの接続の確立
            try {
                //lisenerが無い場合
                if (listener == null)
                    listener = new TcpListener(IPAddress.Any, Port);

                //クライアント接続要求の受付開始
                listener.Start();
                OnSystemMessage(new MessageEventArgs("サーバが開始しました。"));

                //スレッドの作成と開始
                threadServer = new Thread(new ThreadStart(ServerListen));
                threadServer.Start();
                return ( true );

            }
            catch (Exception ex) {
                //エラーが起きた
                listener.Stop();
                OnSystemMessage(new MessageEventArgs("サーバ開始エラー:" + ex.Message));
                return ( false );
            }
        }

        /// <summary>
        /// サーバ受信
        /// </summary>
        private void ServerListen() {

            //クライアントの要求があったら、接続を確立する
            //クライアントの要求が有るまでここで待機する 
            server = listener.AcceptTcpClient();

            //ログの書き込みをデリゲートで実行
            OnSystemMessage(new MessageEventArgs("クライアントが接続しました。"));
            OnConnected(new MessageEventArgs("クライアントが接続しました。"));

            //クライアントとの間の通信に使用するストリームを取得
            NetworkStream stream = server.GetStream();

            //受信文字が入る。
            //この数字は少なくても可、少ない場合はLoopが何回か実行される
            Byte[] bytes = new Byte[10000];

            while (true) {
                try {
                    //受信が無い場合はここで待機する
                    //文字受信が有った場合とクライアントが接続を切った場合に
                    //次のステップに進む
                    int intCount = stream.Read(bytes, 0, bytes.Length);


                    if (intCount != 0) {

                        ReceiveData(bytes, intCount);

                    }
                    else {
                        OnSystemMessage(new MessageEventArgs("クライアントが切断されました。"));
                        OnUnConnected(new MessageEventArgs("クライアントが切断されました。"));
                        IsServer = ServerType.Undef;
                        return;
                    }

                }
                catch (System.Threading.ThreadAbortException) {
                    OnUnConnected(new MessageEventArgs("接続を切断しました"));
                    IsServer = ServerType.Undef;
                    return;
                }
                catch (Exception ex) {
                    OnSystemMessage(new MessageEventArgs("受信エラー　　" + ex.Message.ToString()));
                    OnUnConnected(new MessageEventArgs("受信エラー　　" + ex.Message.ToString()));
                    IsServer = ServerType.Undef;
                    return;
                }
            }
        }

        #endregion

        #region クライアント

        /// <summary>
        /// クライアント開始
        /// </summary>
        /// <param name="ipaddr">IPアドレス</param>
        /// <param name="port">ポート番号</param>
        public bool ClientStart( string ipaddr, int port ) {
            if (IsServer != ServerType.Undef) {
                OnSystemMessage(new MessageEventArgs("クライアント接続する必要はありません"));
                return false;
            }

            IsServer = ServerType.Client;
            try {
                //クライアントのソケットを用意
                client = new TcpClient(ipaddr, port);

                //サーバからのデータを受信するループをスレッドで処理
                threadClient = new Thread(new ThreadStart(this.ClientListen));
                threadClient.Start();

                //接続インディケータ
                OnSystemMessage(new MessageEventArgs("サーバに接続しました"));
                OnConnected(new MessageEventArgs("サーバに接続しました"));
                return ( true );
            }
            catch (Exception ex) {
                OnSystemMessage(new MessageEventArgs("クライアント接続エラー:" + ex.Message.ToString()));
                IsServer = ServerType.Undef;
                return ( false );
            }
        }

        /// <summary>
        /// クライアント受信
        /// </summary>
        private void ClientListen() {

            NetworkStream stream = client.GetStream();

            Byte[] bytes = new Byte[10000];

            while (true) {
                try {
                    int intCount = stream.Read(bytes, 0, bytes.Length);
                    if (intCount != 0) {
                        ReceiveData(bytes, intCount);
                    }
                    else {
                        OnSystemMessage(new MessageEventArgs("ホストが切断されました。"));
                        OnUnConnected(new MessageEventArgs("ホストが切断されました。"));
                        IsServer = ServerType.Undef;
                        return;
                    }
                }
                catch (System.Threading.ThreadAbortException) {
                    OnUnConnected(new MessageEventArgs("接続を切断しました"));
                    IsServer = ServerType.Undef;
                    return;
                }
                catch (Exception ex) {
                    OnSystemMessage(new MessageEventArgs("受信エラー　　" + ex.ToString()));
                    OnUnConnected(new MessageEventArgs("受信エラー　　" + ex.ToString()));
                    IsServer = ServerType.Undef;
                    return;
                }
            }
        }

        #endregion

        #region 送受信処理


        /// <summary>
        /// データの受信
        /// </summary>
        /// <param name="bytes">データ</param>
        /// <param name="size">サイズ</param>
        private void ReceiveData( Byte[] bytes, int size ) {
            //受信部分だけ切り出す
            Byte[] getByte = new byte[size];
            for (int i = 0; i < size; i++)
                getByte[i] = bytes[i];
            MemoryStream stream = new MemoryStream(getByte);
            Command com = Serializer.Deserialize<Command>(stream);
            OnReceiveMessage(new MessageEventArgs( com));
        }

        /// <summary>
        /// データの送信
        /// </summary>
        /// <param name="obj">データ</param>
        public void Send( Command obj ) {
            if (IsServer == ServerType.Undef) {
                OnSystemMessage(new MessageEventArgs("接続されていません"));
                return;
            }
            NetworkStream stream = null;
            //サーバーとクライアントを分けて送信
            if (IsServer == ServerType.Server) { stream = server.GetStream(); }
            else if (IsServer == ServerType.Client) { stream = client.GetStream(); }

            Serializer.Serialize<Command>(stream, obj);

        }


        #endregion



        #region 終了処理

        /// <summary>
        /// 切断
        /// </summary>
        public void Close() {
            
            if (IsServer == ServerType.Server) { CloseServer(); }
            else { CloseClient(); }
            IsServer = ServerType.Undef;

        }

        /// <summary>
        /// サーバーのクローズ
        /// </summary>
        private void CloseServer() {
            if(listener != null)
                listener.Stop();
            //サーバーのインスタンスが有って、接続されていたら
            if (server != null && server.Connected)
                server.Close();

            //スレッドは必ず終了させること
            if (threadServer != null)
                threadServer.Abort();

            //インディケータの色を変える            
            //            picIndicator.BackColor = Color.Navy;
            //ログを書き込む
            OnSystemMessage(new MessageEventArgs("サーバーが閉じられました。"));
            //            writeLog("サーバーが閉じられました。");

        }
        
        /// <summary>
        /// クライアントのクローズ
        /// </summary>
        private void CloseClient() {
            //クライアントのインスタンスが有って、接続されていたら
            if (client != null && client.Connected)
                client.Close();

            //スレッドは必ず終了させること
            if (threadClient != null)
                threadClient.Abort();

            //インディケータの色を変える      
            //            picIndicator.BackColor = Color.Navy;
            //ログを書き込む
            OnSystemMessage(new MessageEventArgs("クライアントが閉じられました。"));
            //            writeLog("クライアントが閉じられました。");
        }


        #endregion




    }

    /// <summary>
    /// サーバかクライアントか
    /// </summary>
    public enum ServerType {
        /// <summary>
        /// どちらでもない
        /// </summary>
        Undef,

        /// <summary>
        /// サーバ
        /// </summary>
        Server,

        /// <summary>
        /// クライアント
        /// </summary>
        Client,

    }



    /// <summary>
    /// メッセージイベント引数
    /// </summary>
    public class MessageEventArgs : EventArgs {

        /// <summary>
        /// イベントメッセージ
        /// </summary>
        public string Message { get; set; }

        /// <summary>
        /// 添付するコマンドオブジェクト
        /// </summary>
        public Command Command { get; set; }

        /// <summary>
        /// イベントメッセージに関するコンストラクタ
        /// </summary>
        /// <param name="message">メッセージ</param>
        public MessageEventArgs( string message ) {
            Message = message;
        }

        /// <summary>
        /// コマンドを添付するコンストラクタ
        /// </summary>
        /// <param name="obj"></param>
        public MessageEventArgs( Command obj ) {
            Command = obj;
        }

    }

}
