using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Threading;
using System.Diagnostics;
using System.IO;
using System.Reflection;

namespace distribution_explorer
{
  /// <summary>
  /// Main distribution explorer.
  /// </summary>
  public partial class DistexForm : Form
  {

    EventLog log = new EventLog();
    /// <summary>
    /// Main form
    /// </summary>
    public DistexForm()
    {
      if (!EventLog.SourceExists("EventLogDistex"))
      {
        EventLog.CreateEventSource("EventLogDistex", "Application");
      }
      log.Source = "EventLogDistex";
      log.WriteEntry("DistexForm");

      InitializeComponent();

      Application.DoEvents();
    }

    private void Form_Load(object sender, EventArgs e)
    { // Load distribution & parameters names, and default values.
      try
      {
        // Create and show splash screen:
        this.Hide();
        distexSplash frmSplash = new distexSplash();
        frmSplash.Show();
        frmSplash.Update();
        // Now load our data while the splash is showing:
        if (boost_math.any_distribution.size() <= 0)
        {
          MessageBox.Show("Problem loading any distributions, size = " + boost_math.any_distribution.size().ToString());
        }
        for (int i = 0; i < boost_math.any_distribution.size(); ++i)
        {
          distribution.Items.Add(boost_math.any_distribution.distribution_name(i));
        }
        distribution.SelectedIndex = 0; // 1st in array, but could be any other.
        // All parameters are made zero by default, but updated from chosen distribution.
        parameter1.Text = boost_math.any_distribution.first_param_default(0).ToString();
        parameter2.Text = boost_math.any_distribution.second_param_default(0).ToString();
        parameter3.Text = boost_math.any_distribution.third_param_default(0).ToString();
        //
        // Sleep and then close splash;
        Thread.Sleep(3000);
        frmSplash.Close();
        this.Visible = true;
      }
      catch
      { //
        log.WriteEntry("DistexForm_load exception!");
        MessageBox.Show("Problem loading distributions, size = " + boost_math.any_distribution.size().ToString());
      }
    }

    private void distribution_SelectedIndexChanged(object sender, EventArgs e)
    {
      int i = distribution.SelectedIndex; // distribution tab.
      parameter1Label.Text = boost_math.any_distribution.first_param_name(i);
      parameterLabel1.Text = boost_math.any_distribution.first_param_name(i); // properties tab.
      parameter2Label.Text = boost_math.any_distribution.second_param_name(i);
      parameter3Label.Text = boost_math.any_distribution.third_param_name(i);
      if (boost_math.any_distribution.first_param_name(i).Length.CompareTo(0) != 0)
      { // Actually all the distributions have at least one parameters,
        parameter1.Visible = true; // so should always be true.
        parameterLabel1.Visible = true;
      }
      else
      { // If distribution chosen has no parameter name(s) then hide.
        parameter1.Visible = false;
        parameterLabel1.Visible = false;
      }
      parameter1.Text = boost_math.any_distribution.first_param_default(i).ToString();
      // Update parameter default to match distribution.
      if (boost_math.any_distribution.second_param_name(i).Length.CompareTo(0) != 0)
      {
        parameter2.Visible = true;
        parameterLabel2.Visible = true;
        parameter2ValueLabel.Visible = true;
      }
      else
      { // hide
        parameter2.Visible = false;
        parameterLabel2.Visible = false;
        parameter2ValueLabel.Visible = false;

      }
      parameter2.Text = boost_math.any_distribution.second_param_default(i).ToString();
      if (boost_math.any_distribution.third_param_name(i).Length.CompareTo(0) != 0)
      {
        parameter3.Visible = true;
        parameterLabel3.Visible = true;
        parameter3ValueLabel.Visible = true;
      }
      else
      { // hide
        parameter3.Visible = false;
        parameterLabel3.Visible = false;
        parameter3ValueLabel.Visible = false;
      }
      parameter3.Text = boost_math.any_distribution.third_param_default(i).ToString();
      // Update tool tips to show total and supported ranges.
      PropertiesTabPage.ToolTipText = "Shows properties and ranges of chosen distribution.";
    }

    private boost_math.any_distribution dist;

