//-------------------------------------------------------------------------------------
// 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.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.IO;
using System.Threading;
using System.Globalization;
using System.Text.RegularExpressions;

namespace SID_monitor
{
    public partial class HWReapply : Form
    {
        int highestPercentageReached = 0;

        public struct HWReapplyArguments
        {
            public string filename;
            public IntPtr outputTextBoxHandle;
        }

        public HWReapply()
        {
            InitializeComponent();
        }

        private void HWReapply_Load(object sender, EventArgs e)
        {
            this.progressBar.Visible = false;
            this.textBoxDatabaseFilename.Text = Path.GetFullPath(SID_monitor.Properties.Settings.Default.RRDToolDatabase);
        }


        private void textBoxDatabaseFilename_TextChanged(object sender, EventArgs e)
        {
            if (!File.Exists(this.textBoxDatabaseFilename.Text))
            {
                errorProvider.SetError(this.textBoxDatabaseFilename, "This file does not exists");
                this.buttonOK.Enabled = false;
                this.buttonSelectDatabase.Enabled = false;
                this.textBoxDatabaseFilename.Enabled = false;
                this.panelForecast.Enabled = false;

            }
            else
            {
                errorProvider.SetError(this.textBoxDatabaseFilename, "");
                this.buttonOK.Enabled = true;
                this.buttonSelectDatabase.Enabled = true;
                this.textBoxDatabaseFilename.Enabled = true;
                this.panelForecast.Enabled = true;
            }
        }

        private void buttonOpenDatabase_Click(object sender, EventArgs e)
        {
            this.openFileDialog.DefaultExt = "rrd";
            this.openFileDialog.Filter = "RRDTool Database|*.rrd|All files|*.*";
            this.openFileDialog.Title = "Select Database to process";
            if (File.Exists(this.textBoxDatabaseFilename.Text))
            {
                this.openFileDialog.FileName = this.textBoxDatabaseFilename.Text;
            }

            if (this.openFileDialog.ShowDialog() == DialogResult.OK)
            {
                this.textBoxDatabaseFilename.Text = this.openFileDialog.FileName;
            }

        }


        /// <summary>
        /// Forecast Algorithm Parameters are reapplied to the whole database
        /// </summary>

