/*******************************************************************************
* Copyright (C) 2016 Maxim Integrated Products, Inc., All rights Reserved.
* 
* This software is protected by copyright laws of the United States and
* of foreign countries. This material may also be protected by patent laws
* and technology transfer regulations of the United States and of foreign
* countries. This software is furnished under a license agreement and/or a
* nondisclosure agreement and may only be used or reproduced in accordance
* with the terms of those agreements. Dissemination of this information to
* any party or parties not specified in the license agreement and/or
* nondisclosure agreement is expressly prohibited.
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL MAXIM INTEGRATED BE LIABLE FOR ANY CLAIM, DAMAGES
* OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
* Except as contained in this notice, the name of Maxim Integrated
* Products, Inc. shall not be used except as stated in the Maxim Integrated
* Products, Inc. Branding Policy.
*
* The mere transfer of this software does not imply any licenses
* of trade secrets, proprietary technology, copyrights, patents,
* trademarks, maskwork rights, or any other form of intellectual
* property whatsoever. Maxim Integrated Products, Inc. retains all
* ownership rights.
*******************************************************************************
*/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using RPCSupport.Streaming;
using HealthSensorPlatform.Model;
using HealthSensorPlatform.View;
using HealthSensorPlatform.CustomControls;

namespace HealthSensorPlatform.Presenter
{
    class DataLogPresenter
    {
        IDataLogModel model;
        DataLoggingView view;
        IRawFileLogView temperatureLog1;
        IRawFileLogView temperatureLog2;
        IRawFileLogView pressureLog;

        IRawFileLogView accelerometerLog;
        IRawFileLogView opticalLog;

        IRawFileLogView ecgLog;
        IRawFileLogView bioZLog;
        IRawFileLogView paceLog;
        IRawFileLogView rToRLog;

        IFormView formView;

        StatusForm statusForm;

        EcgView ecgView; // Needed to obtain some state information, should be decoupled later and use from data log model
        OpticalView opticalView;

        int count = 0;
        int bioZCount = 0;
        int ppgCount = 0;
        int accelCount = 0;
        PaceData paceData;
        RToRCalculator rToRCalculator;
        bool rToRFirst = true;

        string logFileDirectory = null;

        View.FileLogView.FileLogHeader fileLogHeader = new View.FileLogView.FileLogHeader();

        public DataLogPresenter(IFormView formView, EcgView ecgView, OpticalView opticalView, IDataLogModel model, DataLoggingView view, 
            IRawFileLogView temperatureLog1, IRawFileLogView temperatureLog2, IRawFileLogView pressureLog, 
            IRawFileLogView accelerometerLog, IRawFileLogView opticalLog, 
            IRawFileLogView ecgLog, IRawFileLogView bioZLog, IRawFileLogView paceLog, IRawFileLogView rToRLog)
        {
            this.model = model;
            this.view = view;

            this.formView = formView;
            this.ecgView = ecgView;
            this.opticalView = opticalView;

            this.temperatureLog1 = temperatureLog1;
            this.temperatureLog2 = temperatureLog2;
            this.pressureLog = pressureLog;

            this.accelerometerLog = accelerometerLog;
            this.opticalLog = opticalLog;

            this.ecgLog = ecgLog;
            this.bioZLog = bioZLog;
            this.paceLog = paceLog;
            this.rToRLog = rToRLog;

            view.LogDownloadStart += new EventHandler<EventArgs>(OnLogDownloadStart);
            view.MissionWrite += new EventHandler<EventArgs>(OnMissionWrite);
            view.MissionRead += new EventHandler<EventArgs>(OnMissionRead);
            view.MissionErase += new EventHandler<EventArgs>(OnMissionErase);
            model.LogData += new EventHandler<PartialArrayIntAvailableEventArgs>(OnLogData);
        }
        
        public void ProcessTemperature1(int[] data)
        {
            double[] calcTemp;

            calcTemp = CalculateTemperature(data);
            temperatureLog1.DisplayTemperature(data, calcTemp);
        }

        public void ProcessTemperature2(int[] data)
        {
            double[] calcTemp;

            calcTemp = CalculateTemperature(data);
            temperatureLog2.DisplayTemperature(data, calcTemp);
        }

        public double[] CalculateTemperature(int[] data)
        {
            double[] calcTemp = new double[data.Length];
            int rawCode;

            for (int i = 0; i < data.Length; i++ )
            {
                rawCode = data[i];

                if (rawCode > 0x7fff)
                    rawCode -= 0x10000;

                calcTemp[i] = rawCode / Math.Pow(2, 8);
            }

            return calcTemp;
        }

