using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Diagnostics;

namespace shuchancontrol
{
    /// <summary>
    /// チェスプログラム(gnuchessなど)と通信し、人間の手を送りコンピュータの手を得る。
    /// </summary>
    public class ChessProgramTalker : IDisposable
    {
        public readonly string ChessEnginePath;
        public readonly string ChessEngineArguments;
        Queue<string> textMessageFromEngine = new Queue<string>();
        Process engineProcess = new Process();
        /// <summary>
        /// ユーザーへのテキストメッセージをあらわすコマンド名リスト。
        /// </summary>
        List<string> commandOfMessage = new List<string>();

        /// <summary>
        /// ユーザーが最新の手を打ってから受信された"Illegal Move"メッセージ。なければnull。
        /// </summary>
        public string IllegalMoveMessage { get; private set; }
        /// <summary>
        /// ユーザーが最新の手を打ってから受信された"Error"メッセージ。なければnull。
        /// </summary>
        public string ErrorMessage { get; private set; }
        /// <summary>
        /// ユーザーが最新の手を打ってから受信された"Result"メッセージ。なければnull。
        /// </summary>
        public string ResultMessage { get; private set; }
        /// <summary>
        /// ユーザーが最新の手を打ってから受信されたコンピュータの指し手。なければnull。
        /// </summary>
        public string ComputerMove { get; private set; }

        /// <summary>
        /// ユーザーが最新の手を打ってから、ゲームを進展させる応答がチェスエンジンから受信されたかどうか。
        /// </summary>
        public bool HasResponseReceived
        {
            get
            {
                return (IllegalMoveMessage != null) | (ErrorMessage != null) | (ResultMessage != null) | (ComputerMove != null);
            }
        }

        /// <summary>
        /// すでにエンジンを起動しているかどうか(終了してもFalseに戻さない)。
        /// </summary>
        bool onceExecuted = false;

        public ChessProgramTalker(string chessEnginePath, string chessEngineArguments)
        {
            ChessEnginePath = chessEnginePath;
            ChessEngineArguments = chessEngineArguments;

            commandOfMessage.Add("tellopponent");
            commandOfMessage.Add("tellothers");
            commandOfMessage.Add("tellall");
            commandOfMessage.Add("telluser");
            commandOfMessage.Add("tellusererror");
            commandOfMessage.Add("tellics");
            commandOfMessage.Add("tellicsnoalias");
            commandOfMessage.Add("say");
            commandOfMessage.Add("kibitz");

            engineProcess.StartInfo.FileName = ChessEnginePath;
            engineProcess.StartInfo.Arguments = ChessEngineArguments;
            engineProcess.StartInfo.RedirectStandardError = true;
            engineProcess.StartInfo.RedirectStandardInput = true;
            engineProcess.StartInfo.RedirectStandardOutput = true;
            engineProcess.StartInfo.StandardErrorEncoding = Encoding.ASCII;
            engineProcess.StartInfo.StandardOutputEncoding = Encoding.ASCII;
            engineProcess.StartInfo.UseShellExecute = false;
            engineProcess.StartInfo.CreateNoWindow = true;
        }

