using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Threading.Tasks;
using System.Windows.Forms;
using TIBCO.Rendezvous;
using Message = TIBCO.Rendezvous.Message;

namespace JWH.TIB
{

    public class TibRendezvous
    {

        #region [ Static ] ====================================================

        private static bool IsOpen { get; set; } = false;

        public static void Open(XLogger logger = null)
        {
            if (IsOpen) return;
            try
            {
                IsOpen = true;
                if (logger == null) logger = XLogger.Instance;
                logger.Info($"TIBCO.Rendezvous.Environment.Open()");
                TIBCO.Rendezvous.Environment.Open();
            }
            catch (Exception ex)
            {
                if (logger != null) logger.Fatal(ex);
                throw ex;
            }
        }

        public static void Close(XLogger logger = null)
        {
            if (!IsOpen) return;
            try
            {
                IsOpen = false;
                if (logger == null) logger = XLogger.Instance;
                logger.Info($"TIBCO.Rendezvous.Environment.Close()");
                TIBCO.Rendezvous.Environment.Close();
            }
            catch (Exception ex)
            {
                if (logger != null) logger.Fatal(ex);
                throw ex;
            }

        }

        #endregion

        #region [ Event ] =====================================================

        private delegate void ServiceStartHandler();

        public delegate void OnTransportHandler(TibRendezvous sender, XmlMessage xmlMessage);

        public delegate void OnListenerHandler(Listener sender, XmlMessage xmlMessage);

        public delegate void OnListenerInfoHandler(ListenerInfo sender, XmlMessage xmlMessage);

        public delegate void OnListenerInfoEventHandler(ListenerInfo sender, TibRendezvousEventArgs args);

        public delegate void OnExceptionHandler(TibRendezvous sender, XmlMessage xmlMessage, Exception ex);

        /// <summary>
        /// Communication Started
        /// </summary>
        public event EventHandler OnStarted;

        /// <summary>
        /// Communication Stoped
        /// </summary>
        public event EventHandler OnStoped;

        /// <summary>
        /// Message Send
        /// </summary>
        public event OnTransportHandler OnSend;

        /// <summary>
        /// Message Sent
        /// </summary>
        public event OnTransportHandler OnSent;

        /// <summary>
        /// Message Received
        /// </summary>
        public event OnListenerInfoHandler OnReceived;

        /// <summary>
        /// Timeout Exception(SendRequest, SendReply)
        /// </summary>
        public event OnExceptionHandler OnTimeout;

        /// <summary>
        /// TIBCO.Rendezvous Event
        /// </summary>
        public event OnListenerInfoEventHandler OnEvent;

        #endregion

        #region [ Rendezvous.Variable ] =======================================

        /// <summary>
        /// Rendezvous Server Information(서버 이상 발생시, 환형구조로 다음 서버로 연결한다)
        /// </summary>
        public List<ServerInfo> ServerInfos { get; protected set; } = new List<ServerInfo>();

        /// <summary>
        /// Current Rendezvous Server Information
        /// </summary>
        public ServerInfo ServerInfo { get; protected set; } = null;

        /// <summary>
        /// Transport
        /// </summary>
        public Transport Transport { get; set; } = null;

        /// <summary>
        /// Rendezvous Listener Information
        /// </summary>
        public List<ListenerInfo> Listeners { get; protected set; } = new List<ListenerInfo>();

        /// <summary>
        /// Rendezvous Listener Information (System Event)
        /// </summary>
        public ListenerSystem ListenerSystem { get; set; } = null;

        #endregion

        #region [ Variable ] ==================================================
        #endregion

        #region [ Properties ] ================================================

        /// <summary>
        /// Logger
        /// </summary>
        public XLogger Logger { get; set; } = XLogger.Instance;

        /// <summary>
        /// 이벤트 보고시, Invoke 처리를 위한 상위객체
        /// </summary>
        public Control Parent { get; set; } = null;

        /// <summary>
        /// Message 전송시, Thread 방식을 사용할 것인지를 설정한다
        /// </summary>
        public bool UseThreadSend { get; set; } = true;

        /// <summary>
        /// 수신된 SendRequest 메시지를 보관한다
        /// </summary>
        public Dictionary<string, XmlMessage> SendRequestMessages { get; protected set; } = new Dictionary<string, XmlMessage>();

        /// <summary>
        /// SendRequest 수신후, SendReply 전송시간 (기본값: 30초)
        /// </summary>
        [DefaultValue(30)]
        public int SendRequestTime { get; set; } = 30;