        public void ProcessPressure(int[] data)
        {
            int[] rawTemp = new int[data.Length/2];
			int[] rawPress = new int[data.Length/2];
            double[] calcTemp, calcPress;
			
			for (int i = 0, j = 0; i < data.Length; i = i + 2, j++)
			{
				rawTemp[j] = data[i];
				rawPress[j] = data[i + 1];
			}

            var result = CalculatePressure(rawTemp, rawPress);
			calcTemp = result.Item1;
            calcPress = result.Item2;
			
            pressureLog.DisplayPressure(rawTemp, calcTemp, rawPress, calcPress);
        }

        public Tuple<double[], double[]> CalculatePressure(int[] temperature, int[] pressure)
        {
            double[] calcTemp = new double[temperature.Length];
            double[] calcPress = new double[pressure.Length];

            for (int i = 0; i < temperature.Length; i++ )
            {
                calcTemp[i] = temperature[i] / 10.0;
            }

            for (int i = 0; i < pressure.Length; i++ )
            {
                calcPress[i] = pressure[i] / 10;
            }

            return new Tuple<double[],double[]>(calcTemp, calcPress);
        }

        private void OnLogDownloadStart(object sender, EventArgs e)
        {

            if (view.Connected)
            {
                count = 0;
                bioZCount = 0;
                ppgCount = 0;
                accelCount = 0;

                missionRead();

                if (model.MissionSettings.Enable)
                {

                    if (selectFile())
                    {
                        string missionString;
                        StringBuilder sb = new StringBuilder();
                        foreach (string str in model.MissionString())
                        {
                            sb.Append("% ");
                            sb.Append(str);
                            sb.Append(Environment.NewLine);
                        }
                        missionString = sb.ToString().Trim(Environment.NewLine.ToCharArray());


                        if (temperatureLog1 != null && temperatureLog1.Enable)
                        {
                            temperatureLog1.WriteLine(missionString);
                            temperatureLog1.WriteLine(fileLogHeader.Temperature1);
                        }

                        if (temperatureLog2 != null && temperatureLog2.Enable)
                        {
                            temperatureLog2.WriteLine(missionString);
                            temperatureLog2.WriteLine(fileLogHeader.Temperature2);
                        }

                        if (pressureLog != null && pressureLog.Enable)
                        {
                            pressureLog.WriteLine(missionString);
                            pressureLog.WriteLine(fileLogHeader.Pressure);
                        }

                        if (accelerometerLog != null && accelerometerLog.Enable)
                        {
                            accelerometerLog.WriteLine(missionString);
                            accelerometerLog.WriteLine(fileLogHeader.Accelerometer);
                        }

                        if (opticalLog != null && opticalLog.Enable)
                        {
                            opticalLog.WriteLine(missionString);
                            opticalLog.WriteLine(fileLogHeader.Optical);
                        }

                        if (ecgLog != null && ecgLog.Enable)
                        {
                            ecgLog.WriteLine(missionString);
                            ecgLog.WriteLine(fileLogHeader.Ecg);
                        }

                        if (bioZLog != null && bioZLog.Enable)
                        {
                            bioZLog.WriteLine(missionString);
                            bioZLog.WriteLine(fileLogHeader.BioZ);
                        }

                        if (rToRLog != null && rToRLog.Enable && ecgLog.Enable)
                        {
                            rToRLog.WriteLine(missionString);
                            rToRCalculator = new RToRCalculator(ecgView.MasterClockField, view.EcgArgs.Rate, view.EcgArgs.Dlpf, view.RToRArgs.Wndw);
                            rToRLog.WriteLine(fileLogHeader.RToR);
                        }
                        else if (rToRLog != null && rToRLog.Enable)
                        {
                            rToRLog.WriteLine(missionString);
                            rToRCalculator = null;
                            rToRLog.WriteLine(fileLogHeader.RToR);
                        }

                        if (paceLog != null && paceLog.Enable)
                        {
                            paceLog.WriteLine(missionString);
                            paceLog.WriteLine(fileLogHeader.Pace);
                        }

                        statusForm = new StatusForm();
                        statusForm.Text = "Flash Download";
                        statusForm.Message = "Please wait while your data is saved to your file.";
                        statusForm.Show();

                        model.Start();
                    }
                }
            }
        }

