// WS-POS version 1.3 参照実装
// 
// 実装 version 0.6 

using System;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.Collections.Generic;
using System.Reflection;
using System.Diagnostics;
using System.Threading;
using System.Text.RegularExpressions;
using System.Text;
using System.IO;
using System.Security.Cryptography;

namespace WSPOSInitiative.Common
{
    /// <summary>
    /// 各デバイスのサービスプロバイダ共通動作
    /// Service provider common behavior for each device
    /// </summary>
    public abstract class ServiceProviderBehavior : IDisposable
    {
        /// <summary>
        /// ログ出力
        /// Log output.
        /// </summary>
        private ILog _log;

        /// <summary>
        /// WS-POSのセッション管理メンバ変数
        /// The session management member variable of WS-POS
        /// </summary>
        private ISessionManager _sessionManager;

        /// <summary>
        /// KeepAliveのタイマ
        /// KeepAlive timer
        /// </summary>
        private Timer _keepAliveTimer;

        /// <summary>
        /// デフォルトのプロバイダセッションタイムアウト（１８０秒）
        /// Default provider session timeout (180 seconds)
        /// </summary>
        protected const int ProviderSessionTimeoutDefaultValue = 180;

        /// <summary>
        /// デフォルトのプロバイダセッションタイムアウトチェック周期（１秒）
        /// A default provider session time-out check cycle (1 second) 
        /// </summary>
        protected const int ProviderSessionTimeoutCheckPeriodDefaultValue = 1;

        /// <summary>
        /// デフォルトのロングポーリングタイムアウト（無限）
        /// Default long polling timeout (infinity) 
        /// </summary>
        protected const int ProviderLongPollingTimeoutDefaultValue = -1;



        
        // OPOS Status Update Constants - UpdateFirmware
        protected const int OPOS_SUE_UF_COMPLETE  =  2200;
        protected const int OPOS_SUE_UF_COMPLETE_DEV_NOT_RESTORED  = 2205;
        protected const int OPOS_SUE_UF_FAILED_DEV_OK = 2201;
        protected const int OPOS_SUE_UF_FAILED_DEV_UNRECOVERABLE  = 2202;
        protected const int OPOS_SUE_UF_FAILED_DEV_NEEDS_FIRMWARE = 2203; 
        protected const int OPOS_SUE_UF_FAILED_DEV_UNKNOWN = 2204;

        /// <summary>
        /// 呼び出せないようにprotectedとする
        /// A "protected" declaration is made so that it cannot call. 
        /// </summary>
        protected ServiceProviderBehavior() { }

        /// <summary>
        /// コンストラクタ
        /// Constructor
        /// </summary>
        public ServiceProviderBehavior(int providerSessionTimeoutCheckPeriod)
        {
            _log = LogFactory.CreateLogInstance();
            _sessionManager = SessionManagerFactory.CreateSessionManagerInstance(_log);
            _keepAliveTimer = new Timer(new TimerCallback(ServiceProvider_KeepAliveCheck), this, 1000, providerSessionTimeoutCheckPeriod * 1000);
        }

        /// <summary>
        /// セッションごとのコンテキスト情報を生成します。
        /// コンテキスト情報には、各デバイスへの参照が含まれているため、デバイスの取得も行います。
        /// The context information for every session is generated. 
        /// Since the reference to each device is included in conext information, acquisition of a device is also carried out. 
        /// </summary>
        /// <param name="consumerID">セッションID | Session ID </param>
        /// <param name="comsumerEventPort">サービスコンシューマのポート |A service consumer's port </param>
        /// <returns>セッションコンテキスト</returns>
        protected ISessionContext CreateSessionContext(string consumerID, object comsumerEventPort)
        {
            if (_sessionManager.IsExistSession(consumerID))
            {
                // 既にセッションが生成されているため、ILLEGAL扱いとする。
                // Since the session already opened, return ILLEGAL
                throw new Microsoft.PointOfService.PosControlException("Session Already Opened.", Microsoft.PointOfService.ErrorCode.Illegal, 0);
            }

            return SessionContextFactory.CreateSessionContextInstance(consumerID, null, comsumerEventPort, _log, null);
        }

