using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data.Odbc;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using NightScout.Interfaces;
using NightscoutLibrary.Configuration;
using Quobject.EngineIoClientDotNet.Client.Transports;
using Quobject.SocketIoClientDotNet.Client;

namespace NightScout
{
    public class NightscoutDataSource : INightscoutDataSource
    {
        public Socket Socket { get; set; }
        public Manager Manager { get; set; }
        public log4net.ILog Logger { get; set; }
        public IRaspberryPiButtonSource ButtonSource { get; set; }
        public INightscoutAlertConfiguration NightscoutAlertConfiguration { get; set; }
        public event Action Connected;
        public event Action<Object> DataUpdate;
        public event Action<Object> SocketError;

        public event Action<Object> Alarm;
        public event Action<Object> UrgentAlarm;
        public event Action<Object> ClearAlarm;
        public event Action StaleDataAlarm;

        private DateTime lastDataUpdate = DateTime.Now;
        private DateTime lastSgvReading = DateTime.Now;

        public NightscoutDataSource(INightscoutAlertConfiguration configuration, log4net.ILog logger)
        {
            Logger = logger;
            NightscoutAlertConfiguration = configuration;
            ResetManagerAndConnect();
            PrepareSocketEvents();
        }

        public NightscoutDataSource(INightscoutAlertConfiguration configuration, log4net.ILog logger, IRaspberryPiButtonSource buttonSource)
            : this(configuration, logger)
        {
            ButtonSource = buttonSource;
            ButtonSource.ButtonStateChange += ButtonSource_ButtonStateChange;
        }

        public void Emit(String eventName, String data)
        {
            Socket.Emit(eventName, data);
        }

        public void Emit(String eventName, JObject data)
        {
            Socket.Emit(eventName,data);
        }

        public virtual void Authorize()
        {
            JObject authRequest = new JObject {{"client", "web"}};

            Socket.Emit("authorize", authRequest);
        }

        public virtual void ClearAlerts()
        {
            Logger.Info("Sending Clear Alerts Message");
            
            Socket.Emit("ack", 2, "default", 45 * 60 * 1000);
            Logger.Debug("Sent ack");
        }

        protected virtual void OnConnect()
        {
            Logger.Info("Connected to Socket.IO endpoint");

            if (Connected != null)
                Connected();
        }

        protected virtual void OnDataUpdate(object data)
        {
            Logger.DebugFormat("Data Update: {0}", data);
            if (DataUpdate != null)
                DataUpdate(data);
            //Update lastUpdated
            JObject dataUpdate = JObject.Parse(data.ToString());
            if (dataUpdate["lastUpdated"] != null)
            { //lastUpdated is in ms
                Int32 lastUpdatedUnixTime = (Int32)(dataUpdate["lastUpdated"].ToObject<Int64>() / 1000);
                lastDataUpdate = UnixTimeStampToDateTime(lastUpdatedUnixTime);
                Logger.InfoFormat("Last data update reset to {0}", lastDataUpdate);
            }
            if (dataUpdate["sgvs"] != null)
            {
                var sgvs = dataUpdate["sgvs"].Children();
                var newestSSgv =
                    sgvs.Where(sgv => sgv["mills"] != null).OrderByDescending(sgv => sgv["mills"].Value<Int64>()).First();
                Int64 sgvReadTime = newestSSgv["mills"].Value<Int64>();
                DateTime reading = UnixTimeStampToDateTime(sgvReadTime / 1000);;

                if (reading > lastSgvReading)
                    lastSgvReading = reading;
                if(newestSSgv["mgdl"] != null)
                    Logger.InfoFormat("New sgv read of {0} mg/dl on {1}", newestSSgv["mgdl"], lastSgvReading);
            }
        }

        protected virtual void OnSocketError(object error)
        {
            //Socket.Close();
            Logger.InfoFormat("Socket Error: {0}", error);
            if (SocketError != null)
                SocketError(error);
        }