        private void LogDownloadStop()
        {
            if (temperatureLog1 != null && temperatureLog1.Enable)
                temperatureLog1.Enable = false;

            if (temperatureLog2 != null && temperatureLog2.Enable)
                temperatureLog2.Enable = false;

            if (pressureLog != null && pressureLog.Enable)
                pressureLog.Enable = false;

            if (accelerometerLog != null && accelerometerLog.Enable)
                accelerometerLog.Enable = false;

            if (opticalLog != null && opticalLog.Enable)
                opticalLog.Enable = false;

            if (ecgLog != null && ecgLog.Enable)
                ecgLog.Enable = false;

            if (bioZLog != null && bioZLog.Enable)
                bioZLog.Enable = false;

            if (rToRLog != null && rToRLog.Enable)
                rToRLog.Enable = false;

            if (paceLog != null && paceLog.Enable)
                paceLog.Enable = false;

            formView.MessageInfo("File save complete");
            statusForm.Hide();
        }

        private void OnLogData(object sender, PartialArrayIntAvailableEventArgs e)
        {
            switch(e.reportID)
            {
                case PartialArrayIntAvailableEventArgs.PACKET_BMP280_PRESSURE:
                    if (pressureLog != null && pressureLog.Enable)
                        ProcessPressure(e.array1);
                    break;
                case PartialArrayIntAvailableEventArgs.PACKET_MAX31725_TEMP1:
                    if (temperatureLog1 != null && temperatureLog1.Enable)
                        ProcessTemperature1(e.array1);
                    break;
                case PartialArrayIntAvailableEventArgs.PACKET_MAX31725_TEMP2:
                    if (temperatureLog2 != null && temperatureLog2.Enable)
                        ProcessTemperature2(e.array1);
                    break;

                case PartialArrayIntAvailableEventArgs.PACKET_MAX30001_ECG:
                    if (ecgLog != null && ecgLog.Enable)
                        ProcessEcg(e.array1);
                    break;
                case PartialArrayIntAvailableEventArgs.PACKET_MAX30001_BIOZ:
                    if (bioZLog != null && bioZLog.Enable)
                        ProcessBioZ(e.array1);
                    break;
                case PartialArrayIntAvailableEventArgs.PACKET_MAX30001_PACE:
                    if (paceLog != null && paceLog.Enable)
                        paceData = new PaceData(e.array1);
                    break;
                case PartialArrayIntAvailableEventArgs.PACKET_MAX30001_RTOR:
                    if (rToRLog != null && rToRLog.Enable)
                        ProcessRToR(e.array1[0]);
                    break;

                case PartialArrayIntAvailableEventArgs.PACKET_LIS2DH:
                case PartialArrayIntAvailableEventArgs.PACKET_LSM6DS3_ACCEL:
                    if (accelerometerLog != null && accelerometerLog.Enable)
                        ProcessAccelerometer(e.array1, e.array2, e.array3);
                    break;
                case PartialArrayIntAvailableEventArgs.PACKET_END_OF_STREAM:
                    LogDownloadStop();
                    break;
            }

            if ((e.reportID & 0xF0) == PartialArrayIntAvailableEventArgs.PACKET_MAX30101)
            {
                if (opticalLog != null && opticalLog.Enable)
                    ProcessOptical(e.array1, e.array2, e.array3);

            }
        }

        private void OnMissionWrite(object sender, EventArgs e)
        {
            if (view.Connected)
            {
                if (view.EnableAccelerometer || view.EnableBioz || view.EnableEcg || view.EnableOpticalHR 
                    || view.EnableOpticalMulti || view.EnableOpticalSpO2 || view.EnablePace 
                    || view.EnablePressure || view.EnableRToR || view.EnableTemperature1 || view.EnableTemperature2)
                {
                    if (view.ValidateGuiElements())
                    {
                        statusForm = new StatusForm();
                        statusForm.Message = "Please wait while your flash log is cleared and new parameters are written. Do not unplug the USB connection or press start button on the board until write is complete.";
                        statusForm.Show();
                        formView.MessageInfo("Writing in progress...");
                        model.MissionErase();
                        model.EraseWrittenSectors();
                        model.MissionStartDefinition();
                        view.ProcessGuiElements();
                        //rpcClient.DataLogging.Test();
                        model.MissionWrite();
                        statusForm.Hide();
                        formView.MessageInfo("Write parameters complete");
                    }
                    else
                        formView.MessageInfo("Incorrect logging parameters");
                }
                else
                {
                    formView.MessageInfo("No devices selected");
                }
            }
        }

        private void OnMissionRead(object sender, EventArgs e)
        {
            if (view.Connected)
            {
                missionRead();
            }
        }

        private void OnMissionErase(object sender, EventArgs e)
        {
            if (view.Connected)
            {
                model.MissionErase();

                formView.MessageInfo("Erase parameters complete");
            }
        }