        /// <summary>
        /// セッションコンテキストに関連付けられたデバイスをオープンします
        /// Open the device that is associated with the session context
        /// </summary>
        /// <param name="sessionContext">セッションコンテキスト | Session Context</param>
        /// <param name="deviceTypeName">デバイス種類 | Device Type</param>
        /// <param name="deviceName">デバイス名 | Device Name</param>
        protected void OpenDeviceInSessionContext(ISessionContext sessionContext, string deviceTypeName, string deviceName)
        {
            Microsoft.PointOfService.DeviceInfo deviceInfo = null;

            if (deviceName == "") deviceName = deviceTypeName;

            LogWriteLineMessage(MethodBase.GetCurrentMethod(), string.Format("WS-POS 1.3 WCF Service Open : Service Type = {0} : Device Name = {1}", this.GetType().FullName, deviceName));

            if (sessionContext.Device != null)
            {
                // OpenDevice後に、再びOpenDeviceを呼び出された場合、
                // 既にDeviceにはインスタンスが設定されているので、なにもしない。
                // Since the instance is already set to Device 
                // when OpenDevice is again called after OpenDevice, nothing is done. 
                return;
            }

            try
            {
                Microsoft.PointOfService.PosExplorer posExplorer = new Microsoft.PointOfService.PosExplorer();

                // 論理名でデバイスを取得する場合
                // When a device is acquired by a logical name 
                deviceInfo = posExplorer.GetDevice(deviceTypeName, deviceName);

                if (deviceInfo != null)
                {
                    //device = posExplorer.CreateInstance(deviceInfo);
                    sessionContext.Device = posExplorer.CreateInstance(deviceInfo);
                }
                else
                {
                    Microsoft.PointOfService.PosDevice device = null;

                    // SO名でも確認する(不要?)
                    // It checks also by SO name. 
                    Microsoft.PointOfService.DeviceCollection devices = posExplorer.GetDevices(deviceTypeName);
                    foreach (Microsoft.PointOfService.DeviceInfo di in devices)
                    {
                        if (di.ServiceObjectName == deviceName)
                        {
                            device = posExplorer.CreateInstance(di);
                        }
                    }
                    if (device == null)
                    {
                        throw new ArgumentException("wrong device name : " + deviceName + " it can't create instance.");
                    }

                    sessionContext.Device = device;

                    sessionContext.IsLegacySO = false;
                }
            }
            catch (Exception ex)
            {
                LogWriteLineException(MethodBase.GetCurrentMethod(), ex);
                throw;
            }

        }

        /// <summary>
        /// そのセッションIDをもとにセッションマネージャよりセッションコンテキストを取得する
        /// A session context is acquired from a session manager based on the session ID.
        /// </summary>
        /// <param name="throwWhenExistId">true時は、すでにIDがセッションマネージャに登録されていたら例外をスローする。false時はIDがセッションマネージャに登録されていないならば例外をスローする | At the time of true, if ID is already registered into the session manager, an exception will be thrown. At the time of false, if ID is not registered into a session manager, an exception will be thrown. </param>
        /// <returns>セッションコンテキスト | Session Context</returns>
        protected ISessionContext GetSessionContext(string id, bool throwWhenExistId = false)
        {
            ValidateSessionID(id, throwWhenExistId);
            return _sessionManager.GetSessionContext(id);
        }

        /// <summary>
        /// セッションマネージャからセッションコンテキストを取得する
        /// Retrieving session context from the session manager
        /// </summary>
        /// <param name="consumerID">セッションID</param>
        /// <returns></returns>
        protected ISessionContext GetSessionContext(string consumerID)
        {
            return _sessionManager.GetSessionContext(consumerID);
        }

        /// <summary>
        /// セッションマネージャからすべてのセッションIDを取得する
        /// Get all the session ID from the session manager
        /// </summary>
        /// <returns>セッションIDのコレクション </returns>
        protected ICollection<string> GetAllSessionIDs()
        {
            return _sessionManager.GetAllSessionIDs();
        }

        /// <summary>
        /// セッションマネージャからセッションコンテキストを削除する
        /// Delete a session context from the session manager
        /// </summary>
        /// <param name="consumerID">削除するセッションコンテキストが関連付けられたセッションID | Session ID for Delete</param>
        protected void RemoveSessionContext(string consumerID)
        {
            _sessionManager.RemoveSessionContext(consumerID);
        }