    private void dataGridView1_CellEndEdit(object sender, DataGridViewCellEventArgs e)
    { // Display a grid of pdf, cdf... values from user's random variate x value.
      try
      {
        if (e.ColumnIndex == 0)
        { // Clicked on left-most random variate x column to enter a value.
          int i = e.RowIndex;
          string s = CDF_data.Rows[i].Cells[0].Value.ToString();
          double x = double.Parse(s); // Get value of users random variate x.
          double pdf = dist.pdf(x); // Compute pdf values from x
          double cdf = dist.cdf(x); // & cdf
          double ccdf = dist.ccdf(x); // & complements.
          CDF_data.Rows[i].Cells[1].Value = pdf; // and display values.
          CDF_data.Rows[i].Cells[2].Value = cdf;
          CDF_data.Rows[i].Cells[3].Value = ccdf;
        }
      }
      catch (SystemException se)
      {
          MessageBox.Show("Error in random variable value: " + se.Message, "Calculation Error");
      }
    }

    private void tabPage2_Enter(object sender, EventArgs e)
    { // Properties tab shows distribution's mean, mode, median...
      try
      { // Show chosen distribution name, and parameter names & values.
        int i = distribution.SelectedIndex;
        distributionValueLabel.Text = boost_math.any_distribution.distribution_name(i).ToString();
        parameterLabel1.Text = boost_math.any_distribution.first_param_name(i).ToString();
        parameter1ValueLabel.Text = double.Parse(parameter1.Text).ToString();
        parameterLabel2.Text = boost_math.any_distribution.second_param_name(i).ToString();
        parameter2ValueLabel.Text = double.Parse(parameter2.Text).ToString();
        parameterLabel3.Text = boost_math.any_distribution.third_param_name(i).ToString();
        parameter3ValueLabel.Text = double.Parse(parameter3.Text).ToString();

        // Show computed properties of distribution.
        try
        {
            mean.Text = dist.mean().ToString();
        }
        catch
        {
            mean.Text = "Undefined.";
        }
        try
        {
            mode.Text = dist.mode().ToString();
        }
        catch
        {
            mode.Text = "Undefined.";
        }
        try
        {
            median.Text = dist.median().ToString();
        }
        catch
        {
            median.Text = "Undefined.";
        }
        try
        {
            variance.Text = dist.variance().ToString();
        }
        catch
        {
            variance.Text = "Undefined.";
        }
        try
        {
            standard_deviation.Text = dist.standard_deviation().ToString();
        }
        catch
        {
            standard_deviation.Text = "Undefined.";
        }
        try
        {
            skewness.Text = dist.skewness().ToString();
        }
        catch
        {
            skewness.Text = "Undefined.";
        }
        try
        {
            kurtosis.Text = dist.kurtosis().ToString();
        }
        catch
        {
            kurtosis.Text = "Undefined.";
        }
        try
        {
            kurtosis_excess.Text = dist.kurtosis_excess().ToString();
        }
        catch
        {
            kurtosis_excess.Text = "Undefined.";
        }
        try
        {
            coefficient_of_variation.Text = dist.coefficient_of_variation().ToString();
        }
        catch
        {
            coefficient_of_variation.Text = "Undefined.";
        }

        rangeLowestLabel.Text = dist.lowest().ToString();
        rangeGreatestLabel.Text = dist.uppermost().ToString();
        supportLowerLabel.Text = dist.lower().ToString();
        supportUpperLabel.Text = dist.upper().ToString();
        cdfTabPage.ToolTipText = "Random variate can range from " + rangeLowestLabel.Text
          + " to " + rangeGreatestLabel.Text
          + ",\nbut is said to be supported from " + supportLowerLabel.Text
          + " to " + supportUpperLabel.Text
          + "\nWithin this supported range the PDF and CDF have values between 0 and 1,\nbut below " + supportLowerLabel.Text + " both are zero, and above "
          +  supportUpperLabel.Text + " both are unity";
      }
      catch (SystemException se)
      {
        MessageBox.Show(se.Message, "Calculation Error!");
      }
    }

    private void properties_tab_Deselecting(object sender, TabControlCancelEventArgs e)
    {
      try
      {
        if (e.TabPageIndex == 0)
        {   // Update selected distribution object:
          double x = double.Parse(parameter1.Text);
          double y = double.Parse(parameter2.Text);
          double z = double.Parse(parameter3.Text);
          int i = distribution.SelectedIndex;
          dist = new boost_math.any_distribution(i, x, y, z);
          // Clear existing CDF data (has to be a better way?):
          while (CDF_data.Rows.Count > 1)
          {
            CDF_data.Rows.Remove(CDF_data.Rows[0]);
          }
          // Clear existing quantile data (has to be a better way?):
          while (QuantileData.Rows.Count > 1)
          {
            QuantileData.Rows.Remove(QuantileData.Rows[0]);
          }
        }
      }
      catch (SystemException se)
      {
          MessageBox.Show(se.Message +
              " Please check the distribution's parameters and try again.", "Distribution Error");
          this.propertiesTab.SelectedIndex = 0;
          e.Cancel = true;
      }
    }

