using System;
using System.Text;
using Microsoft.SPOT;
using SWModules;

namespace HWModules
{
    public class ErrorHandler
    {

        public enum ErrorType
        {
            warning = 0,
            nonCritical,
            critical
        }


        public delegate void CriticalErrorHandler(object sender, ErrorType e, StringBuilder label);
        public event CriticalErrorHandler criticalErrorHandler;

        private MessageQueue _messageQueue;
        private QRecord _msgQEntry;

        private int errorCount = 0;
        private int maxErrors = 1;
        private TimeSpan timeLimit = new TimeSpan(0, 0, 0);
        private StringBuilder label = new StringBuilder(64);
        private DateTime startTime;
        public bool goneCritical = false;
        public ErrorType errorType;

        public static StringBuilder sbTemp = new StringBuilder(128);

        public ErrorHandler(int maxErrors, TimeSpan timeLimit, StringBuilder label, ErrorType errorType, CriticalErrorHandler ceHandler)
        {
            this.maxErrors = maxErrors;
            this.timeLimit = new TimeSpan(timeLimit.Days, timeLimit.Hours, timeLimit.Minutes, timeLimit.Seconds);
            this.label.Clear();
            this.label.Append(label);
            this.errorType = errorType;
            this.criticalErrorHandler += ceHandler;

            _messageQueue = MessageQueue.Instance;
            _msgQEntry = new QRecord(MessageQueue.width, processMessage);
            
        }

        public ErrorHandler(int maxErrors, long minutes, string label, ErrorType errorType, CriticalErrorHandler ceHandler)
        {
            this.maxErrors = maxErrors;
            this.timeLimit = new TimeSpan(TimeSpan.TicksPerMinute*minutes);
            this.label.Clear();
            this.label.Append(label);
            this.errorType = errorType;
            this.criticalErrorHandler = ceHandler;

            _messageQueue = MessageQueue.Instance;
            _msgQEntry = new QRecord(MessageQueue.width, processMessage);
        }

        public void logError()
        {
            if (!goneCritical)
            {
                Debug.Print(label.ToString() + ": Processing error");

                if (errorCount == 0)
                {
                    startTime = DateTime.Now;
                    errorCount = 1;
                }
                else
                {
                    if (DateTime.Now - startTime >= timeLimit)
                    {
                        errorCount = 1;
                        startTime = DateTime.Now;
                        Debug.Print(label.ToString() + " Cleared error counter due to time limit expired");
                    }
                    else
                        errorCount++;
                }

                Debug.Print(label.ToString() + ": Error count = " + errorCount.ToString());
                _msgQEntry.timeStamp = DateTime.Now;
                sbTemp.Clear();
                sbTemp.Append("[ERROR] ");
                sbTemp.Append(this.label);
                sbTemp.Append(" count = ");
                sbTemp.Append(this.errorCount.ToString());
                sbTemp.Append(" of ");
                sbTemp.Append(this.maxErrors.ToString());
                Array.Clear(_msgQEntry.byteArray, 0, _msgQEntry.byteArray.Length);
                Array.Copy(UTF8Encoding.UTF8.GetBytes(sbTemp.ToString()), _msgQEntry.byteArray, sbTemp.Length);
                _msgQEntry.errorCode = 101;
                _messageQueue.Enqueue(_msgQEntry);

                if (errorCount >= maxErrors)
                {
                    criticalErrorHandler(this, this.errorType, this.label);
                    goneCritical = true;
                }
            }
        }

        public void processMessage(QRecord qRecord)
        {
            //TODO P1 Process Error message!
            sbTemp.Clear();
            sbTemp.Append(SafeEncoding.GetChars(qRecord.byteArray));
            EngrLogger.writeToColumns(sbTemp);
        }

        public static void setRecoveryMode(object o, ErrorType e, StringBuilder label)
        {
            SV.ForceRecoveryState = true;
            SV.ForceRecoveryReason.Clear();
            SV.ForceRecoveryReason.Append("setRecoveryMode set SVForceRecoveryState = true because: ");
            SV.ForceRecoveryReason.Append(label);
            EngrLogger.Comment(SV.ForceRecoveryReason.ToString());
         }


        //TODO P2  replace everything below this with object oriented approach above

        //Count parsePOnlyErrors
        private static int parsePOnlyErrors = 0;
        private static int parsePOnlyErrorsLimit = 10;
        private static TimeSpan parsePOnlyErrorTimeout = new TimeSpan(0, 10, 0);
        private static TimeSpan lastParsePOnlyErrorTime = new TimeSpan();
        private static TimeSpan parsePOnlyTimeNow = new TimeSpan();
        public static void incrParsePOnlyErrors()
        {
            parsePOnlyTimeNow = Microsoft.SPOT.Hardware.Utility.GetMachineTime();

            if (parsePOnlyErrors == 0)
            {
                lastParsePOnlyErrorTime = parsePOnlyTimeNow;
                parsePOnlyErrors = 1;
            }
            else
            {
                if ((parsePOnlyTimeNow - lastParsePOnlyErrorTime) < parsePOnlyErrorTimeout)
                {
                    parsePOnlyErrors = parsePOnlyErrors + 1;
                    if (parsePOnlyErrors > parsePOnlyErrorsLimit)
                        Debug.Print("parsePOnly Errors exceeds limit");
                }
                else
                {
                    parsePOnlyErrors = 1;
                }
                lastParsePOnlyErrorTime = parsePOnlyTimeNow;
            }
        }