        private int HWReapplyParameters(BackgroundWorker worker, DoWorkEventArgs e)
        {
            Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;

            // get local variables
            HWReapplyArguments arguments = (HWReapplyArguments)e.Argument;
            OutputTextBox outputTextBox = (OutputTextBox)Control.FromHandle(arguments.outputTextBoxHandle);

            if (worker.CancellationPending)
            {
                e.Cancel = true;
                return 0;
            }
            // we get the start time of the original database
            outputTextBox.AddOutputTextMessage("Starting Process...\nUsing database: \"" + arguments.filename + "\"\n");
            outputTextBox.AddOutputTextMessage("Determining start time of the existing database...");
            SIDMonitorDateTime firstDatabaseUpdateTime;
            try
            {
                firstDatabaseUpdateTime = RRDTool.getFirstDatabaseUpdateTime(arguments.filename, arguments.outputTextBoxHandle);
            }
            catch
            {
                outputTextBox.AddOutputTextErrorMessage(" Cannot read initial database time \"" + arguments.filename + "\"\n");
                return 0;
            }
            outputTextBox.AddOutputTextMessageWithoutDate(" done.\n");
            if (worker.CancellationPending)
            {
                e.Cancel = true;
                return 0;
            }

            // we create a new database in a temp file
            string NewDatabaseName = Path.GetTempFileName();
            string RRDCreateNewCommand = Program.MainForm.CreateRRDToolCreateCommand(NewDatabaseName, firstDatabaseUpdateTime.UnixTime, this.panelForecast.ForecastAlpha, this.panelForecast.ForecastBeta, this.panelForecast.ForecastGammaSeasonal, this.panelForecast.ForecastGammaDevSeasonal, this.panelForecast.ForecastThreshold, this.panelForecast.ForecastWindowLength);
            outputTextBox.AddOutputTextMessage("Creating new database...");
            //Program.MainForm.AddOutputTextRRDToolMessage("rrdtool " + RRDCreateNewCommand + "\n");
            RRDToolConnection.ExecuteRRDTool(RRDCreateNewCommand, RRDToolProcess.ShowOptions.ErrorsAndStdOut, arguments.outputTextBoxHandle);
            outputTextBox.AddOutputTextMessageWithoutDate(" done.\n");
            if (worker.CancellationPending)
            {
                e.Cancel = true;
                return 0;
            }


            // we tune the database with the new parameters (useful only for deltapos and deltaneg values)
            string RRDTuneCommand = SID_monitor.Program.MainForm.CreateRRDToolTuneCommand(NewDatabaseName, this.panelForecast.ForecastAlpha, this.panelForecast.ForecastBeta, this.panelForecast.ForecastGammaSeasonal, this.panelForecast.ForecastGammaDevSeasonal, this.panelForecast.ForecastThreshold, this.panelForecast.ForecastWindowLength, this.panelForecast.DeltaPos, this.panelForecast.DeltaNeg);
            outputTextBox.AddOutputTextMessage("Tuning new database...");
            //Program.MainForm.AddOutputTextRRDToolMessage("rrdtool " + RRDTuneCommand + "\n");
            RRDToolConnection.ExecuteRRDTool(RRDTuneCommand, RRDToolProcess.ShowOptions.ErrorsAndStdOut, arguments.outputTextBoxHandle);
            outputTextBox.AddOutputTextMessageWithoutDate(" done.\n");
            if (worker.CancellationPending)
            {
                e.Cancel = true;
                return 0;
            }


            // we copy the current dataset values -------------------------------------------------------
            string RRDFetchCommand = Program.MainForm.CreateRRDToolFetchCommand(arguments.filename, firstDatabaseUpdateTime.UnixTime);
            outputTextBox.AddOutputTextMessage("Dumping existing database...");
            //Program.MainForm.AddOutputTextRRDToolMessage("rrdtool " + RRDFetchCommand + "\n");
            String fetchData = RRDToolConnection.ExecuteRRDTool(RRDFetchCommand, RRDToolProcess.ShowOptions.ErrorsOnly, arguments.outputTextBoxHandle);
            outputTextBox.AddOutputTextMessageWithoutDate(" done.\n");
            if (worker.CancellationPending)
            {
                e.Cancel = true;
                return 0;
            }

            // search for the ds names in the data fetched ----------------------------------------------
            String[] arrayDS;
            {
                Regex DSRegEx = new Regex("\\b(?<DS>\\w+)\\b", RegexOptions.Compiled);

                MatchCollection DSMatches = DSRegEx.Matches(fetchData.Substring(0, fetchData.IndexOf('\n'))); // we search on the first line

                if (DSMatches.Count == 0)
                {
                    outputTextBox.AddOutputTextErrorMessage(" No datasets found in database \"" + arguments.filename + "\"\n");
                    return 0;
                }
                else
                {
                    arrayDS = new String[DSMatches.Count];
                    string listDS = "- Database contains " + DSMatches.Count.ToString() + " datasets:";
                    for (int i = 0; i < arrayDS.Length; i++)
                    {
                        arrayDS[i] = DSMatches[i].Groups["DS"].Value;
                        listDS += " " + arrayDS[i] + ",";
                    }
                    listDS = listDS.TrimEnd(',') + ".\n";
                    outputTextBox.AddOutputTextMessage(listDS);
                }
            } // DSMatches gets out of scope and is freed
            if (worker.CancellationPending)
            {
                e.Cancel = true;
                return 0;
            }

            // computation of the theoretical number of points in the database
            UInt64 step = (UInt64)(SID_monitor.Properties.Settings.Default.AcquisitionUpdate / 1000);  // database nominal update
            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
            outputTextBox.AddOutputTextMessage("- Database contains " + archive_size.ToString() + " data.\n");

            // search for the data values in the data fetched -------------------------------------------
            String DataRegExStr = "^(?<time>\\d+)[:]"; // construction of the regex
            for (int i = 0; i < arrayDS.Length; i++)
            {
                DataRegExStr += "\\s+(?<" + arrayDS[i] + ">[^\\s]*)";
            }
            DataRegExStr += "\\s*$";

            int arrayDataSize = 0;  // actual size of arrayData
            int usefulData = 0; // number of valid data
            {
                // Setting new parameters -------------------------------------------------------------------
                outputTextBox.AddOutputTextMessage("Updating new database with existing data. Please wait...\n");

                string update_string = "update \"" + NewDatabaseName + "\" ";

                string template_string = "--template ";
                for (int i = 0; i < arrayDS.Length; i++)
                {
                    template_string += arrayDS[i] + ":";
                }
                template_string = template_string.TrimEnd(':');
                template_string += " ";

                // -------------------------------------------------------------------------------------------
                const int arrayDataMaxSize = 50; // update the database when number of elements gets high to avoid exceeding max command line limit
                Array arrayData = Array.CreateInstance(typeof(long), new int[2] { arrayDataMaxSize, arrayDS.Length + 1 }); // at most arrayDataMaxSize elements, each with arrayDS.Length+1 values. 


                Regex DataRegEx = new Regex(DataRegExStr, RegexOptions.Compiled | RegexOptions.Multiline);

                Match m;
                int nbMatches = 0; // number of data extracted from the database counter (one per timeslot)

                UInt64 data_time; // current time
                int data_value;  // current value

                m = DataRegEx.Match(fetchData);

                while (m.Success)
                {
                    if (worker.CancellationPending)
                    {
                        e.Cancel = true;
                        return 0;
                    }

                    nbMatches++;

                    if (UInt64.TryParse(m.Groups["time"].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out data_time))
                    {
                        arrayData.SetValue((long)data_time, arrayDataSize, 0);
                        bool data_values_valid = false;
                        for (int j = 0; j < arrayDS.Length; j++) // create the item data element
                        {
                            if (Int32.TryParse(m.Groups[arrayDS[j]].Value, NumberStyles.Float, CultureInfo.InvariantCulture, out data_value))
                            {
                                arrayData.SetValue(data_value, arrayDataSize, j + 1);
                                data_values_valid = true;
                            }
                            else
                            {
                                arrayData.SetValue(-1, arrayDataSize, j + 1);
                            }
                        }
                        if (data_values_valid)
                        {
                            arrayDataSize++;
                            usefulData++;
                        }
                    }
                    m = m.NextMatch();

                    if ((!m.Success) || (arrayDataSize >= arrayDataMaxSize))
                    {
                        String data_string = String.Empty;

                        for (int i = 0; i < arrayDataSize; i++)
                        {
                            data_string += arrayData.GetValue(i, 0).ToString();
                            for (int j = 0; j < arrayDS.Length; j++)
                            {
                                if ((long)arrayData.GetValue(i, j + 1) > -1)
                                {
                                    data_string += ":" + arrayData.GetValue(i, j + 1).ToString();
                                }
                                else
                                {
                                    data_string += ":U";
                                }
                            }
                            data_string += " ";
                            RRDToolConnection.ExecuteRRDTool(update_string + template_string + data_string, RRDToolProcess.ShowOptions.ErrorsOnly, outputTextBox.Handle);

                            arrayDataSize = 0;
                        }
                    }
                    // Report progress as a percentage of the total task.
                    int percentComplete =
                        (int)((float)usefulData / (float)archive_size * 100);
                    if (percentComplete > highestPercentageReached)
                    {
                        highestPercentageReached = percentComplete;
                        worker.ReportProgress(percentComplete);
                    }

                } // while

                fetchData = String.Empty; // fetch data no longer needed, free memory

                if (nbMatches == 0)
                {
                    outputTextBox.AddOutputTextErrorMessage(" No data found in database \"" + arguments.filename + "\"\n");
                    return 0;
                }
                else
                {
                    outputTextBox.AddOutputTextMessage("- Database contains " + nbMatches.ToString() + " data (" + usefulData.ToString() + " useful).\n");
                }
            } // Items that get out of scope are freed
            if (worker.CancellationPending)
            {
                e.Cancel = true;
                return 0;
            }


            // Create backup of the initial database
            outputTextBox.AddOutputTextMessageWithoutDate("\n");
            String initialDatabaseBackup = arguments.filename + ".old.rrd";
            outputTextBox.AddOutputTextMessage("Saving existing database to \"" + initialDatabaseBackup + "\" ");
            File.Copy(arguments.filename, initialDatabaseBackup, true);
            outputTextBox.AddOutputTextMessageWithoutDate("done.\n");

            // Copy new database to current database location
            outputTextBox.AddOutputTextMessage("Replacing existing database by new one...");
            if (File.Exists(arguments.filename) && File.Exists(NewDatabaseName))
            {
                File.Delete(arguments.filename);
                File.Move(NewDatabaseName, arguments.filename);
                outputTextBox.AddOutputTextMessageWithoutDate("done.\n");
                outputTextBox.AddOutputTextMessage("New detection algorithm parameters applied.\n");
            }
            else
            {
                outputTextBox.AddOutputTextErrorMessage("Cannot found database file.\n");
            }

            return usefulData;

        }