        /// <summary>
        /// コンシューマーIDを暗号化する
        /// To encrypt consumer ID
        /// </summary>
        /// <param name="consumerID"></param>
        /// <returns>Encrypted consumer ID</returns>
        protected string EncryptConsumerID(string consumerID)
        {
            byte[] byteArray = Encoding.Unicode.GetBytes(consumerID);

            // .NET Framework 2.0　では SHA-1とMD5しかサポートしていない
            MD5 crypt = new System.Security.Cryptography.MD5CryptoServiceProvider();
            byte[] hashValue = crypt.ComputeHash(byteArray);

            StringBuilder hashedText = new StringBuilder();
            for (int i = 0; i < hashValue.Length; i++)
            {
                hashedText.AppendFormat("{0:X2}", hashValue[i]);
            }

            return hashedText.ToString();
        }

        /// <summary>
        /// セッションマネージャにセッションIDがすでに管理下に存在するか否かを返す
        /// Returns whether session ID under control already exists in session manager
        /// </summary>
        /// <param name="consumerID">セッションID | Session ID</param>
        /// <returns>セッションIDがすでに管理下に存在するか否か | Does session ID already exist in a session manager under management or not</returns>
        protected bool IsExistSession(string consumerID)
        {
            return _sessionManager.IsExistSession(consumerID);
        }

        /// <summary>
        /// セッションマネージャにセッションIDが受け入れ可能か否かを返す
        /// Returns whether the session ID is acceptable by session manager
        /// </summary>
        /// <param name="consumerID">セッションID | Session ID</param>
        /// <returns>セッションIDが受け入れ可能か否か | Is session ID acceptable or not</returns>
        protected bool IsValidSessionID(string consumerID)
        {
            return _sessionManager.IsValidSessionID(consumerID);
        }


        /// <summary>
        /// セッションコンテキストを追加する
        /// Add a session context
        /// </summary>
        /// <param name="consumerID">セッションID | Session ID</param>
        /// <param name="context">セッションIDに関連付けるセッションコンテキスト | Session context associated with a session ID</param>
        protected void AddSession(string consumerID, ISessionContext sessionContext)
        {
            _sessionManager.AddSession(consumerID, sessionContext);
        }

        /// <summary>
        /// KeepAlive用のタイマハンドラ
        /// The timer handler for KeepAlive 
        /// </summary>
        /// <param name="o"></param>
        protected void ServiceProvider_KeepAliveCheck(object o)
        {
            try
            {
                var removedSessions = RemoveTimeoutSessionContext(false);
                if (removedSessions != null)
                {
                    foreach (ISessionContext sessionContext in removedSessions)
                    {
                        // CloseではReleaseも行われる
                        CloseSessionResources(sessionContext);
                    }
                }
            }
            catch (Exception ex)
            {
                LogWriteLineException(MethodBase.GetCurrentMethod(), ex);
                // throw; セッションを閉じた際に例外が発生しても
                // ハンドリングする主体がいないため、ログのみ出力して例外を握りつぶす
                // Since there is no entity handling an exception thrown even if it occurs when a session is closed, 
                // only a log is outputted and an exception is discared. 
            }
        }

        /// <summary>
        /// プロバイダセッションタイムアウトの取得
        /// Retrieve provider session timeout (second)
        /// </summary>
        /// <returns></returns>
        public virtual int GetProviderSessionTimeout(string consumerID)
        {
            var sessionContext = GetSessionContext(consumerID);
            return sessionContext.ProviderSessionTimeout;
        }

        public virtual void KeepAlive(string ConsumerID)
        {
            GetSessionContext(ConsumerID); // セッションタイムアウトをクリアするために取得する | It acquires in order to clear session timeout. 
        }

        public string GenerateConsumerID()
        {
            return Guid.NewGuid().ToString();
        }

        /// <summary>
        /// セッションIDのチェック用メソッド
        /// チェックした結果、セッションIDが不正の場合は例外をスローする。
        /// セッションマネージャの内部やServiceBaseでの実装ではなく、
        /// ServiceBaseで抽象メソッドとして定義し、各デバイスサービスに実装させているのは、
        /// UposExceptionクラスがUPOS共通ではなく、各デバイスのネームスペースに所属しているため。
        /// 共通部分のコードでは、UposExceptionを書けない。
        /// A method to check SessionID.
        /// If the SessionID is incorrect, an exception is thrown. 
        /// Since a UposException class belongs to the name space of each device rather than common to UPOS, 
        /// this is defined as an abstract method in ServiceBase and implemented in each device service 
        /// instead of an implementation as a ServiceBase or inside of Session Manager.
        /// </summary>
        /// <param name="consumerID">Session ID</param>
        /// <param name="throwWhenExistId">true時は、すでにIDがセッションマネージャに登録されていたら例外をスローする。false時はIDがセッションマネージャに登録されていないならば例外をスローする |At the time of true, if ID is already registered into the session manager, this throws an exception. </param>
        protected abstract void ValidateSessionID(string id, bool throwWhenExistId = false);

