using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Maser.Feanor.Model;
using Maser.Feanor.Biz;
using System.IO;
using System.Globalization;

namespace FeanorProjects
{
    public static class Export
    {
        private static StringBuilder _Content; // File content
        private static List<List<Result>> _Results = new List<List<Result>>();

        public static void Create(Project Project, Chamber Chamber)
        {
            _Project = Project;
            _Chamber = Chamber;
            _Sensors = new List<Sensor>();
            _File = "";

            // Set project stop to UTC for current object
            _Project.Stop = _Project.Stop == null ? Time.UTC : (DateTime)_Project.Stop;
            
        }


        public static List<Sensor> Sensors
        {
            get { return _Sensors; }
            set { _Sensors = value; }
        }
        private static List<Sensor> _Sensors = new List<Sensor>();

        public static Project Project { get { return _Project; } }
        private static Project _Project;

        public static Chamber Chamber { get { return _Chamber; } }
        private static Chamber _Chamber;

        public static String File { get { return _File; } set { _File = value; } }
        private static String _File;


        public static Boolean ToFile()
        {
            try
            {
                GetResults();               
            }
            catch(Exception ex)
            {
                string msg = "Exception in Export.ToFile() during GetResults()  ->  " + ex.Message;
                throw new Exception(msg);
            }

            _Content = new StringBuilder(); 
            _Content.AppendLine(ProjectHeader);
            _Content.AppendLine(ChamberHeader);
            _Content.AppendLine(SensorHeader);            
            _Content.AppendLine(UncertaintyHeader);          
            AppendDataField();        
            _Content.AppendLine(FileStamp);

            // Write the file
            try
            {
                FileStream fileStream = new FileStream(_File, FileMode.Create);
                StreamWriter MyStreamWriter = new StreamWriter(fileStream);
                MyStreamWriter.WriteLine(_Content);
               

                MyStreamWriter.Close();
                fileStream.Close();                
                return true;
            }
            catch (Exception ex)
            {
                string msg = "Exception in Export.ToFile() during writing of file  ->  " + ex.Message;
                throw new Exception(msg);
            }
        }



