//-------------------------------------------------------------------------------------
// SID Monitor - Utility for Sudden Ionospheric Disturbances Monitoring Stations
// Copyright (C) 2006 - 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 { GOES10, GOES11, GOES12 };

    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 GOES10Url;
            Uri GOES11Url; 
            Uri GOES12Url;
            Hashtable currentGOES10data = new Hashtable(24 * 60); //24 * 60 elements per day @ 1 element per minute
            Hashtable currentGOES11data = new Hashtable(24 * 60); //24 * 60 elements per day @ 1 element per minute
            Hashtable currentGOES12data = new Hashtable(24 * 60); //24 * 60 elements per day @ 1 element per minute
            bool GOES10_OK = true; // Has GOES-10 correctly been downloaded?
            bool GOES11_OK = true; // Has GOES-11 correctly been downloaded?
            bool GOES12_OK = true; // Has GOES-12 correctly been downloaded?

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

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

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




                data_string = String.Empty;

                // work on the intersection of primary and secondary, or primary and backup, or secondary and backup
                GOESsat primary = GOESsat.GOES12;
                GOESsat secondary = GOESsat.GOES10;

                if (GOES12_OK) // is GOES-12 as primary OK?
                {
                    primary = GOESsat.GOES12;

                    if (GOES10_OK)  // is GOES-10 as secondary OK?
                    {
                        secondary = GOESsat.GOES10;
                    }
                    else if (GOES11_OK) // use GOES-11 as secondary
                    {
                        Program.MainForm.outputTextBoxDockablePanel.AddOutputTextWarningMessage(" Note: Using GOES-11 as backup for GOES-10 for " + currentDate.ToShortDateString() + "\n");
                        secondary = GOESsat.GOES11;
                    }
                    else
                    {
                        Program.MainForm.outputTextBoxDockablePanel.AddOutputTextErrorMessage(" Only GOES-12 data available. Cancelling update for " + currentDate.ToShortDateString() + "\n");
                        continue;
                    }
                }
                else if (GOES11_OK) // use GOES-11 as primary
                {
                    Program.MainForm.outputTextBoxDockablePanel.AddOutputTextWarningMessage(" Note: Using GOES-11 as backup for GOES-12 for " + currentDate.ToShortDateString() + "\n");
                    primary = GOESsat.GOES11;

                    if (GOES10_OK)  // is GOES-10 as secondary OK?
                    {
                        secondary = GOESsat.GOES10;
                    }
                    else
                    {
                        Program.MainForm.outputTextBoxDockablePanel.AddOutputTextErrorMessage(" Only GOES-11 data available. Cancelling update for " + currentDate.ToShortDateString() + "\n");
                        continue;
                    }
                }
                else // neither GOES-12 or GOES-11 available
                {
                    if (GOES10_OK) 
                    {
                        Program.MainForm.outputTextBoxDockablePanel.AddOutputTextErrorMessage(" Only GOES-10 data available. Cancelling update for " + currentDate.ToShortDateString() + "\n");
                        continue;
                    }
                    else
                    {
                        Program.MainForm.outputTextBoxDockablePanel.AddOutputTextErrorMessage(" No GOES data available. Cancelling update for " + currentDate.ToShortDateString() + "\n");
                        continue;
                    } 
                }

                UInt64[] listeCurrentTime;

                switch (primary)
                {
                    case GOESsat.GOES10:
                        listeCurrentTime = new UInt64[currentGOES10data.Count];
                        currentGOES10data.Keys.CopyTo(listeCurrentTime, 0);
                        break;
                    case GOESsat.GOES11:
                        listeCurrentTime = new UInt64[currentGOES11data.Count];
                        currentGOES11data.Keys.CopyTo(listeCurrentTime, 0); 
                        break;
                    case GOESsat.GOES12:
                    default:
                        listeCurrentTime = new UInt64[currentGOES12data.Count];
                        currentGOES12data.Keys.CopyTo(listeCurrentTime, 0); 
                        break;
                }
                
                Array.Sort(listeCurrentTime);

                string GOES10value = String.Empty;
                string GOES11value = String.Empty;
                string GOES12value = String.Empty;

                foreach (UInt64 currentTime in listeCurrentTime)
                {

                    if ((currentTime > lastDatabaseUpdateTime.UnixTime) && 
                        (
                            ((secondary == GOESsat.GOES10) && currentGOES10data.ContainsKey(currentTime)) ||
                            ((secondary == GOESsat.GOES11) && currentGOES11data.ContainsKey(currentTime)) ||
                            ((secondary == GOESsat.GOES12) && currentGOES12data.ContainsKey(currentTime))
                        )
                       )
                    {
                        // we check for invalid data that are represented by a negative value (-1e5) in the downloaded file
                        if ((!GOES10_OK) || ((double)currentGOES10data[currentTime] < 0))
                        {
                            GOES10value = "U";
                        }
                        else
                        {
                            GOES10value = currentGOES10data[currentTime].ToString();
                        }

                        if ((!GOES11_OK) || ((double)currentGOES11data[currentTime] < 0))
                        {
                            GOES11value = "U";
                        }
                        else
                        {
                            GOES11value = currentGOES11data[currentTime].ToString();
                        }

                        if ((!GOES12_OK) || ((double)currentGOES12data[currentTime] < 0))
                        {
                            GOES12value = "U";
                        }
                        else
                        {
                            GOES12value = currentGOES12data[currentTime].ToString();
                        }

                        data_string += currentTime + ":" + GOES10value + ":" + GOES11value + ":" + GOES12value + " ";
                    }
                    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;
                    }
                }

                currentGOES10data.Clear();
                currentGOES11data.Clear();
                currentGOES12data.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);
                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:goes10:GAUGE:" + data_heartbeat.ToString() + ":U:U ";
            RRDCommand += "DS:goes11:GAUGE:" + data_heartbeat.ToString() + ":U:U ";
            RRDCommand += "DS:goes12:GAUGE:" + data_heartbeat.ToString() + ":U:U ";

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

            return RRDCommand;
        }
    }

}