        /// <summary>
        /// 指定されたセッションに関連されたリソースを解放します
        /// Releases the resources that are related to the specified session.
        /// </summary>
        /// <param name="sessionContext">セッションコンテキスト | Session context</param>
        protected abstract void CloseSessionResources(ISessionContext sessionContext);

        /// <summary>
        /// 指定されたセッションに関連されたリソースを解放します。
        /// 派生クラスのCloseSessionから呼び出すことを前提しています。
        /// デバイスに依存しないセッション関連リソースの解放を行います。
        /// Releases the resources associated with the specified session. 
        /// We are assuming that you call from CloseSession of the derived class. 
        /// The session related resource independent of a device is released. 
        /// </summary>
        /// <param name="sessionContext">セッションコンテキスト | Session Context </param>
        /// <param name="serviceDataEventHandler">サービスのDataEventハンドラ | DataEvent handler in the service </param>
        /// <param name="serviceDirectIOEventHandler">サービスのDirectIOEventハンドラ | DirectIOEvent handler in the service</param>
        /// <param name="serviceErrorEventHandler">サービスのErrorEventハンドラ | ErrorEvent handler in the service</param>
        /// <param name="serviceOutputCompleteEventHandler">サービスのOutputCompleteEventハンドラ | OutputCompleteEvent handler in the service</param>
        /// <param name="serviceStatusUpdateEventHandler">サービスのStatusUpdateEventハンドラ | StatusUpdateEvent handler in the service</param>
        protected virtual void CloseSessionBase(ISessionContext sessionContext,
            EventHandler<Microsoft.PointOfService.DataEventArgs> serviceDataEventHandler,
            EventHandler<Microsoft.PointOfService.DirectIOEventArgs> serviceDirectIOEventHandler,
            EventHandler<Microsoft.PointOfService.DeviceErrorEventArgs> serviceErrorEventHandler,
            EventHandler<Microsoft.PointOfService.OutputCompleteEventArgs> serviceOutputCompleteEventHandler,
            EventHandler<Microsoft.PointOfService.StatusUpdateEventArgs> serviceStatusUpdateEventHandler)
        {
            if (serviceDataEventHandler != null)
            {
                sessionContext.ServiceDataEvent -= serviceDataEventHandler;
            }
            if (serviceDirectIOEventHandler != null)
            {
                sessionContext.ServiceDirectIOEvent -= serviceDirectIOEventHandler;
            }
            if (serviceErrorEventHandler != null)
            {
                sessionContext.ServiceErrorEvent -= serviceErrorEventHandler;
            }
            if (serviceOutputCompleteEventHandler != null)
            {
                sessionContext.ServiceOutputCompleteEvent -= serviceOutputCompleteEventHandler;
            }
            if (serviceStatusUpdateEventHandler != null)
            {
                sessionContext.ServiceStatusUpdateEvent -= serviceStatusUpdateEventHandler;
            }

            if (sessionContext.ConsumerEventPort != null)
            {
                (sessionContext.ConsumerEventPort as IClientChannel).Close();
                (sessionContext.ConsumerEventPort as IClientChannel).Dispose();
            }
            else
            {
                if (sessionContext.IsPollingNow)
                {
                    sessionContext.OtherPollForUPOSEventCalled.Signal();
                    sessionContext.PreviousPollForUPOSEventEnded.Wait(sessionContext.EventPollingTimeout);
                }

                sessionContext.DeviceEventHappened.Dispose();
                sessionContext.SetEventResponseCalled.Dispose();
                sessionContext.OtherPollForUPOSEventCalled.Dispose();
                sessionContext.PreviousPollForUPOSEventEnded.Dispose();
            }

            DeleteAsyncTemporaryFiles(sessionContext);

            sessionContext.DeviceThread.Dispose();
            sessionContext.IsClosed = true;
        }

