/*******************************************************************************
* 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 HealthSensorPlatform.View;
using HealthSensorPlatform.Model;
using HealthSensorPlatform.CustomControls;

using RPCSupport;
using RPCSupport.Streaming;

namespace HealthSensorPlatform.Presenter
{
    public class RawFileLogPresenter
    {
        IRawFileLogView ecgLog;
        IRawFileLogView bioZLog;
        IRawFileLogView rToRLog;
        IRawFileLogView paceLog;
        IFormView formView;
        IEcgView ecgView;

        bool fileLog = false; // user enabled file logging
        bool streaming = false; // streaming data or flash log data, only save data from streaming
        //bool flashLog = false;

        IDataLoggingView dataLogView;

        PaceData paceData;

        RToRCalculator rToRCalculator;
        bool rToRFirst = true; // received first RR value for offset correction

        //bool ecgLeadOff; // DC Lead off data for ECG or BioZ Channel
        DCLeadOff leadOff; // lead off status bits
        ACLeadOff acLeadOff; // bioz lead off status bits

        int count = 0;
        int bioZCount = 0;
        //int ppgCount = 0;
        //int accelCount = 0;

        string logFileDirectory = null;

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

        public RawFileLogPresenter(IRawFileLogView ecg, IRawFileLogView bioZ, IRawFileLogView rToR, IRawFileLogView pace, IFormView formView, RPCClient rpcClient, IEcgView ecgView, IDataLoggingView dataLogView)
        {
            this.ecgLog = ecg;
            this.bioZLog = bioZ;
            this.rToRLog = rToR;
            this.paceLog = pace;
            this.formView = formView;
            this.ecgView = ecgView;

            this.dataLogView = dataLogView;

            formView.FileLogEnable += new EventHandler<EnableEventArgs>(OnLogFileEnable);
            ecgView.StreamingStartStop += new EventHandler<StreamingStartStopEventArgs>(OnStreamStart);
            //dataLogView.LogDownloadStart += new EventHandler<EventArgs>(OnLogDownloadStart);
            rpcClient.streaming.PartialArrayIntAvailable += new EventHandler<PartialArrayIntAvailableEventArgs>(OnStreamData);
        }

        public void OnLogFileEnable(object sender, EnableEventArgs e)
        {
            fileLog = e.Enable;

            if (e.Enable)
            {
                bool result = false;
                IRawFileLogView log = ecgLog;
                string filePrefixName = String.Empty;

                switch(e.Stream)
                {
                    case StreamType.Ecg:
                        log = ecgLog;
                        filePrefixName = "hsp-ecg";
                        break;
                    case StreamType.RToR:
                        log = rToRLog;
                        filePrefixName = "hsp-rtor";
                        break;
                    case StreamType.Pace:
                        log = paceLog;
                        filePrefixName = "hsp-pace";
                        break;
                    case StreamType.BioZ:
                        log = bioZLog;
                        filePrefixName = "hsp-bioz";
                        break;
                }

                if (logFileDirectory != null) // Set starting directory to be the same as the last
                    log.FileDirectory = logFileDirectory;

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

                if (result) // Save directory if success
                    logFileDirectory = log.FileDirectory;

                formView.LogFileItem(e.Stream, result);
            }
            else
            {
                switch (e.Stream)
                {
                    case StreamType.Ecg:
                        ecgLog.Enable = false;
                        break;
                    case StreamType.RToR:
                        rToRLog.Enable = false;
                        break;
                    case StreamType.Pace:
                        paceLog.Enable = false;
                        break;
                    case StreamType.BioZ:
                        bioZLog.Enable = false;
                        break;
                }

                formView.LogFileItem(e.Stream, false);
            }
        }

        public void OnStreamStart(object sender, StreamingStartStopEventArgs e)
        {
            streaming = e.state;
            count = 0;
            bioZCount = 0;

            if (e.state)
            {
                leadOff = new DCLeadOff { NegativeHigh = false, NegativeLow = false, PostiveHigh = false, PostiveLow = false };
                acLeadOff = new ACLeadOff { BioZOverRange = false, BioZUnderRange = false };

                if (ecgLog != null && ecgLog.Enable && ecgView.EnableECG)
                {
                    //ecgLog.WriteLine("% " + ecgView.HspSetting.EcgSettingString()); // TODO: What is the requirement for headers
                    ecgLog.WriteLine(fileLogHeader.Ecg);
                }

                if (bioZLog != null && bioZLog.Enable && ecgView.EnableBioZ)
                {
                    //ecgLog.WriteLine("% " + ecgView.HspSetting.BioZSettingString());
                    bioZLog.WriteLine(fileLogHeader.BioZ);
                }

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

                if (paceLog != null && paceLog.Enable && ecgView.EnablePace)
                {
                    paceLog.WriteLine(fileLogHeader.Pace);
                }
            }
            else
            {
                if (ecgLog != null)
                    ecgLog.Enable = false;

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

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

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

        void fileLogStop()
        {
            fileLog = false;
            stop();

        }

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

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

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

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

            formView.LogFileItem(StreamType.Ecg, false);
            formView.LogFileItem(StreamType.RToR, false);
            formView.LogFileItem(StreamType.Pace, false);
            formView.LogFileItem(StreamType.BioZ, false);
        }

        private void OnStreamData(object sender, PartialArrayIntAvailableEventArgs e)
        {
            if (streaming)
            {
                switch (e.reportID)
                {
                    case PartialArrayIntAvailableEventArgs.PACKET_MAX30001_ECG:
                        if (ecgLog.Enable)
                            ProcessEcg(e.array1);
                        break;
                    case PartialArrayIntAvailableEventArgs.PACKET_MAX30001_BIOZ:
                        if (bioZLog.Enable)
                            ProcessBioZ(e.array1);
                        break;
                    case PartialArrayIntAvailableEventArgs.PACKET_MAX30001_PACE:
                        if (paceLog.Enable)
                            paceData = new PaceData(e.array1);
                        break;
                    case PartialArrayIntAvailableEventArgs.PACKET_MAX30001_RTOR:
                        if (rToRLog.Enable)
                            ProcessRToR(e.array1[0]);
                        break;
                    case PartialArrayIntAvailableEventArgs.PACKET_END_OF_STREAM:
                        fileLogStop();
                        break;
                    case PartialArrayIntAvailableEventArgs.PACKET_MAX30001_LEADOFF_DC:
                        ProcessLeadOff(e.array1[0]);
                        break;
                    case PartialArrayIntAvailableEventArgs.PACKET_MAX30001_LEADOFF_AC:
                        ProcessACLeadOff(e.array1[0]);
                        break;
                }
            }
        }

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

            bioZFifo = ConvertBioZ(rawData);

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

            if (ecgView.EnableDCLeadOff == false && ecgView.EnableBioZOverUnderRange == true)
                bioZLog.DisplayBioZ(time, bioZFifo, acLeadOff);
            else if (ecgView.EnableDCLeadOff == true && ecgView.EnableEcgDCLeadOff == false && ecgView.EnableBioZOverUnderRange == false) // && ecgLeadOff == false)
                bioZLog.DisplayBioZ(time, bioZFifo, leadOff);
            else if (ecgView.EnableDCLeadOff == true && ecgView.EnableEcgDCLeadOff == false && ecgView.EnableBioZOverUnderRange == true)
                bioZLog.DisplayBioZ(time, bioZFifo, leadOff, acLeadOff);
            else if ((ecgView.EnableDCLeadOff == false && ecgView.EnableBioZOverUnderRange == false) ||
                ecgView.EnableEcgDCLeadOff == true && ecgView.EnableEcgDCLeadOff == true && ecgView.EnableBioZOverUnderRange == false)
                bioZLog.DisplayBioZ(time, bioZFifo);
        }

        public BioZFifo[] ConvertBioZ(int[] data)
        {
            BioZFifo[] impedance = new BioZFifo[data.Length];
            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;
        }

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

            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++;
            }

            if (ecgView.EnableDCLeadOff && ecgView.EnableEcgDCLeadOff)
                ecgLog.DisplayEcg(timeSecond, ecgFifo, leadOff);
            else
                ecgLog.DisplayEcg(timeSecond, ecgFifo);
            
        }

        public EcgFifo[] ConvertEcg(int[] data)
        {
            EcgFifo[] voltage = new EcgFifo[data.Length];
            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 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 ProcessLeadOff(int data)
        {
            /*
            if (bitShiftMask(data, 8)) // ECG lead off
                ecgLeadOff = true;
            else if (bitShiftMask(data, 9)) // BioZ lead off
                ecgLeadOff = false;
            */

            leadOff = new DCLeadOff()
            {
                PostiveHigh = bitShiftMask(data, 3),
                PostiveLow = bitShiftMask(data, 2),
                NegativeHigh = bitShiftMask(data, 1),
                NegativeLow = bitShiftMask(data, 0)
            };

        }

        void ProcessACLeadOff(int data)
        {
            acLeadOff = new ACLeadOff()
            {
                BioZUnderRange = bitShiftMask(data, 1),
                BioZOverRange = bitShiftMask(data, 0),
            };
        }

        private static bool bitShiftMask(int data, int index)
        {
            int state;
            int mask = 1 << index;
            state = ((data & mask) == mask) ? 1 : 0;
            return state == 1;
        }

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