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

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

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

        UnifiedPOS.Scanner.V1_4.Scanner scannerChannel;

        /// <summary>
        /// Scannerの使用方法を説明する
        /// </summary>
        public void Test()
        {
            // WS-POSを利用するための準備

            // チャネルの作成、WS-POSセッションの開始、
            // KeepAliveの開始、ロングポーリングの開始

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

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

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

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

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

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

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

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

            // WS-POS プロバイダのデバイスをOpen, Claim, Enable
            scannerChannel.OpenDevice(consumerID);
            scannerChannel.Claim(consumerID, 1000);
            scannerChannel.SetDeviceEnabled(consumerID, true);
            scannerChannel.SetDataEventEnabled(consumerID, true);
            scannerChannel.SetEventRequestProperties(consumerID, new UnifiedPOS.Scanner.V1_4.PropertyNameList() {"ScanData"});

            Console.WriteLine("Press ENTER to Disable, Release, Close");
            Console.ReadLine();

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

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

            scannerChannel.CloseSession(consumerID);
        }

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

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

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

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

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

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

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

                    // イベントへの応答(WS-POSサービスプロバイダへの応答)
                    while (true)
                    {
                        try
                        {
                            scannerChannel.SetEventResponse(consumerID, res);
                            break; // 応答が正常に完了したら、次のイベントを取得するためPollEventに戻る
                        }
                        catch (TimeoutException)
                        {
                            // イベントの返信がオフラインで帰ってきた場合、PollEventではなく、再度SetEventResponseを行う
                            // 適宜Sleepを入れてもよい
                            continue;
                        }
                        catch (FaultException<UnifiedPOS.Scanner.V1_4.UposException> ex)
                        {
                            Console.WriteLine("FaultException<UposException>" + ex.ToString());
                            if (ex.Detail.ErrorCode == UnifiedPOS.Scanner.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.Scanner.V1_4.ScannerEventResponse EventHandling(UnifiedPOS.Scanner.V1_4.Scanner scannerChannel, UnifiedPOS.Scanner.V1_4.ScannerEvent deviceEvent)
        {
            // 応答の入れ物を作成
            var res = new UnifiedPOS.Scanner.V1_4.ScannerEventResponse();

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

                // 応答を作成
                res.DirectIOEventResponse = new UnifiedPOS.Scanner.V1_4.DirectIOEventResponse();
                res.DirectIOEventResponse.EventID = ev.EventID;
            }
            else if (deviceEvent.DataEvent != null)
            {
                var ev = deviceEvent.DataEvent;
                Console.WriteLine("{0}, {1}, Polling:DataEvent, {2}", ev.EventID, ev.TimeStamp, ev.Status);

                var scanDataBytes = scannerChannel.GetScanData(consumerID);
                string scanDataString = Encoding.ASCII.GetString(scanDataBytes);

                Console.WriteLine("ScanData = {0}", scanDataString);

                scannerChannel.SetDataEventEnabled(consumerID, true);

                // 応答を作成
                res.DataEventResponse = new UnifiedPOS.Scanner.V1_4.DataEventResponse();
                res.DataEventResponse.EventID = ev.EventID;
            }
            else if (deviceEvent.DataContainedEvent != null)
            {
                var ev = deviceEvent.DataContainedEvent;
                Console.WriteLine("{0}, {1}, Polling:DataContainedEvent, {2}", ev.EventID, ev.TimeStamp, ev.Status);

                var scanDataBytes = (byte[])deviceEvent.DataContainedEvent.Parameters["ScanData"];
                string scanDataString = Encoding.ASCII.GetString(scanDataBytes);

                Console.WriteLine("ScanData = {0}", scanDataString);

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

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

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

            return res;
        }

    }
}