    private void QuantileData_CellEndEdit(object sender, DataGridViewCellEventArgs e)
    { // aka Risk & critical values tab.
      try
      {
        if (e.ColumnIndex == 0)
        {
          int i = e.RowIndex;
          string s = QuantileData.Rows[i].Cells[0].Value.ToString();
          double x = double.Parse(s);
          // Remember x is alpha: 1 - the probability:
          double lcv = dist.quantile(x);
          double ucv = dist.quantile_c(x);
          QuantileData.Rows[i].Cells[1].Value = lcv;
          QuantileData.Rows[i].Cells[2].Value = ucv;
        }
      }
      catch (SystemException se)
      {
        // TODO add some proper handling here!
        MessageBox.Show("Error in probability value: " + se.Message, "Calculation Error");
      }
    }

    private void QuantileTab_Enter(object sender, EventArgs e)
    { // Evaluate critical values (quantiles) for pre-chosen risk level.
      // and then, optionally, for other user-provided risk levels.
      try
      {
        if (QuantileData.Rows.Count == 1)
        {
          // Add some defaults:
          QuantileData.Rows.Add(5); // 5 Risk levels.
          QuantileData.Rows[0].Cells[0].Value = "0.001"; // Risk values as text,
          QuantileData.Rows[0].Cells[1].Value = dist.quantile(0.001); // & as double.
          QuantileData.Rows[0].Cells[2].Value = dist.quantile_c(0.001);
          QuantileData.Rows[1].Cells[0].Value = "0.01";
          QuantileData.Rows[1].Cells[1].Value = dist.quantile(0.01); // 99% confidence.
          QuantileData.Rows[1].Cells[2].Value = dist.quantile_c(0.01);
          QuantileData.Rows[2].Cells[0].Value = "0.05";
          QuantileData.Rows[2].Cells[1].Value = dist.quantile(0.05);
          QuantileData.Rows[2].Cells[2].Value = dist.quantile_c(0.05);
          QuantileData.Rows[3].Cells[0].Value = "0.1";
          QuantileData.Rows[3].Cells[1].Value = dist.quantile(0.1);
          QuantileData.Rows[3].Cells[2].Value = dist.quantile_c(0.1);
          QuantileData.Rows[4].Cells[0].Value = "0.33333333333333333";
          QuantileData.Rows[4].Cells[1].Value = dist.quantile(0.33333333333333333);
          QuantileData.Rows[4].Cells[2].Value = dist.quantile_c(0.33333333333333333);
        }
      }
      catch (SystemException se)
      {
        // TODO add some proper handling here!
        MessageBox.Show(se.Message, "Calculation Error");
      }
    }


    private void properties_tab_SelectedIndexChanged(object sender, EventArgs e)
    {
    }

    private void tabPage1_Click(object sender, EventArgs e)
    {
    }

    private void CDF_data_CellContentClick(object sender, DataGridViewCellEventArgs e)
    {
    }

    distexAboutBox DistexAboutBox = new distexAboutBox();

    private void aboutToolStripMenuItem_Click(object sender, EventArgs e)
    {
      DistexAboutBox.ShowDialog();
    }

    private void DistexForm_Activated(object sender, EventArgs e)
    {
    }

    /// get AssemblyDescription
    public string AssemblyDescription
    {
      get
      {
        // Get all Description attributes on this assembly
        object[] attributes = Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(AssemblyDescriptionAttribute), false);
        // If there aren't any Description attributes, return an empty string
        if (attributes.Length == 0)
          return "";
        // If there is a Description attribute, return its value
        return ((AssemblyDescriptionAttribute)attributes[0]).Description;
      }
    }

