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

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

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

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


            // 自己ホストとして、イベント受信クラスを実行する。
            // イベントはPOSPrinterEventServiceクラスで受信される。
            var posprinterEventServiceHost = new ServiceHost(typeof(POSPrinterEventService));
            posprinterEventServiceHost.Open();

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

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

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

            // 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);

            // これ以降は、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();

            posprinterChannel.PrintNormal(consumerID, UnifiedPOS.POSPrinter.V1_4.PrinterStation.PTR_S_RECEIPT, ConvertStringToARTSBinary("WS-POS Test Print\r\n"));

            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セッションの終了、イベント受信クラスの終了
            keepAliveTimer.Dispose();
            posprinterChannel.CloseSession(consumerID);
            posprinterEventServiceHost.Close();

        }

        /// <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.");
            }
        }

        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;
        }


    }
}