        /// <summary>
        /// セッションコンテキストごとのデバイスDataイベント受信ハンドラ
        /// 処理の大枠は各デバイス共通だが、デバイスごとにクラスとネームスペースが異なり、
        /// また、共通の有意なクラスを継承していないため、個々のデバイスサービスクラスでの実装となる。
        /// ここでは抽象メソッドとして定義する。
        /// The device Data event receiving handler for every session context. 
        /// Although the large frame of processing is common to each device, 
        /// since a name space differs from a class for every device and the common significant class is not inherited, 
        /// it is implemented in each device service class. 
        /// An abstract method is defined here.
        /// </summary>
        /// <param name="sender">セッションコンテキスト</param>
        /// <param name="e">イベント</param>
        protected abstract void SessionContext_ServiceDataEvent(object sender, Microsoft.PointOfService.DataEventArgs e);

        /// <summary>
        /// セッションコンテキストごとのデバイスDirectIOイベント受信ハンドラ
        /// 処理の大枠は各デバイス共通だが、デバイスごとにクラスとネームスペースが異なり、
        /// また、共通の有意なクラスを継承していないため、個々のデバイスサービスクラスでの実装となる。
        /// ここでは抽象メソッドとして定義する。
        /// The device Data event receiving handler for every session context. 
        /// Although the large frame of processing is common to each device, 
        /// since a name space differs from a class for every device and the common significant class is not inherited, 
        /// it is implemented in each device service class. 
        /// An abstract method is defined here.
        /// </summary>
        /// <param name="sender">セッションコンテキスト</param>
        /// <param name="e">イベント</param>
        protected abstract void SessionContext_ServiceDirectIOEvent(object sender, Microsoft.PointOfService.DirectIOEventArgs e);

        /// <summary>
        /// セッションコンテキストごとのデバイスErrorイベント受信ハンドラ
        /// 処理の大枠は各デバイス共通だが、デバイスごとにクラスとネームスペースが異なり、
        /// また、共通の有意なクラスを継承していないため、個々のデバイスサービスクラスでの実装となる。
        /// ここでは抽象メソッドとして定義する。
        /// The device Error event receiving handler for every session context. 
        /// Although the large frame of processing is common to each device, 
        /// since a name space differs from a class for every device and the common significant class is not inherited, 
        /// it is implemented in each device service class. 
        /// An abstract method is defined here.
        /// </summary>
        /// <param name="sender">セッションコンテキスト</param>
        /// <param name="e">イベント</param>
        protected abstract void SessionContext_ServiceErrorEvent(object sender, Microsoft.PointOfService.DeviceErrorEventArgs e);

        /// <summary>
        /// セッションコンテキストごとのデバイスOutputCompleteイベント受信ハンドラ
        /// 処理の大枠は各デバイス共通だが、デバイスごとにクラスとネームスペースが異なり、
        /// また、共通の有意なクラスを継承していないため、個々のデバイスサービスクラスでの実装となる。
        /// ここでは抽象メソッドとして定義する。
        /// The device OutputComplete event receiving handler for every session context. 
        /// Although the large frame of processing is common to each device, 
        /// since a name space differs from a class for every device and the common significant class is not inherited, 
        /// it is implemented in each device service class. 
        /// An abstract method is defined here.
        /// </summary>
        /// <param name="sender">セッションコンテキスト</param>
        /// <param name="e">イベント</param>
        protected abstract void SessionContext_ServiceOutputCompleteEvent(object sender, Microsoft.PointOfService.OutputCompleteEventArgs e);

        /// <summary>
        /// セッションコンテキストごとのデバイスStatusUpdateイベント受信ハンドラ
        /// 処理の大枠は各デバイス共通だが、デバイスごとにクラスとネームスペースが異なり、
        /// また、共通の有意なクラスを継承していないため、個々のデバイスサービスクラスでの実装となる。
        /// ここでは抽象メソッドとして定義する。
        /// The device StatusUpdate event receiving handler for every session context. 
        /// Although the large frame of processing is common to each device, 
        /// since a name space differs from a class for every device and the common significant class is not inherited, 
        /// it is implemented in each device service class. 
        /// An abstract method is defined here.
        /// </summary>
        /// <param name="sender">セッションコンテキスト | Session Context</param>
        /// <param name="e">イベント | Event Argument</param>
        protected abstract void SessionContext_ServiceStatusUpdateEvent(object sender, Microsoft.PointOfService.StatusUpdateEventArgs e);