        private static void AppendDataField()
        {
            List<ExportDataItem> exportDataItems = new List<ExportDataItem>();

            String[,] dataField = new String[_Results[0].Count, _Sensors.Count + 1];
            
            DateTime start = _Results[0][0].TimeStamp; 

            // sensor0 primary sensor (with timestamp)
            for (int i = 1; i < _Sensors.Count; i++)            //sensor1,sensor2,.... (without timestamp)
            {
                Sensor sensor = _Sensors[i];
                List<Result> results = _Results[i];
            }

            int count = 0;
            
            TimeSpan maxDifference = new TimeSpan(0, 0, 20);     // max difference in time from sensor 0 timestamp
            TimeSpan minDifference = new TimeSpan(0, 0, -20);    // min difference in time from sensor 0 timestamp

            //Console.WriteLine("count 0 and 1  "+ _Results[0].Count.ToString() + " " +_Results[2].Count.ToString());
            //foreach (Result r in _Results[2])  // Itereer door elke rij van de eerste sensor
            //Console.WriteLine("v ch1 " + r.TimeStamp.ToString() + " | " + r.Value.ToString());

          

          Int32 foundsensorpk = 0;
            double[] CalibrCoeff = new double[40];

            for (int i = 0; i < _Sensors.Count; i++)
            {
                try
                {
                    List<Int32> calibrationPKs = new List<int>();
                    foreach (Result r in _Results[i])
                    {
                        if (!calibrationPKs.Contains(r.CalibrationPK))
                        {                           
                            calibrationPKs.Add(r.CalibrationPK);                           
                            foundsensorpk = r.SensorPK;
                        }
                    }
                   
                    Calibration t = new Calibrations().GetLastCreatedBySensorPK(foundsensorpk);

                    if (t != null) // calibration found
                    {     
                        CalibrCoeff[2*i] = t.Coefficients[1];  // gain
                        CalibrCoeff[2*i+1] = t.Coefficients[0]; // offset
                    }
                    else // no calibration found
                    {
                        CalibrCoeff[2 * i] = 1.0; // gain
                        CalibrCoeff[2 * i + 1] = 0.0; // offset
                    }
                }
                catch
                {
                    string msg = "Could not retrieve calibrations for sensor ";                    
                }                
            }

            int[] shift = new int[_Sensors.Count];
            int[] skipcount = new int[_Sensors.Count];

            #region oldExportSorter
            /*
            // Primary sensor is sensor with lowest ID (the first object in _Sensors)
            foreach (Result r in _Results[0])                   // for each row (first sensor)            
            {
                ExportDataItem item = new ExportDataItem(r.TimeStamp, (r.Value*CalibrCoeff[0]+CalibrCoeff[1]), _Results.Count, start);          // for sensor 0 export value and timestamp first sensor                
                exportDataItems.Add(item);

                Console.Write(r.TimeStamp.ToString() + "   " + r.Value.ToString() + "  ");

                for (int i = 1; i < _Sensors.Count; i++)                                                        // for the sensor1, sensor2, ....
                {                    
                    try
                    {                        
                        TimeSpan difference = r.TimeStamp - _Results[i][count-shift[i]].TimeStamp;              // Calculate difference between sensor 0 and sensor X

                        Console.Write(difference.TotalSeconds.ToString() + "  ");
                         
                        if (difference <= maxDifference && difference >= minDifference)                         // If difference is between min and max
                        {
                            // difference between sensor0 and sensor X is between max and min difference --> ADD value to file
                        }
                        else
                        {
                            // difference between sensor0 and sensor X is not between max and min difference --> DO NOT ADD VALUE to file
                            shift[i]++;
                        }

                        if (_Results[i][count - shift[i]].Value == 9999.99)
                        {
                            item.AddValue(_Results[i][count-shift[i]].Value, i);                           
                        }
                        else
                        {
                            _Results[i][count - shift[i]].Value = _Results[i][count-shift[i]].Value * CalibrCoeff[2*i] + CalibrCoeff[2*i+1];
                            item.AddValue(_Results[i][count-shift[i]].Value, i);                           
                        }
                       Console.Write(_Results[i][count - shift[i]].Value.ToString() + "  ");
                    }
                    catch { }
                }
                count++;                                                    // next row
                Console.WriteLine();
            }
            */
            #endregion

            bool debug = true;

            foreach (Result r in _Results[0])  
            {
                //Console.WriteLine("timestamp "+ r.TimeStamp.ToString());
                ExportDataItem item = new ExportDataItem(
                    r.TimeStamp,
                    (r.Value * CalibrCoeff[0] + CalibrCoeff[1]),
                    _Results.Count,
                    start
                );
                exportDataItems.Add(item);

                for (int i = 1; i < _Sensors.Count; i++)  // Itereren over andere sensoren (o.a. sensor 2)
                {
                    bool dataAdded = false;
                    int index = BinarySearchClosestTimestamp(_Results[i], r.TimeStamp);

                    if (index != -1)
                    {
                        TimeSpan diff = _Results[i][index].TimeStamp - r.TimeStamp;
                        if (diff <= maxDifference && diff >= minDifference)
                        {
                            double calibratedValue = (_Results[i][index].Value != 9999.99)
                                ? _Results[i][index].Value * CalibrCoeff[2 * i] + CalibrCoeff[2 * i + 1]
                                : _Results[i][index].Value;

                            item.AddValue(calibratedValue, i);
                            dataAdded = true;
                        }
                    }

                    // Voeg 0 toe als er geen match was
                    if (!dataAdded)
                    {
                        item.AddValue(0.0, i);
                    }
                }
            }

           
            foreach (ExportDataItem exportDataItem in exportDataItems)      // for each line of .csv file
            {                           
                _Content.AppendLine(exportDataItem.ToString());             // add line to content                
            }
        }

        private static int BinarySearchClosestTimestamp(List<Result> results, DateTime targetTimestamp)
        {
            int left = 0, right = results.Count - 1;
            while (left <= right)
            {
                int mid = left + (right - left) / 2;
                if (results[mid].TimeStamp == targetTimestamp)
                    return mid;
                else if (results[mid].TimeStamp < targetTimestamp)
                    left = mid + 1;
                else
                    right = mid - 1;
            }
            int closestIndex = Math.Max(0, Math.Min(left, results.Count - 1));
            return closestIndex;
        }



        private static void GetResults()
        {
            _Results = new List<List<Result>>();
            foreach (Sensor sensor in _Sensors)
            {
                if(Chamber.Humidity == true)
                {
                    _Results.Add(new Results().GetByProjectWindow(_Project.PK, sensor.PK));
                }
                else
                {
                    _Results.Add(new Results().GetByProjectWindowOudeHal(_Project.PK, sensor.PK));
                }

               

            }               
        }        