        private void backgroundWorkerHWReapply_DoWork(object sender, DoWorkEventArgs e)
        {
            // Get the BackgroundWorker that raised this event.
            BackgroundWorker worker = sender as BackgroundWorker;

            e.Result = HWReapplyParameters(worker, e);
        }

        private void backgroundWorkerHWReapply_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            // First, handle the case where an exception was thrown.
            if (e.Error != null)
            {
                this.outputTextBoxLogMessages.AddOutputTextErrorMessage(e.Error.Message);
            }
            else if (e.Cancelled)
            {
                this.outputTextBoxLogMessages.AddOutputTextMessage("Cancelled. No change have been done to the database.\n");
            }
            else
            {
                this.outputTextBoxLogMessages.AddOutputTextMessage(e.Result.ToString() + " data updated\n");
            }

            // Enable the buttons.
            this.buttonOK.Enabled = true;
            this.buttonSelectDatabase.Enabled = true;
            this.textBoxDatabaseFilename.Enabled = true;
            this.panelForecast.Enabled = true;

            // Hides the progress bar
            this.progressBar.Visible = false;

            // restart the timers
            //this.timerADCUpdate.Enabled = true;
            //this.timerDatabaseUpdate.Enabled = true;

        }


        private void backgroundWorkerHWReapply_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            this.progressBar.Value = e.ProgressPercentage;
        }

        private void buttonOK_Click(object sender, EventArgs e)
        {
            // Disable buttons until the asynchronous operation is done.
            this.buttonOK.Enabled = false;
            this.buttonSelectDatabase.Enabled = false;
            this.textBoxDatabaseFilename.Enabled = false;
            this.panelForecast.Enabled = false;

            // Reset the variable for percentage tracking.
            highestPercentageReached = 0;
            this.progressBar.Value = 0;

            // show the progress bar
            this.progressBar.Visible = true;

            // stop the timers
            //this.timerADCUpdate.Enabled = false;
            //this.timerDatabaseUpdate.Enabled = false;

            // Start the asynchronous operation.
            HWReapplyArguments args = new HWReapplyArguments();
            args.filename = this.textBoxDatabaseFilename.Text;
            args.outputTextBoxHandle = this.outputTextBoxLogMessages.Handle;
            backgroundWorkerHWReapply.RunWorkerAsync(args);

        }

        private void buttonCancel_Click(object sender, EventArgs e)
        {
            if (backgroundWorkerHWReapply.IsBusy)
            {
                // Cancel the asynchronous operation.
                backgroundWorkerHWReapply.CancelAsync();
            }
            else
            {
                this.Close();
            }
        }







    }
}