        /// <summary>
        /// デバイスのクローズ処理を共通化
        /// 各デバイスのCloseメソッド内ではこれを呼び出す
        /// The common close process of the device
        /// Call it in each device Close method
        /// </summary>
        protected virtual void Close(string consumerID)
        {
            try
            {
                ISessionContext sessionContext = GetSessionContext(consumerID);
                CloseSessionResources(sessionContext);

                RemoveSessionContext(consumerID);
            }
            catch (Exception ex)
            {
                LogWriteLineException(MethodBase.GetCurrentMethod(), ex);
                throw;
            }
        }

        /// <summary>
        /// セッションマネージャからタイムアウトに達したセッションコンテキストを削除する
        /// Delete the session context that the timeout is reached
        /// </summary>
        /// <param name="all">true時にはすべてのコンテキストを削除 | If true is specified as this, all the contexts will be deleted. </param>
        /// <returns>削除したセッションコンテキストの列挙 | Enumeration of session context deleted</returns>
        protected IEnumerable<ISessionContext> RemoveTimeoutSessionContext(bool all)
        {
            return _sessionManager.RemoveTimeoutSessionContext(all);
        }

        /// <summary>
        /// ログ出力
        /// Log output
        /// </summary>
        /// <param name="currentMethod">呼び出したメソッド | The called method</param>
        /// <param name="message">出力文字列 | Output string</param>
        protected virtual void LogWriteLineMessage(MethodBase currentMethod, string message)
        {
            _log.LogWriteLineMessage(currentMethod, message);
        }

        /// <summary>
        /// ログ出力
        /// Log output
        /// </summary>
        /// <param name="currentMethod">呼び出したメソッド | The called method</param>
        /// <param name="ex">Exception object</param>
        protected virtual void LogWriteLineException(MethodBase currentMethod, Exception ex)
        {
            _log.LogWriteLineException(currentMethod, ex);
        }

        /// <summary>
        /// ログ出力
        /// Log output 
        /// </summary>
        /// <param name="currentMethod">呼び出したメソッド | The called method</param>
        /// <param name="ex">Exception object</param>
        /// <param name="message">出力文字列 | Output string</param>
        protected virtual void LogWriteLineException(MethodBase currentMethod, Exception ex, string message)
        {
            _log.LogWriteLineException(currentMethod, ex, message);
        }

        #region IDisposable メンバー

        public virtual void Dispose()
        {
            if (_sessionManager != null)
            {
                var removedSessions = _sessionManager.RemoveTimeoutSessionContext(true);
                if (removedSessions != null)
                {
                    foreach (ISessionContext sessionContext in removedSessions)
                    {
                        // CloseではReleaseも行われる
                        CloseSessionResources(sessionContext);
                    }
                }

                _sessionManager = null;
            }

            if (_keepAliveTimer != null)
            {
                _keepAliveTimer.Dispose();
                _keepAliveTimer = null;
            }

        }

        #endregion

        protected virtual string ProcessEscapeSequence(string data)
        {
            Regex pattern = new Regex("\\\\u([0-9a-fA-F]{4})|(\\\\\\\\)|(\\\\n)|(\\\\r)|(\\\\t)|(\\\\f)|(\\\\b)", RegexOptions.Compiled);
            Match m = pattern.Match(data);
            StringBuilder s = new StringBuilder();
            int start = 0;
            while (m.Success)
            {
                s.Append(data.Substring(start, m.Index - start));
                if (m.Groups[1].Length > 0)
                {
                    s.Append(System.Convert.ToChar(System.Convert.ToInt32(m.Groups[1].Value, 16)));
                }
                else if (m.Groups[2].Length > 0)
                {
                    s.Append("\\");
                }
                else if (m.Groups[3].Length > 0)
                {
                    s.Append("\n");
                }
                else if (m.Groups[4].Length > 0)
                {
                    s.Append("\r");
                }
                else if (m.Groups[5].Length > 0)
                {
                    s.Append("\t");
                }
                else if (m.Groups[6].Length > 0)
                {
                    s.Append("\f");
                }
                else if (m.Groups[7].Length > 0)
                {
                    s.Append("\b");
                }
                start = m.Index + m.Length;
                m = m.NextMatch();
            }
            s.Append(data.Substring(start));
            return s.ToString();
        }

