//-------------------------------------------------------------------------------------
// SID Monitor - Utility for Sudden Ionospheric Disturbances Monitoring Stations
// Copyright (C) 2006-2011 - Lionel Loudet
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
//-------------------------------------------------------------------------------------

using System;
using System.Globalization;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;


namespace SID_monitor
{
    enum GOESsat { PrimaryGOES, SecondaryGOES };

    static class GOES
    {

        /// <summary>
        /// Download GOES data and updates the GOES database
        /// </summary>
        /// <returns>The new GOES database update time. 0 if no update.</returns>
        public static SIDMonitorDateTime updateGOESdata()
        {
            if (!File.Exists(SID_monitor.Properties.Settings.Default.RRDToolGOES))
            {
               Program.MainForm.outputTextBoxDockablePanel.AddOutputTextErrorMessage(" Cannot read GOES database \"" + SID_monitor.Properties.Settings.Default.RRDToolGOES + "\"\n");
                return null;
            }

            SIDMonitorDateTime lastDatabaseUpdateTime;
            try
            {
                lastDatabaseUpdateTime = RRDTool.getLastDatabaseUpdateTime(SID_monitor.Properties.Settings.Default.RRDToolGOES, SID_monitor.Program.MainForm.outputTextBoxDockablePanel.Handle);
            }
            catch (Exception ex)
            {
                Program.MainForm.outputTextBoxDockablePanel.AddOutputTextErrorMessage(" Cannot read last GOES database update time \"" + SID_monitor.Properties.Settings.Default.RRDToolGOES +
                   "\"\n*** " + ex.ToString().Split('\n')[0] + "\n");
                return null;
            }

            // download data for each day since the last time the database has been updated
            Uri PrimaryGOESUrl;
            Uri SecondaryGOESUrl; 
            Hashtable currentPrimaryGOESdata = new Hashtable(24 * 60); //24 * 60 elements per day @ 1 element per minute
            Hashtable currentSecondaryGOESdata = new Hashtable(24 * 60); //24 * 60 elements per day @ 1 element per minute
            bool PrimaryGOES_OK = true; // Has Primary GOES correctly been downloaded?
            bool SecondaryGOES_OK = true; // Has Secondary GOES correctly been downloaded?

            if (SID_monitor.Properties.Settings.Default.Verbose)
            {
                Program.MainForm.outputTextBoxDockablePanel.AddOutputTextMessage(" Updating GOES database...\n");
            }
            string update_string = "update \"" + SID_monitor.Properties.Settings.Default.RRDToolGOES + "\" ";
            string template_string = "-t goesp:goess ";
            string data_string = String.Empty;
            for (DateTime currentDate = lastDatabaseUpdateTime.WinTime.Date; currentDate.Date <= DateTime.UtcNow.Date; currentDate = currentDate.AddDays(1))
            {
                try
                {
                    PrimaryGOESUrl = new Uri(SID_monitor.Properties.Settings.Default.GOESDataUrl + currentDate.ToString("yyyyMMdd") + "_" + SID_monitor.Properties.Settings.Default.PrimaryGOESFileSuffix);
                    extractData(readData(PrimaryGOESUrl), ref currentPrimaryGOESdata);
                    if (SID_monitor.Properties.Settings.Default.Verbose)
                    {
                        Program.MainForm.outputTextBoxDockablePanel.AddOutputTextMessage(" Primary GOES: " + currentPrimaryGOESdata.Count + " X-ray flux values for " + currentDate.ToShortDateString() + " downloaded\n");
                    }
                }
                catch (Exception ex)
                {
                    Program.MainForm.outputTextBoxDockablePanel.AddOutputTextWarningMessage(" Primary GOES: Cannot fetch data. " + ex.ToString().Split('\n')[0] + "\n");
                    PrimaryGOES_OK = false;
                }

                if (SID_monitor.Properties.Settings.Default.UpdateSecondaryGOES)
                {
                    try
                    {
                        SecondaryGOESUrl = new Uri(SID_monitor.Properties.Settings.Default.GOESDataUrl + currentDate.ToString("yyyyMMdd") + "_" + SID_monitor.Properties.Settings.Default.SecondaryGOESFileSuffix);
                        extractData(readData(SecondaryGOESUrl), ref currentSecondaryGOESdata);
                        if (SID_monitor.Properties.Settings.Default.Verbose)
                        {
                            Program.MainForm.outputTextBoxDockablePanel.AddOutputTextMessage(" Secondary GOES: " + currentSecondaryGOESdata.Count + " X-ray flux values for " + currentDate.ToShortDateString() + " downloaded\n");
                        }
                    }
                    catch (Exception ex)
                    {
                        Program.MainForm.outputTextBoxDockablePanel.AddOutputTextWarningMessage(" Secondary GOES: Cannot fetch data. " + ex.ToString().Split('\n')[0] + "\n");
                        SecondaryGOES_OK = false;
                    }
                }


                data_string = String.Empty;

                UInt64[] listeCurrentTime;

                listeCurrentTime = new UInt64[currentPrimaryGOESdata.Count];
                currentPrimaryGOESdata.Keys.CopyTo(listeCurrentTime, 0);
                
                Array.Sort(listeCurrentTime);

                string PrimaryGOESvalue = String.Empty;
                string SecondaryGOESvalue = String.Empty;

                foreach (UInt64 currentTime in listeCurrentTime)
                {

                    if (currentTime > lastDatabaseUpdateTime.UnixTime)
                    {
                        // we check for invalid data that are represented by a negative value (-1e5) in the downloaded file
                        if ((!PrimaryGOES_OK) || ((double)currentPrimaryGOESdata[currentTime] < 0))
                        {
                            PrimaryGOESvalue = "U";
                        }
                        else
                        {
                            PrimaryGOESvalue = currentPrimaryGOESdata[currentTime].ToString();
                        }

                        if ( (SID_monitor.Properties.Settings.Default.UpdateSecondaryGOES == false )||
                             (!SecondaryGOES_OK) || (!currentSecondaryGOESdata.ContainsKey(currentTime)) ||
                             ((double)currentSecondaryGOESdata[currentTime] < 0))
                        {
                            SecondaryGOESvalue = "U";
                        }
                        else
                        {
                            SecondaryGOESvalue = currentSecondaryGOESdata[currentTime].ToString();
                        }

                        data_string += currentTime + ":" + PrimaryGOESvalue + ":" + SecondaryGOESvalue + " ";
                    }
                    if (data_string.Length >= 10000) // we make a partial update to limit the size of the data string to avoid exceeding max line size
                    {
                        RRDToolConnection.ExecuteRRDTool(update_string + template_string + data_string, RRDToolProcess.ShowOptions.ErrorsAndStdOut, Program.MainForm.outputTextBoxDockablePanel.Handle);
                        data_string = String.Empty;
                    }
                }

                currentPrimaryGOESdata.Clear();
                currentSecondaryGOESdata.Clear();

                if (!String.IsNullOrEmpty(data_string)) // we update remaining data
                {
                    RRDToolConnection.ExecuteRRDTool(update_string + template_string + data_string, RRDToolProcess.ShowOptions.ErrorsAndStdOut, Program.MainForm.outputTextBoxDockablePanel.Handle);
                }
            }

            // we get the new update time
            try
            {
                lastDatabaseUpdateTime = RRDTool.getLastDatabaseUpdateTime(SID_monitor.Properties.Settings.Default.RRDToolGOES, SID_monitor.Program.MainForm.outputTextBoxDockablePanel.Handle);
                if (SID_monitor.Properties.Settings.Default.Verbose)
                {
                    Program.MainForm.outputTextBoxDockablePanel.AddOutputTextMessage(" GOES database updated.\n");
                }
            }
            catch (Exception ex)
            {
                Program.MainForm.outputTextBoxDockablePanel.AddOutputTextErrorMessage(" Cannot read last GOES database update time \"" + SID_monitor.Properties.Settings.Default.RRDToolGOES +
                    "\"\n*** " + ex.ToString().Split('\n')[0] + "\n");
                return null;
            }

            return lastDatabaseUpdateTime;
        }