    private void saveFileDialog1_FileOk(object sender, CancelEventArgs e)
    {
      using (StreamWriter sw = new StreamWriter(this.saveFileDialog.FileName))
      { // Write distribution info and properties to file.
        sw.WriteLine( AssemblyDescription);
        sw.WriteLine("Version " + Assembly.GetExecutingAssembly().GetName().Version.ToString());
        // Get parameter names (null "" if no parameter).
        int i = distribution.SelectedIndex;
        distributionValueLabel.Text = boost_math.any_distribution.distribution_name(i).ToString();
        sw.WriteLine(distributionValueLabel.Text + " distribution");
        parameterLabel1.Text = boost_math.any_distribution.first_param_name(i).ToString();
        parameterLabel2.Text = boost_math.any_distribution.second_param_name(i).ToString();
        parameterLabel3.Text = boost_math.any_distribution.third_param_name(i).ToString();
        string separator = "\t "; // , or tab or space?
        // Write parameter name & value.
        sw.WriteLine(parameterLabel1.Text + separator + this.parameter1.Text);
        if (boost_math.any_distribution.second_param_name(i).Length.CompareTo(0) != 0)
        { // Is a 2nd parameter.
          sw.WriteLine(parameterLabel2.Text + separator +  this.parameter2.Text);
        }
        if (boost_math.any_distribution.third_param_name(i).Length.CompareTo(0) != 0)
        { // Is a 3rd parameter.
          sw.WriteLine(parameterLabel3.Text + separator + this.parameter3.Text);
        }
        sw.WriteLine();
        sw.WriteLine("Properties");
        // Show computed properties of distribution.
        double x = double.Parse(parameter1.Text);
        double y = double.Parse(parameter2.Text);
        double z = double.Parse(parameter3.Text);
        dist = new boost_math.any_distribution(i, x, y, z);
        // Note global dist might not have been calculated yet if no of the tabs clicked.
        try
        {
            mean.Text = dist.mean().ToString();
        }
        catch
        {
            mean.Text = "Undefined";
        }
        sw.WriteLine("Mean" + separator + mean.Text);
        try
        {
            mode.Text = dist.mode().ToString();
        }
        catch
        {
            mode.Text = "Undefined";
        }
        sw.WriteLine("mode" + separator + mode.Text);
        try
        {
            median.Text = dist.median().ToString();
        }
        catch
        {
            median.Text = "Undefined";
        }
        sw.WriteLine("Median" + separator + median.Text);
        try
        {
            variance.Text = dist.variance().ToString();
        }
        catch
        {
            variance.Text = "Undefined";
        }
        sw.WriteLine("Variance" + separator + variance.Text);
        try
        {
            standard_deviation.Text = dist.standard_deviation().ToString();
        }
        catch
        {
            standard_deviation.Text = "Undefined";
        }
        sw.WriteLine("Standard Deviation" + separator + standard_deviation.Text);
        try
        {
            skewness.Text = dist.skewness().ToString();
        }
        catch
        {
            skewness.Text = "Undefined";
        }
        sw.WriteLine("Skewness" + separator + skewness.Text);
        try
        {
            coefficient_of_variation.Text = dist.coefficient_of_variation().ToString();
        }
        catch
        {
            coefficient_of_variation.Text = "Undefined";
        }
        sw.WriteLine("Cofficient of variation" + separator + coefficient_of_variation.Text);
        try
        {
            kurtosis.Text = dist.kurtosis().ToString();
        }
        catch
        {
            kurtosis.Text = "Undefined";
        }
        sw.WriteLine("Kurtosis" + separator + kurtosis.Text);
        try
        {
            kurtosis_excess.Text = dist.kurtosis_excess().ToString();
        }
        catch
        {
            kurtosis_excess.Text = "Undefined";
        }
        sw.WriteLine("Kurtosis excess" + separator + kurtosis_excess.Text);
        sw.WriteLine();

        sw.WriteLine("Range from" + separator + dist.lowest().ToString() + separator +
        "to" + separator + dist.uppermost().ToString());
        sw.WriteLine("Support from " + separator + dist.lower().ToString() +separator+
        "to " + separator + dist.upper().ToString());
        sw.WriteLine();

        //
        sw.WriteLine("Quantiles");
        if (QuantileData.Rows.Count == 1)
        { // Add some defaults:
          QuantileData.Rows.Add(5); // 5 Risk levels.
          QuantileData.Rows[0].Cells[0].Value = "0.001"; // Risk values as text,
          QuantileData.Rows[0].Cells[1].Value = dist.quantile(0.001); // & as double.
          QuantileData.Rows[0].Cells[2].Value = dist.quantile_c(0.001);
          QuantileData.Rows[1].Cells[0].Value = "0.01";
          QuantileData.Rows[1].Cells[1].Value = dist.quantile(0.01); // 99% confidence.
          QuantileData.Rows[1].Cells[2].Value = dist.quantile_c(0.01);
          QuantileData.Rows[2].Cells[0].Value = "0.05";
          QuantileData.Rows[2].Cells[1].Value = dist.quantile(0.05);
          QuantileData.Rows[2].Cells[2].Value = dist.quantile_c(0.05);
          QuantileData.Rows[3].Cells[0].Value = "0.1";
          QuantileData.Rows[3].Cells[1].Value = dist.quantile(0.1);
          QuantileData.Rows[3].Cells[2].Value = dist.quantile_c(0.1);
          QuantileData.Rows[4].Cells[0].Value = "0.33333333333333333";
          QuantileData.Rows[4].Cells[1].Value = dist.quantile(0.33333333333333333);
          QuantileData.Rows[4].Cells[2].Value = dist.quantile_c(0.33333333333333333);
        }
        // else have already been calculated by entering the quantile tab.
        for (int r = 0; r < QuantileData.Rows.Count-1; r++)
        { // Show all the rows of quantiles, including any optional user values.
          sw.WriteLine(QuantileData.Rows[r].Cells[0].Value.ToString() + separator +
            QuantileData.Rows[r].Cells[1].Value.ToString() + separator +
            QuantileData.Rows[r].Cells[2].Value.ToString());
        }
        sw.WriteLine();
        sw.WriteLine("PDF, CDF & complement(s)");
        for (int r = 0; r < CDF_data.Rows.Count-1; r++)
        { // Show all the rows of pdf, cdf, including any optional user values.
          sw.WriteLine(CDF_data.Rows[r].Cells[0].Value.ToString() + separator + // x value.
            CDF_data.Rows[r].Cells[1].Value.ToString() + separator + // pdf
            CDF_data.Rows[r].Cells[2].Value.ToString() + separator + // cdf
            CDF_data.Rows[r].Cells[3].Value.ToString());// cdf complement.
        }
        sw.WriteLine();
    }

    } // saveFileDialog1_FileOk