        //Count Optode Event Handler errors
        private static int optodeEHErrors = 0;
        private static int optodeEHErrorsLimit = 10;
        private static TimeSpan optodeEHErrorTimeout = new TimeSpan(0, 10, 0);
        private static TimeSpan lastoptodeEHErrorTime = new TimeSpan();
        private static TimeSpan optodeEHTimeNow = new TimeSpan();
        public static void incrOptodeEHErrors()
        {
            optodeEHTimeNow = Microsoft.SPOT.Hardware.Utility.GetMachineTime();

            if (optodeEHErrors == 0)
            {
                lastoptodeEHErrorTime = optodeEHTimeNow;
                optodeEHErrors = 1;
            }
            else
            {
                if ((optodeEHTimeNow - lastoptodeEHErrorTime) < optodeEHErrorTimeout)
                {
                    optodeEHErrors = optodeEHErrors + 1;
                    if (optodeEHErrors > optodeEHErrorsLimit)
                    {
                        //Debug.Print("optodeEH Errors exceeds limit");
                    }
                }
                else
                {
                    optodeEHErrors = 1;
                }
                lastoptodeEHErrorTime = optodeEHTimeNow;
            }
        }

        //Count structured queue Enqueue errors
        private static int enqueueErrors = 0;
        private static int enqueueErrorsLimit = 10;
        private static TimeSpan enqueueErrorTimeout = new TimeSpan(0, 10, 0);
        private static TimeSpan lastEnqueueErrorTime = new TimeSpan();
        private static TimeSpan enqueueTimeNow = new TimeSpan();
        public static void incrEnqueueErrors()
        {
            enqueueTimeNow = Microsoft.SPOT.Hardware.Utility.GetMachineTime();

            if (enqueueErrors == 0)
            {
                lastEnqueueErrorTime = enqueueTimeNow;
                enqueueErrors = 1;
            }
            else
            {
                if ((enqueueTimeNow - lastEnqueueErrorTime) < enqueueErrorTimeout)
                {
                    enqueueErrors = enqueueErrors + 1;
                    if (enqueueErrors > enqueueErrorsLimit)
                    {
                        //Debug.Print("enqueueError Errors exceeds limit");
                    }
                }
                else
                {
                    enqueueErrors = 1;
                }
                lastEnqueueErrorTime = enqueueTimeNow;
            }
        }

        //Check for too many sequential identical pressure values in a given time period
        private static int stuckPressureErrors = -1;
        private static int stuckPressureErrorsLimit = 20;       //GM 2015Oct28 Changed from 34 to 20
        private static double lastPressure = double.NaN;
        private static TimeSpan stuckPressureErrorPeriod = new TimeSpan(0, 10, 0);       
        private static TimeSpan stuckPressureStartTime = new TimeSpan();
        private static TimeSpan stuckPressureTimeNow = new TimeSpan();
        public static bool isPressureStuck(double inPressure)
        {
            //ctd.SBE41LPF.Pressureis initialized as double.NaN, so if SBE41 hasn't given a pressure yet, inPressure
            //will come in as double.NaN. Here we change double.NaN to (double)0 so logical evaluations conducted
            //below will not produce eroneous results (for example: 17.5 == double.NaN evaluates true).

            if (double.IsNaN(inPressure))
                inPressure = 0;

            stuckPressureTimeNow = Microsoft.SPOT.Hardware.Utility.GetMachineTime();
            //Initialize the timer and counter
            if (stuckPressureErrors == -1)
            {
                lastPressure = inPressure;
                stuckPressureErrors = stuckPressureErrors + 1;
                stuckPressureStartTime = stuckPressureTimeNow;
            }
            else  //Increment or reset the counter
            {
                if (inPressure == lastPressure)
                {
                    stuckPressureErrors = stuckPressureErrors + 1;
                }
                else
                    stuckPressureErrors = -1;
            }

            //If the error Period has has expired check if error count exceeds the limit
            if ((stuckPressureTimeNow - stuckPressureStartTime) > stuckPressureErrorPeriod)
            {
                if (stuckPressureErrors > stuckPressureErrorsLimit)
                {
                    //Try resetting the CTD with qsr command.  TODO Do a power reset when we have power switching
                    //EngrLogger.writeToColumns("Resetting SBE41 with qsr");
                    //TODO P0 Need to figure out how to make this work with a singleton ctd
                    //ctd.resetSBE41();

                    stuckPressureErrors = -1;
                    return (true);
                }
                else
                    return (false);
            }
            else
                //TODO P2 return below bool to false. this is here to force EAAscend because RomBOOT was seen when stuck in EA.
                return (false);


        }
    }
}