        /// <summary>
        /// SendRequest 전송후, SendReply 메시지 수신시간 (기본값: 30초)
        /// </summary>
        [DefaultValue(30)]
        public int SendReplyTime { get; set; } = 30;

        #endregion

        #region [ Constructor ] ===============================================

        public TibRendezvous()
        {
        }

        public TibRendezvous(XLogger logger = null)
        {
            this.Logger = logger;
        }

        public TibRendezvous(ServerInfo serverInfo, XLogger logger = null) : this()
        {
            try
            {
                this.Logger = logger;
                this.ServerInfo = serverInfo;
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        #endregion

        #region [ Raise_Events ] ==============================================

        private void Raise_OnStarted()
        {
            try
            {
                if (this.OnStarted == null) return;
                if (this.Parent != null && this.Parent.InvokeRequired)
                    this.Parent.BeginInvoke(new EventHandler(this.OnStarted), this, new EventArgs());
                else
                    this.OnStarted?.Invoke(this, new EventArgs());
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        private void Raise_OnStoped()
        {
            try
            {
                if (this.OnStoped == null) return;
                if (this.Parent != null && this.Parent.InvokeRequired)
                    this.Parent.BeginInvoke(new EventHandler(this.OnStoped), this, new EventArgs());
                else
                    this.OnStoped?.Invoke(this, new EventArgs());
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        private void Raise_OnSend(XmlMessage xmlMessage)
        {
            try
            {
                if (this.OnSend == null) return;
                if (this.Parent != null && this.Parent.InvokeRequired)
                    this.Parent.BeginInvoke(new OnTransportHandler(this.OnSend), this, xmlMessage);
                else
                    this.OnSend?.Invoke(this, xmlMessage);
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        private void Raise_OnSent(XmlMessage xmlMessage)
        {
            try
            {
                this.SendRequestMessage_Remove(xmlMessage);

                if (this.OnSent == null) return;
                if (this.Parent != null && this.Parent.InvokeRequired)
                    this.Parent.BeginInvoke(new OnTransportHandler(this.OnSent), this, xmlMessage);
                else
                    this.OnSent?.Invoke(this, xmlMessage);
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        private void Raise_OnReceived(ListenerInfo sender, XmlMessage xmlMessage)
        {
            try
            {
                this.Logger.Info($"SendSubject='{xmlMessage.SendSubject}', ReplySubject='{xmlMessage.ReplySubject}', SendRequestFlag='{xmlMessage.SendRequestFlag}'");

                if (this.OnReceived == null) return;
                if (this.Parent != null && this.Parent.InvokeRequired)
                    this.Parent.BeginInvoke(new OnListenerInfoHandler(this.OnReceived), sender, xmlMessage);
                else
                    this.OnReceived?.Invoke(sender, xmlMessage);
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        private void Raise_OnEvent(ListenerInfo sender, TibRendezvousEventArgs args)
        {
            try
            {
                if (this.OnEvent == null) return;
                if (this.Parent != null && this.Parent.InvokeRequired)
                    this.Parent.BeginInvoke(new OnListenerInfoEventHandler(this.OnEvent), sender, args);
                else
                    this.OnEvent?.Invoke(sender, args);
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        private void Raise_OnTimeout(XmlMessage xmlMessage, Exception ex)
        {
            try
            {
                if (this.OnTimeout == null) return;
                if (this.Parent != null && this.Parent.InvokeRequired)
                    this.Parent.BeginInvoke(new OnExceptionHandler(this.OnTimeout), this, xmlMessage, ex);
                else
                    this.OnTimeout?.Invoke(this, xmlMessage, ex);
            }
            catch (Exception ex1)
            {
                throw ex1;
            }
        }

        #endregion

        #region [ Public Method ] =============================================

        /// <summary>
        /// Communication Start
        /// </summary>
        public void Start()
        {
            try
            {
                this.Logger.Info();
                this.NextServerInfo();
                if (this.ServerInfo == null) return;

                TibRendezvous.Open();
                ServiceStartHandler startMethod = null;
                switch (this.ServerInfo.TransportType)
                {
                    case TransportType.Reliable: startMethod = this.RMServiceStart; break;
                    case TransportType.Certiry: startMethod = this.CMServiceStart; break;
                    case TransportType.Distributed: startMethod = this.DQServiceStart; break;
                }

                startMethod();
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        /// <summary>
        /// Communication Start
        /// </summary>
        public void Restart()
        {
            try
            {
                this.Logger.Info();
                if (this.ServerInfo == null) this.NextServerInfo();
                if (this.ServerInfo == null) return;

                TibRendezvous.Open();
                ServiceStartHandler startMethod = null;
                switch (this.ServerInfo.TransportType)
                {
                    case TransportType.Reliable: startMethod = this.RMServiceStart; break;
                    case TransportType.Certiry: startMethod = this.CMServiceStart; break;
                    case TransportType.Distributed: startMethod = this.DQServiceStart; break;
                }

                startMethod();
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        /// <summary>
        /// Stop
        /// </summary>
        public void Stop()
        {
            this.Logger.Info();
            this.ServiceStop();
        }

        #endregion

        #region [ Method ] ====================================================

        /// <summary>
        /// RM ServiceStart(Reliable Message)
        /// </summary>
        private void RMServiceStart()
        {
            try
            {
                this.ServiceStop();
                this.Logger.Info();

                this.Logger.Info($"NetTransport({this.ServerInfo.Service}, {this.ServerInfo.Network}, {this.ServerInfo.Daemon})");
                NetTransport netTransport = new NetTransport(this.ServerInfo.Service, this.ServerInfo.Network, this.ServerInfo.Daemon);
                this.Transport = netTransport;

                #region [ Event Listener : Failover ]
                {
                    ListenerSystem listenerSys = new ListenerSystem();
                    listenerSys.Name = "SYSTEM";
                    listenerSys.Subject = $"_RV.*.*.*.*";
                    this.ListenerSystem = listenerSys;

                    Queue queue = new Queue();
                    queue.Name = listenerSys.Name;
                    Listener listener = new Listener(queue, listenerSys.ReceivedMessage, this.Transport, listenerSys.Subject, null);
                    this.Logger.Info($"Listener({queue.GetType().Name}, ReceivedMessage, {listener?.Transport.GetType().Name}, {listener.Subject}, null)");
                    listenerSys.Logger = this.Logger;
                    listenerSys.Listener = listener;
                    listenerSys.OnReceivedMessage += this.OnReceivedMessage;
                    listenerSys.OnReceivedEvent += this.OnReceivedEvent;
                    listenerSys.QueueDispatch();
                }
                #endregion

                foreach (ListenerInfo listenerInfo in this.Listeners)
                {
                    if (string.IsNullOrEmpty(netTransport.Description)) netTransport.Description = $"{listenerInfo.Subject}";

                    Queue queue = new Queue();
                    queue.Name = listenerInfo.Name;
                    Listener listener = new Listener(queue, listenerInfo.ReceivedMessage, this.Transport, listenerInfo.Subject, null);
                    this.Logger.Info($"Listener({queue.GetType().Name}, ReceivedMessage, {listener?.Transport.GetType().Name}, {listener.Subject}, null)");
                    listenerInfo.Logger = this.Logger;
                    listenerInfo.Listener = listener;
                    listenerInfo.OnReceivedMessage += this.OnReceivedMessage;
                    listenerInfo.OnReceivedEvent += this.OnReceivedEvent;
                    listenerInfo.QueueDispatch();
                }

                this.Raise_OnStarted();
            }
            catch (RendezvousException ex)
            {
                this.Logger.Fatal(ex);
                throw ex;
            }
            catch (Exception ex)
            {
                this.Logger.Fatal(ex);
                throw ex;
            }
        }

        /// <summary>
        /// CM ServiceStart(Certiry Message)
        /// </summary>
        private void CMServiceStart()
        {
            try
            {
                this.ServiceStop();
                this.Logger.Info();

                this.Logger.Info($"NetTransport({this.ServerInfo.Service}, {this.ServerInfo.Network}, {this.ServerInfo.Daemon})");
                NetTransport netTransport = new NetTransport(this.ServerInfo.Service, this.ServerInfo.Network, this.ServerInfo.Daemon);
                //this.Transport = netTransport;

                this.Logger.Info($"CMTransport(NetTransport, {this.ServerInfo.CMName}, {this.ServerInfo.RequestOld}, {this.ServerInfo.LedgerName}, {this.ServerInfo.SyncLedger})");
                CMTransport cmTransport = new CMTransport(netTransport, this.ServerInfo.CMName, this.ServerInfo.RequestOld, this.ServerInfo.LedgerName, this.ServerInfo.SyncLedger);
                //this.Logger.Info($"CMTransport(NetTransport, {this.ServerInfo.CMName}, {this.ServerInfo.RequestOld}, {this.ServerInfo.LedgerName}, {this.ServerInfo.SyncLedger})");
                //CMTransport cmTransport = new CMTransport(netTransport, this.ServerInfo.CMName, this.ServerInfo.RequestOld, this.ServerInfo.LedgerName, this.ServerInfo.SyncLedger);
                this.Transport = cmTransport;

                #region [ Event Listener ]
                {
                    ListenerSystem listenerSys = new ListenerSystem();
                    listenerSys.Name = "SYSTEM";
                    listenerSys.Subject = $"_RV.*.*.*.*";
                    this.ListenerSystem = listenerSys;

                    Queue queue = new Queue();
                    Listener listener = new Listener(queue, listenerSys.ReceivedMessage, netTransport, listenerSys.Subject, null);
                    this.Logger.Info($"Listener(Queue[{queue.Name}], ReceivedMessage, {listener.Transport.GetType().ToString()}, {listener.Subject}, null)");
                    listenerSys.Listener = listener;
                    listenerSys.OnReceivedMessage += this.OnReceivedMessage;
                    listenerSys.OnReceivedEvent += this.OnReceivedEvent;
                    listenerSys.Dispatch();
                }
                #endregion

                foreach (ListenerInfo listenerInfo in this.Listeners)
                {
                    Queue queue = new Queue();
                    CMListener listener = new CMListener(queue, listenerInfo.ReceivedMessage, cmTransport, listenerInfo.Subject, null);
                    this.Logger.Info($"CMListener(Queue[{queue.Name}], ReceivedMessage, {listener.Transport.GetType().ToString()}, {listener.Subject}, null)");
                    listenerInfo.Listener = listener;
                    listenerInfo.OnReceivedMessage += this.OnReceivedMessage;
                    listenerInfo.OnReceivedEvent += this.OnReceivedEvent;
                    listenerInfo.Dispatch();
                }

                this.Raise_OnStarted();
            }
            catch (RendezvousException ex)
            {
                this.Logger.Fatal(ex);
                throw ex;
            }
            catch (Exception ex)
            {
                this.Logger.Fatal(ex);
                throw ex;
            }
        }

        /// <summary>
        /// Distributed ServiceStart(Distributed Queue)
        /// </summary>
        private void DQServiceStart()
        {
            try
            {
                this.ServiceStop();
                this.Logger.Info();

                this.Logger.Info($"NetTransport({this.ServerInfo.Service}, {this.ServerInfo.Network}, {this.ServerInfo.Daemon})");
                NetTransport netTransport = new NetTransport(this.ServerInfo.Service, this.ServerInfo.Network, this.ServerInfo.Daemon);
                this.Transport = netTransport;

                //this.Logger.Info($"CMTransport(NetTransport, {this.ServerInfo.CMName}, {this.ServerInfo.RequestOld}, {this.ServerInfo.LedgerName}, {this.ServerInfo.SyncLedger})");
                //CMTransport cmTransport = new CMTransport(netTransport, this.ServerInfo.CMName, this.ServerInfo.RequestOld, this.ServerInfo.LedgerName, this.ServerInfo.SyncLedger);
                //this.Transport = cmTransport;

                this.Logger.Info($"CMQueueTransport(CMTransport, {this.ServerInfo.CMName})");
                CMQueueTransport cmqTransport = new CMQueueTransport(netTransport, this.ServerInfo.CMName, 1, 5, 1, 1.0, 1.5);
                //this.Logger.Info($"CMQueueTransport(NetTransport, {this.ServerInfo.CMName}, {this.ServerInfo.RequestOld}, {this.ServerInfo.LedgerName}, {this.ServerInfo.SyncLedger})");
                //CMQueueTransport cmqTransport = new CMQueueTransport(netTransport, this.ServerInfo.CMName, this.ServerInfo.RequestOld, this.ServerInfo.LedgerName, this.ServerInfo.SyncLedger);

                #region [ Event Listener ]
                {
                    ListenerSystem listenerSys = new ListenerSystem();
                    listenerSys.Name = "SYSTEM";
                    listenerSys.Subject = $"_RV.*.*.*.*";
                    this.ListenerSystem = listenerSys;

                    Queue queue = new Queue();
                    Listener listener = new Listener(queue, listenerSys.ReceivedMessage, netTransport, listenerSys.Subject, null);
                    this.Logger.Info($"{listener.GetType().Name}(Queue[{queue.Name}], ReceivedMessage, {listener.Transport.GetType().ToString()}, {listener.Subject}, null)");
                    listenerSys.Listener = listener;
                    listenerSys.OnReceivedMessage += this.OnReceivedMessage;
                    listenerSys.OnReceivedEvent += this.OnReceivedEvent;
                    listenerSys.Dispatch();
                }
                #endregion

                foreach (ListenerInfo listenerInfo in this.Listeners)
                {
                    Queue queue = new Queue();
                    CMListener cmListener = new CMListener(queue, listenerInfo.ReceivedMessage, cmqTransport, listenerInfo.Subject, null);
                    this.Logger.Info($"{cmListener.GetType().Name}(Queue[{queue.Name}], ReceivedMessage, {cmListener.Transport.GetType().ToString()}, {cmListener.Subject}, null)");
                    listenerInfo.Listener = cmListener;
                    listenerInfo.OnReceivedMessage += this.OnReceivedMessage;
                    listenerInfo.OnReceivedEvent += this.OnReceivedEvent;
                    listenerInfo.Dispatch();
                }

                this.Raise_OnStarted();
            }
            catch (RendezvousException ex)
            {
                this.Logger.Fatal(ex);
                throw ex;
            }
            catch (Exception ex)
            {
                this.Logger.Fatal(ex);
                throw ex;
            }
        }

        /// <summary>
        /// ServiceStop
        /// </summary>
        private void ServiceStop()
        {
            try
            {
                this.Logger.Info();

                if (this.ListenerSystem != null)
                {
                    this.ListenerSystem.Listener?.Transport?.Destroy();
                    this.ListenerSystem.OnReceivedMessage -= this.OnReceivedMessage;
                    this.ListenerSystem.OnReceivedEvent -= this.OnReceivedEvent;
                    this.ListenerSystem.Dispose();
                }
            
                foreach(ListenerInfo listenerInfo in  this.Listeners)
                {
                    this.Logger.Debug($"Listener[{listenerInfo.Subject}].Dispose");
                    listenerInfo.Listener?.Transport?.Destroy();
                    listenerInfo.OnReceivedMessage -= this.OnReceivedMessage;
                    listenerInfo.OnReceivedEvent -= this.OnReceivedEvent;
                    listenerInfo.Dispose();
                }

                if (this.Transport != null)
                {
                    this.Logger.Debug($"Transport.Destroy()");
                    this.Transport.Destroy();
                    this.Transport = null;

                    this.Raise_OnStoped();
                }
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        /// <summary>
        /// Received Data(XmlData)
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="eventArgs"></param>
        public void OnReceivedMessage(ListenerInfo sender, MessageReceivedEventArgs args)
        {
            try
            {
                Listener listener = sender.Listener;
                Message message = args.Message;
                XmlMessage xmlMessage = new XmlMessage(message);
                this.SendRequestMessage_Add(xmlMessage);

                this.Raise_OnReceived(sender, xmlMessage);
            }
            catch (Exception ex)
            {
                this.Logger.Fatal(ex);
            }
        }

        /// <summary>
        /// Add SendRequestMessage
        /// </summary>
        /// <param name="xmlMessage"></param>
        private void SendRequestMessage_Add(XmlMessage xmlMessage)
        {
            try
            {
                if (string.IsNullOrEmpty(xmlMessage.XmlData.TransactionID)) return;
                if (this.SendRequestMessages == null) return;
                if (this.SendRequestMessages.ContainsKey(xmlMessage.XmlData.TransactionID)) return;

                bool isAdd = false;
                // 1. SendRequest() --> SendReply()
                if (!string.IsNullOrEmpty(xmlMessage.Message.ReplySubject)) isAdd = true;
                // 2. SendRequestFlag is True --> Send()
                //if (xmlMessage.SendRequestFlag) isAdd = true;
                if (isAdd == false) return;

                this.SendRequestMessages.Add(xmlMessage.XmlData.TransactionID, xmlMessage);
                xmlMessage.OnTimeOver += this.SendRequest_OnTimeover;
                xmlMessage.TimerStart(this.SendRequestTime);
            }
            catch (Exception ex)
            {
                this.Logger.Fatal(ex);
            }
        }

        /// <summary>
        /// Remove SendRequestMessage
        /// </summary>
        /// <param name="xmlMessage"></param>
        private void SendRequestMessage_Remove(XmlMessage xmlMessage)
        {
            try
            {
                if (string.IsNullOrEmpty(xmlMessage.XmlData.TransactionID)) return;
                if (this.SendRequestMessages == null) return;
                if (!this.SendRequestMessages.ContainsKey(xmlMessage.XmlData.TransactionID)) return;

                XmlMessage message = this.SendRequestMessages[xmlMessage.XmlData.TransactionID];
                message.OnTimeOver -= this.SendRequest_OnTimeover;
                message.TimerStop();
                this.SendRequestMessages.Remove(xmlMessage.XmlData.TransactionID);
            }
            catch (Exception ex)
            {
                this.Logger.Fatal(ex);
            }
        }

        /// <summary>
        /// SendRequest 수신후, SendReply를 전송하지 않았을 경우 호출된다
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void SendRequest_OnTimeover(object sender, EventArgs e)
        {
            XmlMessage xmlMessage = sender as XmlMessage;
            if (xmlMessage == null) return;
            if (this.SendRequestMessages.ContainsKey(xmlMessage.XmlData.TransactionID))
                    this.SendRequestMessages.Remove(xmlMessage.XmlData.TransactionID);

            this.Raise_OnTimeout(xmlMessage, new Exception(Source.S10001) { Source = "S10001" });
        }

        /// <summary>
        /// Received Event
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="args"></param>
        public void OnReceivedEvent(ListenerInfo sender, TibRendezvousEventArgs args)
        {
            try
            {
                this.Logger.Debug(args.ToString());
                this.Raise_OnEvent(sender, args);
            }
            catch (Exception ex)
            {
                this.Logger.Fatal(ex);
            }
        }

        /// <summary>
        /// ServerInfo에 접속정보를 할당한다
        /// </summary>
        /// <returns></returns>
        private ServerInfo NextServerInfo()
        {
            if (this.ServerInfos.Count == 0) return null;
            if (this.ServerInfo == null)
            {
                this.ServerInfo = this.ServerInfos[0];
                return this.ServerInfo;
            }

            int index = this.ServerInfos.IndexOf(this.ServerInfo);
            if (index == -1) index = 0;
            else index += 1;

            if (index >= this.ServerInfos.Count) index = 0;
            this.ServerInfo = this.ServerInfos[index];

            return this.ServerInfo;
        }

        #endregion

        #region [ SendMessage ] ===============================================

        public XmlMessage SendMessage(XmlMessage xmlMessage, OnTransportHandler callback = null)
        {
            XmlMessage response = null;
            try
            {
                if (xmlMessage == null) return response;

                if (xmlMessage.IsReply && !string.IsNullOrEmpty(xmlMessage?.RequestMessage?.ReplySubject)) this.SendReply(xmlMessage, callback);
                else if (xmlMessage.SendRequestFlag) response = this.SendRequest(xmlMessage, callback);
                else this.Send(xmlMessage, callback);

                return response;
            }
            catch (Exception ex)
            {
                this.Logger.Fatal(ex);
                throw ex;
            }
        }

        public XmlMessage SendRequest(XmlMessage xmlMessage, OnTransportHandler callback = null)
        {
            XmlMessage xmlResponse = null;
            try
            {
                xmlMessage.SendRequestFlag = true;
                Message message = xmlMessage.Message;

                this.Raise_OnSend(xmlMessage);
                if (this.UseThreadSend)
                {
                    Task.Factory.StartNew(delegate ()
                    {
                        try
                        {
                            this.Logger.Debug($"SendSubject='{message.SendSubject}', ReplySubject='{message.ReplySubject}', SendRequestFlag='{xmlMessage.SendRequestFlag}'");
                            Message response = this.Transport.SendRequest(message, this.SendReplyTime);
                            this.Raise_OnSent(xmlMessage);
                            if (response == null)
                            {
                                this.Raise_OnTimeout(xmlMessage, new Exception(Source.S10002) { Source = "S10002" });
                                return;
                            }

                            xmlResponse = new XmlMessage(response) { RequestMessage = xmlMessage, IsReply = true };
                            this.Raise_OnReceived(null, xmlResponse);
                            if (callback != null) callback(this, xmlResponse);
                        }
                        catch (RendezvousException ex)
                        {
                            throw ex;
                        }
                        catch (Exception ex)
                        {
                            throw ex;
                        }
                    });
                }
                else
                {
                    this.Logger.Debug($"SendSubject='{message.SendSubject}', ReplySubject='{message.ReplySubject}', SendRequestFlag='{xmlMessage.SendRequestFlag}'");
                    Message response = this.Transport.SendRequest(message, this.SendReplyTime);
                    this.Raise_OnSent(xmlMessage);
                    if (response == null)
                    {
                        this.Raise_OnTimeout(xmlMessage, new Exception(Source.S10002) { Source = "S10002" });
                        return xmlResponse;
                    }

                    xmlResponse = new XmlMessage(response) { RequestMessage = xmlMessage, IsReply = true };
                    this.Raise_OnReceived(null, xmlResponse);
                    if (callback != null) callback(this, xmlResponse);
                }
            }
            catch (RendezvousException ex)
            {
                throw ex;
            }
            catch (Exception ex)
            {
                throw ex;
            }

            return xmlResponse;
        }

        public void SendReply(XmlMessage xmlMessage, OnTransportHandler callback = null)
        {
            try
            {
                Message reply = xmlMessage.Message;
                XmlMessage request = xmlMessage.RequestMessage;

                this.Raise_OnSend(xmlMessage);
                if (this.UseThreadSend)
                {
                    Task.Factory.StartNew(delegate ()
                    {
                        try
                        {
                            this.Logger.Debug($"SendSubject='{reply.SendSubject}', ReplySubject='{reply.ReplySubject}', SendRequestFlag='{xmlMessage.SendRequestFlag}'");
                            this.Transport.SendReply(reply, request.Message);
                            this.Raise_OnSent(xmlMessage);
                            if (callback != null) callback(this, xmlMessage);
                        }
                        catch (RendezvousException ex)
                        {
                            this.Logger.Fatal(ex);
                            throw ex;
                        }
                        catch (Exception ex)
                        {
                            this.Logger.Fatal(ex);
                            throw ex;
                        }
                    });
                }
                else
                {
                    this.Logger.Debug($"SendSubject='{reply.SendSubject}', ReplySubject='{reply.ReplySubject}', SendRequestFlag='{xmlMessage.SendRequestFlag}'");
                    this.Transport.SendReply(reply, request.Message);
                    this.Raise_OnSent(xmlMessage);
                    if (callback != null) callback(this, xmlMessage);
                }
            }
            catch (RendezvousException ex)
            {
                this.Logger.Fatal(ex);
                throw ex;
            }
            catch (Exception ex)
            {
                this.Logger.Fatal(ex);
                throw ex;
            }
        }

        public void Send(XmlMessage xmlMessage, OnTransportHandler callback = null)
        {
            try
            {
                Message message = xmlMessage.Message;

                this.Raise_OnSend(xmlMessage);
                if (this.UseThreadSend)
                {
                    try
                    {
                        Task.Factory.StartNew(delegate ()
                        {
                            this.Logger.Debug($"SendSubject='{message.SendSubject}', ReplySubject='{message.ReplySubject}', SendRequestFlag='{xmlMessage.SendRequestFlag}'");
                            this.Transport.Send(message);
                            this.Raise_OnSent(xmlMessage);
                            if (callback != null) callback(this, xmlMessage);
                        });
                    }
                    catch (RendezvousException ex)
                    {
                        this.Logger.Fatal(ex);
                        throw ex;
                    }
                    catch (Exception ex)
                    {
                        this.Logger.Fatal(ex);
                        throw ex;
                    }
                }
                else
                {
                    this.Logger.Debug($"SendSubject='{message.SendSubject}', ReplySubject='{message.ReplySubject}', SendRequestFlag='{xmlMessage.SendRequestFlag}'");
                    this.Transport.Send(message);
                    this.Raise_OnSent(xmlMessage);
                    if (callback != null) callback(this, xmlMessage);
                }
            }
            catch (RendezvousException ex)
            {
                this.Logger.Fatal(ex);
                throw ex;
            }
            catch (Exception ex)
            {
                this.Logger.Fatal(ex);
                throw ex;
            }
        }

        #endregion

    }

    public static class Source
    {

        public const string S10001 = "SendRequest Timeout: SendReply 메시지를 지정된 시간내에 전송하지 못하였습니다";

        public const string S10002 = "SendReply Timeout: SendReply 메시지를 지정된 시간내에 수신하지 못하였습니다";

    }

}