        private void OnLogFileEnable(object sender, EnableEventArgs e)
        {
            // Not used
        }

        private bool selectFile()
        {
            bool result = false;

            Mission settings = model.MissionSettings;

            if (settings.EnableEcg)
                result |= selectLogFileName(ecgLog, "hsp-log-ecg");

            if (settings.EnablePace)
                result |= selectLogFileName(paceLog, "hsp-log-pace");

            if (settings.EnableBioZ)
                result |= selectLogFileName(bioZLog, "hsp-log-bioz");

            if (settings.EnableRToR)
                result |= selectLogFileName(rToRLog, "hsp-log-rtor");

            if (settings.EnableOpticalHR || settings.EnableOpticalMulti || settings.EnableOpticalSpO2)
                result |= selectLogFileName(opticalLog, "hsp-log-optical");

            if (settings.EnableTemperature1)
                result |= selectLogFileName(temperatureLog1, "hsp-log-temperature1");

            if (settings.EnableTemperature2)
                result |= selectLogFileName(temperatureLog2, "hsp-log-temperature2");

            if (settings.EnablePressure)
                result |= selectLogFileName(pressureLog, "hsp-log-barometer");

            if (settings.EnableAccelerometer)
                result |= selectLogFileName(accelerometerLog, "hsp-log-accelerometer");

            return result;
        }

        bool selectLogFileName(IRawFileLogView log, string name)
        {
            bool result;

            if (logFileDirectory != null)
                log.FileDirectory = logFileDirectory;

            result = log.SelectCSVFile(name);
            log.Enable = result;

            if (result)
                logFileDirectory = log.FileDirectory;

            return result;
        }

        private void missionRead()
        {
            model.MissionRead();
            view.UpdateGuiElements(model.MissionSettings);
            if (model.MissionSettings.Enable)
                formView.MessageInfo("Read complete");
            else
                formView.MessageInfo("No parameters defined");
        }

        void ProcessBioZ(int[] rawData)
        {
            BioZFifo[] bioZFifo;
            double[] time = new double[rawData.Length];
            double sampleRate = ecgView.SampleRateBioZ;

            bioZFifo = ConvertBioZ(rawData);

            for (int i = 0; i < time.Length; i++ )
            {
                time[i] = bioZCount / (double)sampleRate;
                bioZCount++;
            }

            bioZLog.DisplayBioZ(time, bioZFifo);
        }

        public BioZFifo[] ConvertBioZ(int[] data)
        {
            BioZFifo[] impedance = new BioZFifo[data.Length];
            //EcgView.ChartInfo chartInfo = BioZInfo();
            EcgView.ChartInfo chartInfo = ecgView.BioZInfo;

            int dataShift;

            for (int i = 0; i < data.Length; i++)
            {
                dataShift = data[i] >> chartInfo.Shift;

                // Two's Complement Conversions
                if (dataShift > chartInfo.Threshold)
                {
                    dataShift -= chartInfo.Offset;
                }

                // 1.9734 = 1/2^19 * 1e-6
                impedance[i].Data = dataShift * 1.9073486328125 /
                    (chartInfo.Gain * ((chartInfo.CurrentGenerator == 0) ? 1 : chartInfo.CurrentGenerator));
                impedance[i].Code = data[i];
                impedance[i].BioZData = dataShift;
                impedance[i].BTag = data[i] & 0x07;
            }

            return impedance;
        }

        public EcgView.ChartInfo BioZInfo()
        {
            EcgView.ChartInfo info = new EcgView.ChartInfo();
            int[] currentGen = new int[] {1, 8, 16, 32, 48, 64, 80, 96};
            int[] gain = new int[] {20, 40, 80, 160};

            info.Shift = 4;
            info.Offset = 0x100000;
            info.Threshold = 0x100000;
            info.Gain = gain[model.MissionSettings.BioZArgs[10]];
            info.CurrentGenerator = currentGen[model.MissionSettings.BioZArgs[15]];

            return info;
        }

