using System;
using System.Threading;
using System.ServiceModel;
using System.Text;

//サンプルコードでは、個々のクラスがどの名前空間・アセンブリに
//属しているのか表現するため、あえてusingは使用しない。
//using UnifiedPOS.POSPrinter;

namespace Console_POSPrinterConsumerLongPollingSample
{
    /// <summary>
    /// POSPrinterの使用方法を説明する
    /// </summary>
    class POSPrinterTest
    {
        // このアプリケーションのためのコンシューマIDを定義する
        string consumerID = Guid.NewGuid().ToString();

        /// <summary>
        /// POSPrinterの使用方法を説明する
        /// </summary>
        public void Test()
        {
            // WS-POSを利用するための準備
            // チャネルの作成、WS-POSセッションの開始、
            // KeepAliveの開始、ロングポーリングの開始

            // WCFを使用してWS-POSサービスプロバイダに接続するための準備
            var channelFactory = new ChannelFactory<UnifiedPOS.POSPrinter.V1_4.POSPrinter>("POSPrinterPort");

            // POSPrinter WS-POSサービスプロバイダへのチャネル
            var posprinterChannel = channelFactory.CreateChannel();

            // WS-POS プロバイダのセッションを開始する。ロングポーリングのため、Endpointにnullを渡す
            // (自己ホスト（双方向通信）では、イベント受信用のエンドポイントを渡す）
            posprinterChannel.OpenSession(consumerID, null);

            // Keep AliveのためにWS-POSサービスプロバイダのセッションタイムアウト時間(秒)を取得
            var providerSessionTimoutSec = posprinterChannel.GetProviderSessionTimeout(consumerID);

            // Keep Aliveを開始する。タイマーのPeriodには、サービスプロバイダセッションタイムアウトの半分の指定を推奨
            var keepAliveTimer = new Timer(new TimerCallback(KeepAliveTimer_TimerCallback), posprinterChannel, 0, providerSessionTimoutSec * 1000 / 2);

            // ロングポーリング用にスレッドを作成して開始する
            var longPollingThread = new Thread(new ParameterizedThreadStart(POSPrinterEvent_LongPolling));
            longPollingThread.IsBackground = true;
            longPollingThread.Start(posprinterChannel);

            // これ以降は、OPOS / POS for .NET でのシーケンスと
            // 非常に似ている

            Console.WriteLine("Press ENTER to OpenDevice, Claim, Enable.");
            Console.ReadLine();

            // WS-POS プロバイダのデバイスをOpen, Claim, Enable
            posprinterChannel.OpenDevice(consumerID);
            posprinterChannel.Claim(consumerID, 1000);
            posprinterChannel.SetDeviceEnabled(consumerID, true);

            posprinterChannel.SetAsyncMode(consumerID, true);

            Console.WriteLine("Press ENTER to PrintNormal");
            Console.ReadLine();

            for (int i = 0; i < 5; i++)
            {
                posprinterChannel.PrintNormal(consumerID, UnifiedPOS.POSPrinter.V1_4.PrinterStation.PTR_S_RECEIPT, ConvertStringToARTSBinary(string.Format("12あ\r\n", i)));
            }
            Console.WriteLine("Press ENTER to Disable, Release, Close");
            Console.ReadLine();

            // UPOSの仕様では、デバイスをCloseすると、Release, Disableも行われる。
            // ネットワークトラフィックを軽減するため、連続して呼び出す場合は
            // CloseDeviceのみ呼び出すことを推奨
            // posprinterChannel.SetDeviceEnabled(consumerID, false);
            // posprinterChannel.Release(consumerID);
            posprinterChannel.CloseDevice(consumerID);

            // WS-POSの後始末
            // ロングポーリングの停止、KeepAliveの停止、
            // WS-POSセッションの終了
            longPollingThread.Abort();
            keepAliveTimer.Dispose();

            posprinterChannel.CloseSession(consumerID);
                        

        }

        protected virtual string ConvertStringToARTSBinary(string str)
        {
            StringBuilder sb = new StringBuilder();
            foreach (ushort c in str)
            {
                sb.AppendFormat("\\u{0,0:x4}", c);
            }
            return sb.ToString();
        }

        protected virtual string ConvertARTSBinaryToString(string bin)
        {
            string[] tokens = bin.Split(new string[] { "\\u" }, StringSplitOptions.None);
            if (tokens.Length < 2)
            {
                throw new ArgumentException("Bad ARTSBinary format.");
            }
            if (tokens[0] != string.Empty)
            {
                throw new ArgumentException("Bad ARTSBinary format. ARTSBinary must be begun \\u");
            }

            StringBuilder sb = new StringBuilder();
            for (int i = 1; i < tokens.Length; i++)
            {
                var token = tokens[i];

                if (token.Length != 4)
                {
                    throw new ArgumentException("Bad ARTSBinary format.");
                }
                if (ValidateARTSBinaryToken(token) == false)
                {
                    throw new ArgumentException("Bad ARTSBinary format.");
                }

                int value = Convert.ToInt32(token, 16);
                char c = (char)value;

                sb.Append(c);
            }

            return sb.ToString();
        }

        private bool ValidateARTSBinaryToken(string token)
        {
            foreach (char c in token)
            {
                if (c != '0' && c != '1' && c != '2' && c != '3' && c != '4' && c != '5' && c != '6' && c != '7' && c != '8' && c != '9' &&
                    c != 'a' && c != 'b' && c != 'c' && c != 'd' && c != 'e' && c != 'f')
                {
                    return false;
                }
            }
            return true;
        }