        static String readData(Uri url)
        {
            try
            {
                WebRequest GOESDataRequest = WebRequest.Create(url);
                WebResponse GOESDataResponse = GOESDataRequest.GetResponse();
                Stream GOESDataStream = GOESDataResponse.GetResponseStream();
                StreamReader GOESDataReader = new StreamReader(GOESDataStream);
                string GOESdata = GOESDataReader.ReadToEnd();
                GOESDataResponse.Close();
                return GOESdata;
            }
            catch
            {
                throw; // Program.MainForm.outputTextBoxDockablePanel.AddOutputTextErrorMessage(" Cannot retrieve GOES data from \"" + url.AbsoluteUri + "\"\n*** " + ex.ToString().Split('\n')[0] + "\n");
            }

        }

        static int extractData(String data, ref Hashtable fluxData)
        {
            //#         1-minute GOES-10 Solar X-ray Flux
            //# 
            //#                 Modified Seconds
            //# UTC Date  Time   Julian  of the
            //# YR MO DA  HHMM    Day     Day       Short       Long
            //#-------------------------------------------------------
            //2006 04 08  1317   53833  47820     1.10e-08    1.78e-07

            // search for events
            Regex FluxRegEx = new Regex("^(?<Year>\\d\\d\\d\\d)\\s+(?<Month>\\d\\d)\\s+(?<Day>\\d\\d)\\s+(?<Hour>\\d\\d)(?<Min>\\d\\d)\\s+\\d+\\s+\\d+\\s+[+-]?\\d+[.]\\d+[eE][+-]\\d+\\s+(?<Flux>[+-]?\\d+[.]\\d+[eE][+-]\\d+).*$",
                                          RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.Multiline);

            Match m;
            int nbMatches = 0; // events counter

            // fills in the events list for each event found in the report
            for (m = FluxRegEx.Match(data); m.Success; m = m.NextMatch())
            {
                nbMatches++;
                SIDMonitorDateTime FluxDate = new SIDMonitorDateTime(DateTime.ParseExact(m.Groups["Day"].Value + "/" + m.Groups["Month"].Value + "/" + m.Groups["Year"].Value + " " + m.Groups["Hour"].Value + ":" + m.Groups["Min"].Value, "dd/MM/yyyy HH:mm", DateTimeFormatInfo.InvariantInfo));
                Double FluxValue = Double.Parse(m.Groups["Flux"].Value, NumberFormatInfo.InvariantInfo);

                fluxData.Add(FluxDate.UnixTime, FluxValue);
            }
            return nbMatches;
        }



