using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using JWH;
using TIBCO.Rendezvous;

namespace JWH.TIB
{

    public class ListenerInfo : IDisposable
    {

        public static int NextID = 1;

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

        public delegate void OnReceivedHandler(ListenerInfo sender, MessageReceivedEventArgs args);

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

        
        public event OnReceivedHandler OnReceivedMessage;

        public event OnEventHandler OnReceivedEvent;

        #endregion

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

        public int ID { get; protected set; } = 0;

        public XLogger Logger { get; set; } = XLogger.Instance;

        public string Name { get; set; } = string.Empty;

        public string Subject { get; set; } = string.Empty;

        public Listener Listener { get; set; } = null;

        public Queue Queue { get { return this?.Listener?.Queue; } }

        protected Dispatcher Dispatcher { get; set; } = null;

        protected Thread QueueDispatchThread { get; set; } = null;

        protected bool QueueDispatchFlag { get; set; } = false;

        protected double DispatchInterval { get; set; } = -1;

        #endregion

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

        public ListenerInfo()
        {
            this.ID = NextID++;
        }

        ~ListenerInfo()
        {
            this.Dispose();
        }

        public void Dispose()
        {
            try
            {
                if (this.Dispatcher != null)
                {
                    this.Dispatcher.Destroy();
                    this.Dispatcher = null;
                }

                if (this.QueueDispatchThread != null)
                {
                    this.QueueDispatchFlag = false;
                    if (this.QueueDispatchThread.ThreadState != ThreadState.Stopped) this.QueueDispatchThread.Abort();
                    this.QueueDispatchThread = null;
                }

                if (this.Listener != null)
                {
                    this.Listener.Destroy();
                    this.Listener = null;
                }
            }
            catch (Exception ex)
            {
                this.Logger.Fatal(ex);
                throw ex;
            }
        }

        #endregion

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

        protected void Raise_ReceivedData(MessageReceivedEventArgs args)
        {
            this.OnReceivedMessage?.Invoke(this, args);
        }

        protected void Raise_ReceivedEvent(TibRendezvousEventArgs args)
        {
            this.OnReceivedEvent?.Invoke(this, args);
        }

        /// <summary>
        /// 비정상 종료시, 내부 쓰레드가 종료되지 않는다.
        /// </summary>
        /// <returns></returns>
        public bool Dispatch()
        {
            try
            {
                if (this.Listener == null) return false;
                if (this.Listener.Queue == null) return false;
                if (this.QueueDispatchThread != null) return false;

                this.Dispatcher = new Dispatcher(this.Listener.Queue);

                return true;
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        /// <summary>
        /// Queue.Dispatch() or Queue.TimedDispatch(interval)
        /// </summary>
        /// <param name="interval"></param>
        /// <returns></returns>
        public bool QueueDispatch(double interval = -1)
        {
            try
            {
                if (this.Listener == null) return false;
                if (this.Listener.Queue == null) return false;
                if (this.Dispatcher != null) return false;
                this.DispatchInterval = interval;

                if (this.QueueDispatchThread != null) this.QueueDispatchThread.Abort();
                this.QueueDispatchThread = new Thread(new ThreadStart(this.QueueDispatch_Threading));
                this.QueueDispatchThread.IsBackground = true;
                this.QueueDispatchThread.Start();
                return true;
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        protected void QueueDispatch_Threading()
        {
            try
            {
                this.Logger.Debug($"Start {this.Subject}");
                this.QueueDispatchFlag = true;
                while (this.QueueDispatchFlag)
                {
                    try
                    {
                        if (this.DispatchInterval > 0) this.Queue.TimedDispatch(this.DispatchInterval);
                        else this.Queue.Dispatch();
                        //this.Logger.Info($"Queue.Count is {this.Queue.Count}");
                    }
                    catch (RendezvousException ex)
                    {
                        this.Logger.Fatal(ex);
                    }
                    catch (Exception ex)
                    {
                        this.Logger.Fatal(ex);
                    }
                }
                this.QueueDispatchFlag = false;
                this.QueueDispatchThread = null;
                this.Logger.Debug($"Finish");
            }
            catch (Exception ex)
            {
                // Tread Exception...!
                throw ex;
            }
        }

        public virtual void ReceivedMessage(object sender, MessageReceivedEventArgs eventArgs)
        {
            this.Raise_ReceivedData(eventArgs);
        }

        #endregion

    }

    public class TibRendezvousEventArgs : EventArgs
    {

        public Message Message { get; private set; } = null;

        public string SendSubject { get; private set; } = string.Empty;

        public string ReplySubject { get; private set; } = string.Empty;

        public string Class { get; private set; } = string.Empty;

        public string Source { get; private set; } = string.Empty;

        public string Name { get; private set; } = string.Empty;

        public Dictionary<string, object> Properties { get; private set; } = new Dictionary<string, object>();

        public TibRendezvousEventArgs()
        {
        }

        public TibRendezvousEventArgs(Message message)
        {
            this.Message = message;
            this.SendSubject = message.SendSubject;
            this.ReplySubject = message.ReplySubject;
            for (uint i = 0; i < message.FieldCount; i++)
            {
                MessageField field = message.GetFieldByIndex(i);
                if (string.Compare(field.Name, "ADV_CLASS", true) == 0) { this.Class = field.Value.ToString(); continue; }
                if (string.Compare(field.Name, "ADV_SOURCE", true) == 0) { this.Source = field.Value.ToString(); continue; }
                if (string.Compare(field.Name, "ADV_NAME", true) == 0) { this.Name = field.Value.ToString(); continue; }

                if (field.Value?.GetType() == typeof(DateTime))
                {
                    DateTime dt = (DateTime)field.Value;
                    if (dt.Kind != DateTimeKind.Local)
                    {
                        TimeZoneInfo localZone = TimeZoneInfo.Local;
                        DateTime localTime = TimeZoneInfo.ConvertTimeFromUtc(dt, TimeZoneInfo.Local);
                        this.Properties[field.Name] = localTime;
                        continue;
                    }
                }

                this.Properties[field.Name] = field.Value;
            }
        }

        public override string ToString()
        {
            StringBuilder sb = new StringBuilder();
            //sb.Append($"[{this.SendSubject}] ");
            //if (!string.IsNullOrEmpty(this.ReplySubject)) sb.Append($"From {this.ReplySubject}: ");
            //sb.AppendLine();
            sb.Append($"ADV_CLASS='{this.Class}' ");
            sb.Append($"ADV_SOURCE='{this.Source}' ");
            sb.Append($"ADV_NAME='{this.Name}' ");
            foreach (KeyValuePair<string, object> pair in this.Properties)
                sb.Append($"({pair.Value?.GetType().Name}){pair.Key}='{pair.Value?.ToString()}' ");

            return sb.ToString();
        }

    }

}