        protected virtual void DeleteAsyncTemporaryFiles(ISessionContext sessionContext)
        {
            IList<string> deletedFiles = new List<string>();
            foreach (string tempFileName in sessionContext.TempFileNamesForDeleteWhenCloseDevice)
            {
                try
                {
                    if (File.Exists(tempFileName))
                    {
                        File.Delete(tempFileName);
                    }
                    deletedFiles.Add(tempFileName);
                }
                catch (Exception ex)
                {
                    _log.LogWriteLineException(MethodBase.GetCurrentMethod(), ex);
                }
            }

            foreach (var deletedFile in deletedFiles)
            {
                sessionContext.TempFileNamesForDeleteWhenCloseDevice.Remove(deletedFile);
            }
        }

        protected virtual void WaitForEventHandlerReturned(ISessionContext sessionContext)
        {
            if (sessionContext.ConsumerEventPort == null)
            {
                sessionContext.SetEventResponseCalled.Signal();
            }
            else
            {
                if ((sessionContext.ConsumerEventPort as IClientChannel).State != CommunicationState.Closed)
                {
                    (sessionContext.ConsumerEventPort as IClientChannel).Close();
                }
            }

            while (sessionContext.UPOSEventHandlingOnGoing)
            {
                Thread.Sleep(0);
            }

            Thread.Sleep(100);
        }

        protected virtual string ConvertARTSBinaryToString(bool isLegacySO, bool codePageConversion, string bin)
        {
            string value;
            if (isLegacySO)
            {
                if (codePageConversion)
                {
                    value = BinaryConversionHelper.ConvertUnicodeARTSBinaryToSJISNIBBLE(bin);
                }
                else
                {
                    value = BinaryConversionHelper.ConvertUnicodeARTSBinaryToUnicodeNIBBLE(bin);
                }
            }
            else
            {
                value = BinaryConversionHelper.ConvertUnicodeARTSBinaryToUnicodeString(bin);
            }

            return value;
        }

        protected virtual string ConvertStringToARTSBinary(bool isLegacySO, bool codePageConversion, string str)
        {
            string value;
            if (isLegacySO)
            {
                if (codePageConversion)
                {
                    value = BinaryConversionHelper.ConvertSJISNIBBLEToUnicodeARTSBinary(str);
                }
                else
                {
                    value = BinaryConversionHelper.ConvertUnicodeNIBBLEToUnicodeARTSBinary(str);
                }
            }
            else
            {
                value = BinaryConversionHelper.ConvertUnicodeStringToUnicodeARTSBinary(str);
            }

            return value;
        }

        protected virtual byte[] ConvertWSPOSBytesToDeviceBytes(bool isLegacySO, bool codePageConversion, byte[] wsposBytes)
        {
            byte[] deviceBytes;

            if (isLegacySO)
            {
                if (codePageConversion)
                {
                    string nibble = BinaryConversionHelper.ConvertBytesToNIBBLE(wsposBytes);
                    deviceBytes = Encoding.GetEncoding(932).GetBytes(nibble);
                }
                else
                {
                    string nibble = BinaryConversionHelper.ConvertBytesToNIBBLE(wsposBytes);
                    deviceBytes = Encoding.Unicode.GetBytes(nibble);
                }
            }
            else
            {
                deviceBytes = wsposBytes;
            }

            return deviceBytes;
        }

        protected virtual byte[] ConvertDeviceBytesToWSPOSBytes(bool isLegacySO, bool codePageConversion, byte[] deviceBytes)
        {
            byte[] wsposBytes;

            if (isLegacySO)
            {
                if (codePageConversion)
                {
                    string nibble = Encoding.GetEncoding(932).GetString(deviceBytes);
                    wsposBytes = BinaryConversionHelper.ConvertNIBBLEToBytes(nibble);
                }
                else
                {
                    string nibble = Encoding.Unicode.GetString(deviceBytes);
                    wsposBytes = BinaryConversionHelper.ConvertNIBBLEToBytes(nibble);
                }
            }
            else
            {
                wsposBytes = deviceBytes;
            }

            return wsposBytes;
        }

    }
}