        /// <summary>
        /// WS-POS KeepAliveを行う
        /// </summary>
        void KeepAliveTimer_TimerCallback(object o)
        {
            var posprinterChannel = o as UnifiedPOS.POSPrinter.V1_4.POSPrinter;

            try
            {
                posprinterChannel.KeepAlive(consumerID);
            }
            catch (TimeoutException)
            {
                // サービスプロバイダとの通信がオフラインとなった場合は、
                // 再度 OpenSessionからやりなおす。
                // ただし、サービスプロバイダ側がセッションタイムアウトする前は、
                // OpenSessionできない。
                // このサンプルプログラムでは、何も処理をしていない

                Console.WriteLine("WS-POS KeepAlive Timeout happend.");
            }
        }

        /// <summary>
        /// ロングポーリングスレッドの実装
        /// </summary>
        void POSPrinterEvent_LongPolling(object o)
        {
            var posprinterChannel = o as UnifiedPOS.POSPrinter.V1_4.POSPrinter;

            while (true)
            {
                try
                {
                    // イベントポーリングを実行
                    var deviceEvent = posprinterChannel.PollForUPOSEvent(consumerID);

                    if (deviceEvent == null)
                    {
                        continue; // イベントがなかった場合、再度ポーリング
                    }

                    // イベント処理を行い応答を作成(別メソッド呼び出し)
                    var res = EventHandling(deviceEvent);

                    // イベントへの応答(WS-POSサービスプロバイダへの応答)
                    while (true)
                    {
                        try
                        {
                            posprinterChannel.SetEventResponse(consumerID, res);
                            break; // 応答が正常に完了したら、次のイベントを取得するためPollEventに戻る
                        }
                        catch (TimeoutException)
                        {
                            // イベントの返信がオフラインで帰ってきた場合、PollEventではなく、再度SetEventResponseを行う
                            // 適宜Sleepを入れてもよい
                            continue;
                        }
                        catch (FaultException<UnifiedPOS.POSPrinter.V1_4.UposException> ex)
                        {
                            Console.WriteLine("FaultException<UposException>" + ex.ToString());
                            if (ex.Detail.ErrorCode == UnifiedPOS.POSPrinter.V1_4.ErrorCode.E_ILLEGAL)
                            {
                                // ILLEGALが帰ってきた場合は、SetEventResponseは既に受信されていたので、再度PollEventに戻る
                                // (SetEventResponseをWS-POSプロバイダが受信し、処理し、応答を返したところでオフラインになった)
                                break;
                            }
                            else
                            {
                                throw;
                            }
                        }
                    }
                }
                catch (TimeoutException)
                {
                    // PollEventがオフラインになった または イベント取得のタイムアウトになった場合
                    // 再度PollEventを行う
                    // 適宜Sleepを入れてもよい
                    Console.WriteLine("EventPolling TimeoutException");
                    continue;
                }
            }

        }

        /// <summary>
        /// イベント応答
        /// </summary>
        UnifiedPOS.POSPrinter.V1_4.POSPrinterEventResponse EventHandling(UnifiedPOS.POSPrinter.V1_4.POSPrinterEvent deviceEvent)
        {
            // 応答の入れ物を作成
            var res = new UnifiedPOS.POSPrinter.V1_4.POSPrinterEventResponse();

            // 受信したイベントごとの処理を実行
            if (deviceEvent.DirectIOEvent != null)
            {
                var ev = deviceEvent.DirectIOEvent;
                Console.WriteLine(string.Format("DirectIOEvent: EventNumber={0}, Data={1}, Obj={2}", ev.EventNumber, ev.Data, ev.Obj));

                // 応答を作成
                res.DirectIOEventResponse = new UnifiedPOS.POSPrinter.V1_4.DirectIOEventResponse();
                res.DirectIOEventResponse.EventID = ev.EventID;
            }
            else if (deviceEvent.ErrorEvent != null)
            {
                var ev = deviceEvent.ErrorEvent;
                Console.WriteLine("ErrorEvent: ErrorCode={0}, ErrorCodeExtended={1}", ev.ErrorCode, ev.ErrorCodeExtended);

                // 応答を作成
                res.ErrorEventResponse = new UnifiedPOS.POSPrinter.V1_4.ErrorEventResponse();
                res.ErrorEventResponse.EventID = ev.EventID;
                res.ErrorEventResponse.ErrorResponse = UnifiedPOS.POSPrinter.V1_4.ErrorResponse.ER_CLEAR;
            }
            else if (deviceEvent.OutputCompleteEvent != null)
            {
                var ev = deviceEvent.OutputCompleteEvent;
                Console.WriteLine(string.Format("OutputCompleteEvent: OutputID={0}, TimeStamp={1}", ev.OutputID, ev.TimeStamp));

                // 応答を作成
                res.OutputCompleteEventResponse = new UnifiedPOS.POSPrinter.V1_4.OutputCompleteEventResponse();
                res.OutputCompleteEventResponse.EventID = ev.EventID;
            }
            else if (deviceEvent.StatusUpdateEvent != null)
            {
                var ev = deviceEvent.StatusUpdateEvent;
                Console.WriteLine(string.Format("StatusUpdateEvent: Status={0}", ev.Status));

                // 応答を作成
                res.StatusUpdateEventResponse = new UnifiedPOS.POSPrinter.V1_4.StatusUpdateEventResponse();
                res.StatusUpdateEventResponse.EventID = ev.EventID;
            }

            return res;
        }

    }
}