    private void saveToolStripMenuItem_Click(object sender, EventArgs e)
    {
      this.saveFileDialog.ShowDialog();
    }

    private void saveAsToolStripMenuItem_Click(object sender, EventArgs e)
    { // Same as Save.
      this.saveFileDialog.ShowDialog();
    }

    private void contentsToolStripMenuItem_Click(object sender, EventArgs e)
    { // In lieu of proper help.
      string helpText = "\n" + AssemblyDescription +
      "\nVersion " + Assembly.GetExecutingAssembly().GetName().Version.ToString() +
      "\nA Windows utility to show the properties of distributions " +
      "\nand permit calculation of probability density (or mass) function (PDF) " +
      "\nand cumulative distribution function (CDF) and complements from values provided." +
      "\nQuantiles are also calculated for typical risk (alpha) probabilities" +
      "\nand for probabilities provided by the user." +
      "\n" +
      "\nResults can be saved to text files using Save or SaveAs." +
      "\nAll the values on the four tabs are output to the file chosen," +
      "\nand are tab separated to assist input to other programs," +
      "\nfor example, spreadsheets or text editors." +
      "\nNote: when importing to Excel, by default only 10 decimal digits are shown by Excel:" +
      "\nit is necessary to format all cells to display the full 15 decimal digits," +
      "\nalthough not all computed values will be as accurate as this." +
      "\n\nValues shown as NaN cannot be calculated for the value given," +
      "\nmost commonly because the value is outside the range for the distribution." +
      "\n" +
      "\nFor more information, including downloads, see " +
      "\nhttp://sourceforge.net/projects/distexplorer/" +
      "\n(Note that .NET framework 4.0 and VC Redistribution X86 are requirements for this program.)" +
      "\n\nCopyright John Maddock & Paul A. Bristow 2007, 2009, 2010, 2012";

      MessageBox.Show("Statistical Distribution Explorer\n" + helpText);
    }

    private void newToolStripMenuItem_Click(object sender, EventArgs e)
    {
      MessageBox.Show("New is not yet implemented.");
    }

    private void openToolStripMenuItem_Click(object sender, EventArgs e)
    {
      MessageBox.Show("Open is not yet implemented.");
    }

    private void printToolStripMenuItem_Click(object sender, EventArgs e)
    {
      MessageBox.Show("Print is not yet implemented." +
        "\nSave all values to a text file and print that file.");
    }

    private void printPreviewToolStripMenuItem_Click(object sender, EventArgs e)
    {
      MessageBox.Show("Print Preview is not yet implemented." +
        "\nSave all values to a text file and print that file.");
    }


    private void exitToolStripMenuItem_Click(object sender, EventArgs e)
    { // exit DistexForm
        this.Close();
    }
  } // class DistexForm
} // namespace distribution_explorer