using System;
using Microsoft.SPOT;

using System.Text;
using System.Threading;

namespace CPF
{
    class ErrorHandler
    {
        public enum ErrorType
        {
            warning = 0,
            nonCritical,
            critical
        }

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

        private static StructQueue.qStruct qS = new StructQueue.qStruct();

        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)
        {
            this.maxErrors = maxErrors;
            this.timeLimit = new TimeSpan(timeLimit.Hours, timeLimit.Minutes, timeLimit.Seconds);
            this.label.Clear();
            this.label.Append(label);
            this.errorType = errorType;

            qS.byteArray = new byte[StructQueue.byteArrayWidth];
        }

        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());
                qS.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(qS.byteArray, 0, qS.byteArray.Length);
                Array.Copy(UTF8Encoding.UTF8.GetBytes(sbTemp.ToString()), qS.byteArray, sbTemp.Length);
                qS.qRecordType = StructQueue.QRecordType.Error;
                qS.errorCode = 0;
                Program.sQueue.Enqueue(qS);

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

        public static void setRecoveryMode(object o, ErrorType e)
        {
            SV.ForceRecoveryMode = true;
        }


        //TODO 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, 2, 0);       //GM 2015Aug04 Changed from (0, 1, 0) to (0, 2, 0)
        private static TimeSpan stuckPressureStartTime = new TimeSpan();
        private static TimeSpan stuckPressureTimeNow = new TimeSpan();
        public static bool isPressureStuck(double inPressure)
        {
            //SV.Pressure is 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");
                    SBE41.resetSBE41();

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


        }
    }
}