        void ProcessEcg(int[] rawData)
        {
            EcgFifo[] ecgFifo;
            double[] ecgVoltage = new double[rawData.Length];
            double[] timeSecond = new double[rawData.Length];
            double ecgRate = ecgView.SampleRateEcg;

            ecgFifo = ConvertEcg(rawData);

            for (int i = 0; i < ecgFifo.Length; i++)
            {
                timeSecond[i] = count / ecgRate;

                // Look for Pace Events
                if (paceLog.Enable)
                {
                    //for (int j = 0; j < ecgFifo.Length; j++)
                    //{
                    if (ecgFifo[i].PTag != 7)
                    {
                        PaceData.PaceRegister paceRegister = paceData.PaceGroup(ecgFifo[i].PTag);
                        List<double> timeMillsecondPace = new List<double>();
                        List<PaceData.PaceEdge> paceEdges = new List<PaceData.PaceEdge>();

                        for (int k = 0; k < 6; k++)
                        {
                            PaceData.PaceEdge edge = paceRegister.Edge[k];

                            timeMillsecondPace.Add(count / ecgRate + ConvertPace(edge.Data));
                            paceEdges.Add(edge);

                            if (edge.Last == true)
                                break;
                        }

                        paceLog.DisplayPace(timeMillsecondPace.ToArray(), paceEdges.ToArray());
                        System.Diagnostics.Debug.Print("ECG PTag = " + ecgFifo[i].PTag);
                    }
                    //}
                }

                count++;
            }

            ecgLog.DisplayEcg(timeSecond, ecgFifo);

        }

        public EcgFifo[] ConvertEcg(int[] data)
        {
            EcgFifo[] voltage = new EcgFifo[data.Length];
            //EcgView.ChartInfo chartInfo = EcgInfo();
            EcgView.ChartInfo chartInfo = ecgView.EcgInfo;

            int dataShift;

            for (int i = 0; i < data.Length; i++)
            {
                dataShift = data[i] >> chartInfo.Shift;

                // Two's Complement Conversions
                if (dataShift > chartInfo.Threshold)
                {
                    dataShift -= chartInfo.Offset;
                }

                voltage[i].Data = 1000 * 7.62939453125e-6 * dataShift / chartInfo.Gain;
                voltage[i].EcgData = dataShift;
                voltage[i].Code = data[i];
                voltage[i].PTag = data[i] & 0x07;
                voltage[i].ETag = (data[i] >> 3) & 0x07;
            }

            return voltage;
        }

        public EcgView.ChartInfo EcgInfo()
        {
            EcgView.ChartInfo info = new EcgView.ChartInfo();
            int[] gain = new int[] {20, 40, 80, 160};

            info.Shift = 6;
            info.Threshold = 0x1ffff;
            info.Offset = 0x40000;

            info.Gain = gain[model.MissionSettings.EcgArgs[10]];

            return info;
        }

        public double ConvertRToR(int data)
        {
            return ecgView.TimeResolution * 1000 * data * 512;
        }

        public double ConvertPace(int data)
        {
            return data * ecgView.TimeResolution;
        }

        public string PaceRegisterGroupToString(PaceData.PaceRegister paceRegister)
        {
            StringBuilder paceRegisterLogBuilder = new StringBuilder();
            for (int j = 0; j < 6; j++)
            {
                paceRegisterLogBuilder.Append(paceRegister.Edge[j].Data / (2 * ecgView.MasterClockFrequency));
                paceRegisterLogBuilder.Append(", ");
                paceRegisterLogBuilder.Append(paceRegister.Edge[j].Polarity ? 'R' : 'F');
                paceRegisterLogBuilder.Append(", ");
                paceRegisterLogBuilder.Append(paceRegister.Edge[j].Last ? 'Y' : 'N');
                paceRegisterLogBuilder.Append(", ");
            }

            return paceRegisterLogBuilder.ToString();
        }

        void ProcessRToR(int data)
        {
            if (rToRCalculator != null)
            {
                if (rToRFirst)
                {
                    rToRLog.DisplayRToR(data, rToRCalculator.Corrected(data, true) / 1000);
                    rToRFirst = false;
                }
                else
                    rToRLog.DisplayRToR(data, rToRCalculator.Corrected(data, false) / 1000);
            }
            else
            {
                rToRLog.DisplayRToR(data, 0);
            }
        }

        public void ProcessOptical(int[] red, int[] ir, int[] green)
        {
            int sampleRate = opticalView.OpticalSampleRate;
            double[] time = new double[red.Length];

            for (int i = 0; i < time.Length; i++ )
            {
                time[i] = ppgCount / (double)sampleRate;
                ppgCount++;
            }

            opticalLog.DisplayPpg(time, new int[][] { red, ir, green });
        }

        public void ProcessAccelerometer(int[] x, int[] y, int[] z)
        {
            int sampleRate = view.AccelSampleRate;
            double[] time = new double[x.Length];

            for (int i = 0; i < time.Length; i++)
            {
                time[i] = accelCount / (double)sampleRate;
                accelCount++;
            }

            accelerometerLog.DisplayXYZ(time, new int[][] { x, y, z });
        }
    }
}