        void engineProcess_OutputDataReceived(object sender, DataReceivedEventArgs e)
        {
            //コマンドを受信。
            if (!string.IsNullOrEmpty(e.Data))
            {
                string command = e.Data.Trim();
                Trace.WriteLine(command, "Engine output");
                //コマンド処理。
                lock (this)
                {
                    bool iscommandmove = false;
                    bool iscommandillegal = false;
                    bool iscommanderror = false;
                    bool iscommandresult = false;
                    string movenotation = null;
                    if (command.StartsWith("move ", StringComparison.InvariantCultureIgnoreCase))
                    {
                        try
                        {
                            movenotation = command.Split(' ')[1];
                            ComputerMove = movenotation;
                            iscommandmove = true;
                        }
                        catch (Exception)
                        {
                        }
                    }
                    else if (command.StartsWith("my move is: ", StringComparison.InvariantCultureIgnoreCase))
                    {
                        try
                        {
                            movenotation = command.Split(' ')[3];
                            ComputerMove = movenotation;
                            iscommandmove = true;
                        }
                        catch (Exception)
                        {
                        }
                    }
                    else if (command.StartsWith("illegal move", StringComparison.InvariantCultureIgnoreCase))
                    {
                        iscommandillegal = true;
                        IllegalMoveMessage = command.Trim();
                    }
                    else if (command.StartsWith("error", StringComparison.InvariantCultureIgnoreCase))
                    {
                        iscommanderror = true;
                        ErrorMessage = command.Trim();
                    }
                    else if (command.StartsWith("result ", StringComparison.InvariantCultureIgnoreCase))
                    {
                        iscommandresult = true;
                        ResultMessage = command.Substring(command.IndexOf(' ')).Trim();
                    }
                    else if (command.StartsWith("0-1", StringComparison.InvariantCultureIgnoreCase) |
                        command.StartsWith("1-0", StringComparison.InvariantCultureIgnoreCase) |
                        command.StartsWith("1/2-1/2", StringComparison.InvariantCultureIgnoreCase))
                    {
                        iscommandresult = true;
                        ResultMessage = command.Trim();
                    }
                    else if (commandOfMessage.Contains(command.Split(' ')[0].ToLowerInvariant()))
                    {
                        textMessageFromEngine.Enqueue(command.Substring(command.IndexOf(' ')).Trim());
                    }

                }
            }
        }

        void engineProcess_ErrorDataReceived(object sender, DataReceivedEventArgs e)
        {
            //今のところ標準エラー出力は使わない。
            return;
        }

        public bool StartEngine()
        {
            if (!engineProcess.Start())
            {
                return false;
            }

            onceExecuted = true;
            engineProcess.BeginErrorReadLine();
            engineProcess.BeginOutputReadLine();
            engineProcess.ErrorDataReceived += new DataReceivedEventHandler(engineProcess_ErrorDataReceived);
            engineProcess.OutputDataReceived += new DataReceivedEventHandler(engineProcess_OutputDataReceived);


            engineProcess.StandardInput.WriteLine("xboard");    //xboard準拠の通信を開始する。
            engineProcess.StandardInput.WriteLine("new");   //ゲームを初期化する。
            return true;
        }

        public void SendCommand(string command)
        {
            engineProcess.StandardInput.WriteLine(command);
        }

        public void DiscardMessages()
        {
            //IllegalMoveMessage,ErrorMessage,ResultMessage,ComputerMoveを消去する。
            lock (this)
            {
                IllegalMoveMessage = null;
                ErrorMessage = null;
                ResultMessage = null;
                ComputerMove = null;
            }
        }

        public void DiscardCommentMessages()
        {
            lock (this)
            {
                textMessageFromEngine.Clear();
            }
        }

        public void NewGame()
        {
            DiscardMessages();
            DiscardCommentMessages();
            SendCommand("new");
        }

        public void SendUserMove(string move)
        {
            SendCommand(move);
        }

        public void SetBoardWithFEN(string boardstate)
        {
            SendCommand("setboard " + boardstate);
        }

        public void SetThinkingTimePerComputerMove(int second)
        {
            SendCommand("st " + second.ToString());
        }

        public void Resign()
        {
            SendCommand("resign");
        }

        public string ReadCommentFromEngine()
        {
            lock (this)
            {
                if (textMessageFromEngine.Count > 0)
                {
                    return textMessageFromEngine.Dequeue();
                }
                else
                {
                    return null;
                }
            }
        }

        #region IDisposable メンバ

        public void Dispose()
        {
            if ((!engineProcess.HasExited) & onceExecuted)
            {
                engineProcess.StandardInput.WriteLine("quit");
                engineProcess.CancelErrorRead();
                engineProcess.CancelOutputRead();
                if (!engineProcess.WaitForExit(1000))
                {
                    //"quit"でエンジンが終了しなかった。
                    engineProcess.Kill();
                }
                engineProcess.Dispose();
            }
        }

        #endregion
    }
}