        /// <summary>
        ///  The RRDTool command when creating a new GOES database
        /// </summary>
        public static string CreateRRDToolGOESCreateCommand(string filename)
        {
            UInt64 step = 60;                                 // database nominal update = 1 min
            UInt64 data_heartbeat = 2 * step;                 // we accept to miss one value
            //UInt64 sun_heartbeat = (UInt64)(2 * (Program.MainForm.timerSunUpdate.Interval / 1000));    // we accept to miss one value

            UInt64 datapoints_per_day = 86400 / step;         // number of datapoints in a given day
            UInt64 archive_size = (UInt64)(datapoints_per_day * SID_monitor.Properties.Settings.Default.DatabaseSize);      // number on points to store in the archive

            String RRDCommand = String.Empty;

            RRDCommand = "create \"" + filename + "\" ";
            RRDCommand += "--start " + (SIDMonitorDateTime.UtcNow.UnixTime - archive_size * step).ToString() + " ";
            RRDCommand += "--step " + step.ToString() + " ";
            RRDCommand += "DS:goesp:GAUGE:" + data_heartbeat.ToString() + ":U:U ";
            RRDCommand += "DS:goess:GAUGE:" + data_heartbeat.ToString() + ":U:U ";

            // RRA
            RRDCommand += "RRA:AVERAGE:0.99:1:" + archive_size.ToString() + " "; // RRA #1

            return RRDCommand;
        }
    }

}