        private static string UncertaintyHeader
        {
            get
            {
                String uncertainty;

                uncertainty = String.Format("Uncertainty (k=2), {0:0}, ", Math.Ceiling((double)_Chamber.Interval / 2));

                double U = 0;

                Int32 foundsensorpk = 0;
                for (int i = 0; i < _Sensors.Count; i++)
                {
                    try
                    {
                        List<Int32> calibrationPKs = new List<int>();
                        foreach (Result r in _Results[i])
                        {

                            if (!calibrationPKs.Contains(r.CalibrationPK))
                            {
                                //Console.WriteLine(r.SensorPK.ToString()); // 21 dit gaat goed
                                calibrationPKs.Add(r.CalibrationPK);
                                //Console.WriteLine(r.CalibrationPK.ToString());
                                foundsensorpk = r.SensorPK;
                            }
                        }
                        Console.Write("Found sensor PK = ");
                        Console.WriteLine(foundsensorpk.ToString());


                        Calibration t = new Calibrations().GetLastCreatedBySensorPK(foundsensorpk);

                        if (t != null) // calibration found
                        { 
                          
                        U = t.Uncertainty;

                           
                        Console.Write("a = ");
                        Console.Write(t.Coefficients[1]); // gain

                        Console.Write("    ");

                        Console.Write("b = ");
                        Console.WriteLine(t.Coefficients[0]); // offset
                           

                         }
                        else // no calibration found
                        {
                            Console.WriteLine("No calibration found for this sensor ( a = 0 and b = 0 )");
                            U = 0.0;
                        }
                        
                    }
                    catch
                    {
                        string msg = "Could not retrieve calibrations for sensor ";
                        msg += string.Format("PK={0:0} ChamberPK={1:0) ID={2:00} [{3}]", _Sensors[i].PK, _Sensors[i].ChamberPK, _Sensors[i].ID, _Sensors[i].Description);
                        throw new Exception(msg);
                    }
                    
                    uncertainty += String.Format("{0:0.00},", U);
                }

                // Remove last comma of line   
                uncertainty = uncertainty.Remove(uncertainty.Length - 1, 1);
                Console.WriteLine(uncertainty);

                return uncertainty;
            }
        }

        private static string SensorHeader
        {
            get
            {
                String sensorHeader;
                
                // Are sensors from other chambers linked to project's chamber?
                bool linked = false;
                foreach (Sensor sensor in _Sensors)
                {
                    if (sensor.ChamberPK != _Chamber.PK)
                    {
                        linked = true;
                        break;
                    }
                }

                sensorHeader = "Timestamp, Duration,";
                foreach (Sensor sensor in _Sensors)
                {
                    if(linked)
                    {
                        try
                        {
                            Chamber c = new Chambers().GetByPK(sensor.ChamberPK);
                            sensorHeader = string.Format("{0:00000}.{1:00} {2}", c.MIDS, sensor.ID, sensor.Description);
                        }
                        catch { sensorHeader += String.Format("\"#{0:00} {1}\",", sensor.ID, sensor.Description); }
                    }
                    else
                        sensorHeader += String.Format("\"#{0:00} {1}\",", sensor.ID, sensor.Description);
                }

                // Remove last comma of line   
                sensorHeader = sensorHeader.Remove(sensorHeader.Length - 1, 1);

                
                // Add newline
                sensorHeader += "\n";

                // Units
                sensorHeader += "YYYY/MM/DD @ hh:mm:ss, s,";
                foreach (Sensor sensor in _Sensors)
                    sensorHeader += String.Format("{0},", sensor.Units);


                // Remove last comma of line   
                sensorHeader = sensorHeader.Remove(sensorHeader.Length - 1, 1);

                return sensorHeader;
            }
        }