        protected virtual void OnAlarm(object alarm)
        {
            Logger.InfoFormat("Alarm: {0}", alarm);
            if (Alarm != null)
                Alarm(alarm);
        }

        protected virtual void OnUrgentAlarm(object alarm)
        {
            Logger.InfoFormat("Urgent Alarm: {0}", alarm);
            if (UrgentAlarm != null)
                UrgentAlarm(alarm);
        }
        protected virtual void OnClearAlarm(object alarm)
        {
            Logger.InfoFormat("Clear Alarm: {0}", alarm);
            if (ClearAlarm != null)
                ClearAlarm(alarm);
        }

        protected virtual void OnStaleDataAlarm()
        {
            if (StaleDataAlarm != null)
                StaleDataAlarm();
        }

        protected void ResetManagerAndConnect()
        {
            //, DisablePing = true, Transports = new List<string>() { "polling" }
            Options options;
            options = new IO.Options();
                
            

            Manager = new Manager(new Uri(NightscoutAlertConfiguration.NightscoutBaseURL), options);
            

            Socket = Manager.Socket("/"); //IO.Socket(configuration.NightscoutBaseURL, options);
        }

        private void HeartbeatThread()
        {
            while (true)
            {
                //30 minutes - (currentTime - lastDataUpdate)
                const double millisecondsToSleep = (5*60*1000); //(30*60*1000) - (DateTime.Now - lastDataUpdate).TotalMilliseconds;
                Thread.Sleep((Int32)millisecondsToSleep);
                if ((DateTime.Now - lastDataUpdate).TotalMinutes >= 20)
                { //forcefully reset data connection
                    Logger.Info("Data update timeout, closing socket");
                    Manager.EngineSocket.Close();
                    lastDataUpdate = DateTime.Now;
                }
                if ((DateTime.Now - lastSgvReading).TotalMinutes >= 30)
                { //Stale data alarm
                    OnStaleDataAlarm();
                    Logger.Info("Data stale alarm");
                    lastSgvReading = DateTime.Now;
                }
            }
        }
        private void PrepareSocketEvents()
        {
            Socket.On(Socket.EVENT_CONNECT, OnConnect);
            Socket.On(Socket.EVENT_ERROR, OnSocketError);

            Socket.On("dataUpdate", OnDataUpdate);

            Socket.On("alarm", OnAlarm);
            Socket.On("urgent_alarm", OnUrgentAlarm);
            Socket.On("clear_alarm", OnClearAlarm);

            Manager.On(Manager.EVENT_CONNECT_ERROR, (err) => Logger.InfoFormat("EVENT_CONNECT_ERROR: {0}",err));
            Manager.On(Manager.EVENT_ERROR, (err) => Logger.InfoFormat("EVENT_ERROR: {0}", err));
            Manager.On(Manager.EVENT_RECONNECT_ERROR, (err) => Logger.InfoFormat("EVENT_RECONNECT_ERROR: {0}", err));
            Manager.On(Manager.EVENT_RECONNECT_FAILED, () => Logger.Info("EVENT_RECONNECT_FAILED"));
            Manager.On(Manager.EVENT_CLOSE, (reason) => Logger.InfoFormat("EVENT_CLOSE: {0}", reason));

            Task.Factory.StartNew(HeartbeatThread,TaskCreationOptions.LongRunning);
        }

        private void ButtonSource_ButtonStateChange(bool obj)
        {
            if (obj)
            { //Button pressed, STOP active alerts
                ClearAlerts();
            }
        }

        private static DateTime UnixTimeStampToDateTime(double unixTimeStamp)
        {
            // Unix timestamp is seconds past epoch
            System.DateTime dtDateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, System.DateTimeKind.Utc);
            dtDateTime = dtDateTime.AddSeconds(unixTimeStamp).ToLocalTime();
            return dtDateTime;
        }

    }
}