        private static string ProjectHeader
        {
            get
            {
                DateTime stop = (DateTime)_Project.Stop; // Cast to non-nullable

                string projectHeader;
                projectHeader = "Project details\r\n";
                projectHeader += String.Format(",Customer, \"{0}\"\r\n", _Project.Customer);
                projectHeader += String.Format(",P{0:000000} , \"{1}\"\r\n", _Project.ProjectID, _Project.ProjectDescription);
                projectHeader += String.Format(",Sub  {0:00} , \"{1}\"\r\n", _Project.SubProject, _Project.SubProjectDescription);
                               
                String stepD = _Project.StepDescription;
                stepD = stepD.Replace(System.Environment.NewLine, " ");
                Console.WriteLine(stepD);

                projectHeader += String.Format(",Step {0:00} , \"{1}\"\r\n", _Project.Step, stepD);
                projectHeader += String.Format(",Start, {0:0000}/{1:00}/{2:00} @ {3:00}:{4:00}\r\n", _Project.Start.Year, _Project.Start.Month, _Project.Start.Day, _Project.Start.Hour+1, _Project.Start.Minute);
                projectHeader += String.Format(",Stop, {0:0000}/{1:00}/{2:00} @ {3:00}:{4:00}\r\n", stop.Year, stop.Month, stop.Day, stop.Hour+1, stop.Minute);
                projectHeader += "\r\n";
                projectHeader += ",Note: Time is observed in Central European Time+1 (UTC+1).\r\n";
                return projectHeader;
            }
        }

        private static string ChamberHeader
        {
            get
            {
                // return String.Format("Apparatus, \"{0:00000} {1}\"\r\n", _Chamber.MIDS, _Chamber.Description);
                return String.Format("Apparatus, \"{0:00000}\"\r\n", _Chamber.MIDSandDescription);
            }
        }

        public static string FileStamp
        {
            get
            {
                string str = "Maser Engineering B.V. Central Monitoring System";
                //return String.Format("\r\n{0}{1}{2} {3}", Time.UTC.Year, Time.UTC.Month, Time.UTC.Day, str); // <-- original \r\n zorgt voor witregel wat problemen met META geeft
                return String.Format("{0}{1}{2} {3}", Time.UTC.Year, Time.UTC.Month, Time.UTC.Day, str);
            }
        }

        public static string DefaultExportFileName
        {
            get
            {
                return String.Format("P{0:00000}-{1:00}-{2:00} measurements in {3:00000}.csv", _Project.ProjectID, _Project.SubProject, _Project.Step, _Chamber.MIDS);
            }
        }


    }





    public class ExportDataItem
    {
        public DateTime TimeStamp
        {
            get { return _timeStamp; }
        }
        private DateTime _timeStamp;

        private Double[] _sensorData;

        private DateTime _startOfProject;

        public ExportDataItem(DateTime timeStamp, Double firstValue, Int32 sensorCount, DateTime startOfProject)
        {
            _timeStamp = timeStamp;
            _sensorData = new Double[sensorCount];
            _sensorData[0] = firstValue;           
            _startOfProject = startOfProject;
        }

        public void AddValue(Double value, Int32 SensorNr)
        {
            try
            {
                if (SensorNr < _sensorData.Length)
                    _sensorData[SensorNr] = value;
            }
            catch { }
            
        }

        public override string ToString()
        {
            StringBuilder result = new StringBuilder();
            var culture = CultureInfo.InvariantCulture; // Zorgt ervoor dat het decimaalteken altijd een punt is.


            // exported time is UTC + 1 !!!!
            // onderstaande regel zorgde voor fouten rond middernacht data zoals 00h01 werd geexporteerd als 24h01
            // result.AppendFormat("{0:0000}/{1:00}/{2:00} @ {3:00}:{4:00}:{5:00},", TimeStamp.Year, TimeStamp.Month, TimeStamp.Day, TimeStamp.Hour+1, TimeStamp.Minute, TimeStamp.Second);

            DateTime localTime = TimeStamp.AddHours(1); // UTC +1 correct verwerken
            result.AppendFormat("{0:0000}/{1:00}/{2:00} @ {3:00}:{4:00}:{5:00},",
                localTime.Year, localTime.Month, localTime.Day,
                localTime.Hour, localTime.Minute, localTime.Second);


            result.AppendFormat("{0}", ((TimeSpan)TimeStamp.Subtract(_startOfProject)).TotalSeconds);

            foreach (Double value in _sensorData)
            {
                if (Math.Abs(value) < 1 && value != 0)
                {
                    int k = 0;
                    Double v = Math.Abs(value);
                    while (v < 10 && k < 10)
                    {
                        v = v * 10;
                        k++;
                    }
                    string s = ",{0:0.";
                    for (int i = 0; i < k; i++)
                    {
                        s += "0";
                    }
                    s += "000}";
                    result.AppendFormat(culture,s, value);
                }
                else
                {
                    result.AppendFormat(culture,",{0:0.00}", value);                              
                }
            }
            //Console.WriteLine(result.ToString());
            return result.ToString();
        }
    }




}
