using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using System.Threading;
using System.Windows.Forms;

namespace ParticulateSensorRecords
{
    public partial class Form1 : Form, IUserInterface
    {
        public IFileReader IOhandler { get; set; }
        private ConfigData configData;
        private LocationList locationList;
        private string sDivider = "==================================================================" + Environment.NewLine;

        public Form1(IFileReader IOhandler)
        {
            InitializeComponent();
            this.IOhandler = IOhandler;
        }

        private void Form1_Load(object sender, EventArgs e)
        {

        }

        private void btnRead_Click(object sender, EventArgs e)
        {
            // Clear any items in listbox and textbox
            lsbLocations.Items.Clear();
            lsbDate.Items.Clear();
            txtResult.Clear();

            SetupConfigData();

            // Run producer consumer to load location data
            RunProducerConsumer();

            // Display locations in ListBox
            DisplayLocation();

        }

        public void SetupConfigData()
        {
            // Make sure configData is a new empty object
            configData = new ConfigData();

            // Generate configuration data (filename required for each airdata)
            configData.configRecords.Add(new ConfigRecord("AirData-01.xml"));
            configData.configRecords.Add(new ConfigRecord("AirData-02.xml"));
            configData.configRecords.Add(new ConfigRecord("AirData-03.xml"));
            configData.configRecords.Add(new ConfigRecord("AirData-04.xml"));
            configData.configRecords.Add(new ConfigRecord("AirData-05.xml"));
            configData.configRecords.Add(new ConfigRecord("AirData-06.xml"));
            configData.configRecords.Add(new ConfigRecord("AirData-07.xml"));
        }

        public void RunProducerConsumer()
        {
            //Create location list to hold individual locationlist objects read from datasets
            locationList = new LocationList();

            // Create progress manager with number of files to process
            ProgressManager progManager = new ProgressManager(configData.configRecords.Count);

            // Output message to indicate that the program has started
            StringBuilder sb = new StringBuilder();

            sb.Append(sDivider);
            sb.Append("Creating and starting all producers and consumers" + Environment.NewLine);
            sb.Append(sDivider);

            txtResult.Text = sb.ToString();
            txtResult.Refresh();


            // Create a PCQueue instance, give it a capacity of 4
            var pcQueue = new PCQueue(4);

            // Create 2 Producer instances and 2 Consumer instances, these will begin executing on
            // their respective threads as soon as they are instantiated
            Producer[] producers = { new Producer("P1", pcQueue, configData, IOhandler),
                                     new Producer("P2", pcQueue, configData, IOhandler) };

            Consumer[] consumers = { new Consumer("C1", pcQueue, locationList, progManager),
                                     new Consumer("C2", pcQueue, locationList, progManager) };

            // Keep producing and consuming until all work items are completed
            while (progManager.ItemsRemaining > 0) ;

            // Output message to indicate that the program is shutting down
            sb.Clear();
            sb.Append(sDivider);
            sb.Append("Shutting down all producers and consumers" + Environment.NewLine);
            sb.Append(sDivider);

            txtResult.Text += sb.ToString();
            txtResult.Refresh();

            // Deactivate the PCQueue so it does not prevent waiting producer and/or consumer threads
            // from completing
            pcQueue.Active = false;

            // Iterate through producers and signal them to finish
            foreach (var p in producers)
            {
                p.Finished = true;
            }

            // Iterate through consumers and signal them to finish
            foreach (var c in consumers)
            {
                c.Finished = true;
            }

            // We need to ensure that no thread waiting on Monitor.Wait() is stranded with
            // no Monitor.Pulse() now possible since all producer and consumer threads have
            // been signalled to stop, in the worse case all such threads could be stranded
            // so pulse that many times to ensure enough pulses are made available (or the
            // program can halt erroneously), wasted pulse are simply ignored 
            for (int i = 0; i < (Producer.RunningThreads + Consumer.RunningThreads); i++)
            {
                lock (pcQueue)
                {
                    // Pulse the PCQueue to signal any waiting threads
                    Monitor.Pulse(pcQueue);

                    // Give a short break to the main thread so the pulses have time to be
                    // detected by any potentially waiting producer and/or consumer threads
                    Thread.Sleep(100);
                }
            }

            // Once all producer and consumer threads have finally finished we can gracefully
            // shutdown the main thread, this is achieved by spinning on a while() loop until
            // there are no running threads, in this case we do not mind the main thread
            // spinning as we are about to shutdown the program
            while ((Producer.RunningThreads > 0) || (Consumer.RunningThreads > 0)) ; // Wait by spinning

            sb.Clear();
            sb.Append(sDivider);
            sb.Append("All producers and consumers shut down" + Environment.NewLine);
            sb.Append(sDivider);

            txtResult.Text += sb.ToString();
            txtResult.Refresh();
        }

        public void DisplayLocation()
        {
            // Having finished generating data we can now display location data
            StringBuilder sb = new StringBuilder();

            sb.Append(sDivider);

            foreach (var location in locationList.Locations)
            {
                sb.Append("Location read from XML data: " + location.ToString() + Environment.NewLine);
            }
            sb.Append(sDivider);

            sb.Append(sDivider);
            sb.Append("Please use list boxes to generate reports " + Environment.NewLine);
            sb.Append(sDivider);

            txtResult.Text += sb.ToString();
            txtResult.Refresh();

            // Clear any items in listbox
            lsbLocations.Items.Clear();

            // Get all location and add to ListBox
            foreach (var location in locationList.Locations)
            {
                lsbLocations.Items.Add(location);
            }

            // Sort listbox items to alphabethical order
            lsbLocations.Sorted = true;

            // Insert all location option to first place
            lsbLocations.Items.Insert(0, "- All Location -");
        }

        private void lsbLocations_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (lsbLocations.SelectedItem.ToString() == "- All Location -")
            {
                // Get all dates from all location

                // Temporary list to hold date ones
                List<DateTime> lDates = new List<DateTime>();
                lDates.Clear();

                // Loop through locations
                foreach (var location in locationList.Locations)
                {
                    // Get dates from locations
                    foreach (var a in location.AirDatas)
                    {                        
                        // Check date already added to list
                        if (lDates.Contains(a.GetDate()))
                        {
                            // Do nothing
                        }
                        else
                        {
                            // Add date to list
                            lDates.Add(a.GetDate());
                        }                        
                    }
                }

                // Sort dates in list
                lDates.Sort();

                // Clear ListBox
                lsbDate.Items.Clear();

                // Add all date option to first place
                lsbDate.Items.Add("- All Date -");

                foreach (var d in lDates)
                {
                    lsbDate.Items.Add(d.ToShortDateString());
                }
            }
            else
            {
                // Clear any items in listbox
                lsbDate.Items.Clear();
                lsbDate.Items.Add("- All Date -");

                Location location = (Location)lsbLocations.SelectedItem;

                // Having finished generating data we can now display date data on form
                foreach (var a in location.AirDatas)
                {
                    lsbDate.Items.Add(a.GetDate().ToShortDateString());
                }
            }          

        }

        private void lsbDate_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (lsbLocations.SelectedItem.ToString() == "- All Location -")
            {
                if (lsbDate.SelectedItem.ToString() == "- All Date -")
                {
                    // Display all local all date details
                    AllDateGlobalReport();
                }
                else
                {
                    // Display all local one date details
                    DateGlobalReport();
                }                
            }
            else
            {
                if (lsbDate.SelectedItem.ToString() == "- All Date -")
                {
                    // Display onle local all dates details
                    AllDateLocalReport();
                }
                else
                {
                    // Display one local one date details
                    DateLocalReport();
                }
            }
        }

        private void AllDateGlobalReport()
        {
            // Display all local all date details

            // Must display largest individual particulates value recorded by the system first
            int iLargestParticulate = 0;

            // StringBuilder to build report content
            StringBuilder sb = new StringBuilder();

            // Generate particulate totals
            // Sort list first
            List<string> lTotal = new List<string>(locationList.CalculateLocationTotal());
            lTotal.Sort();
            sb.Append(sDivider);
            sb.Append("List of dates, with the total recorded particulates values (across all locations)," + Environment.NewLine + Environment.NewLine);

            foreach (var t in lTotal)
            {
                sb.Append(t + Environment.NewLine);
            }
            sb.Append(sDivider);

            List<string> lLargest = new List<string>();

            // Loop through locations
            foreach (var location in locationList.Locations)
            {
                sb.Append(sDivider);
                sb.Append("Location:" + location.ToString() + Environment.NewLine);
                

                // Get dates from locations
                foreach (var a in location.AirDatas)
                {
                    //Check largest particulate
                    if (a.Particulate >= iLargestParticulate)
                    {
                        // Add same largest particulates to report, to avoid display the firts one only
                        if (a.Particulate > iLargestParticulate)
                        {
                            // Clear list if find a higher particulate
                            lLargest.Clear();
                            lLargest.Add("Location: " + location.ToString() + ", Date: " + a.Date.ToShortDateString() +
                                ", Particulate: " + a.Particulate.ToString() + ", Temperature: " + a.Temperature.ToString() + ", Humidity: " +
                                a.Huminity.ToString() + Environment.NewLine);
                        }
                        else
                        {
                            lLargest.Add("Location: " + location.ToString() + ", Date: " + a.Date.ToShortDateString() +
                                ", Particulate: " + a.Particulate.ToString() + ", Temperature: " + a.Temperature.ToString() + ", Humidity: " +
                                a.Huminity.ToString() + Environment.NewLine);
                        }

                        iLargestParticulate = a.Particulate;

                    }

                    sb.Append(a.ToString() + Environment.NewLine);
                }

                sb.Append(sDivider);
            }

            // Add highest particulate first and then other content

            StringBuilder sbLargest = new StringBuilder();
            sbLargest.Append(sDivider);
            sbLargest.Append("Largest individual particulates value recorded by the system" + 
                Environment.NewLine + Environment.NewLine);

            // Sort locations
            lLargest.Sort();

            foreach( var l in lLargest)
            {
                sbLargest.Append(l);
            }

            sbLargest.Append(sDivider);

            // Add both sring builder to text box
            txtResult.Text =  sbLargest.ToString() + Environment.NewLine + sb.ToString();
        }

        private void DateGlobalReport()
        {
            // Display all local one date details

            // StringBuilder to build report content
            StringBuilder sb = new StringBuilder();

            // Loop through locations
            foreach (var location in locationList.Locations)
            {
                sb.Append(sDivider);
                sb.Append("Location:" + location.ToString() + Environment.NewLine);


                // Get dates from locations
                foreach (var a in location.AirDatas)
                {
                    Debug.Print(lsbDate.SelectedItem.ToString() + ", " + a.Date.ToShortDateString());

                    // Add selected date only
                    if (lsbDate.SelectedItem.ToString() == a.Date.ToShortDateString())
                    {
                        sb.Append(a.ToString() + Environment.NewLine);
                    }                    
                }

                sb.Append(sDivider);
            }

            // Add highest particulate first and then other content
            txtResult.Text = sb.ToString();
        }

        private void AllDateLocalReport()
        {
            // Display onle local all dates details

            // StringBuilder to build report content
            StringBuilder sb = new StringBuilder();

            // Loop through locations
            foreach (var location in locationList.Locations)
            {
                // Add selected location only
                if (lsbLocations.SelectedItem.ToString() == location.ToString())
                {
                    sb.Append(sDivider);
                    sb.Append("Location:" + location.ToString() + Environment.NewLine);

                    // Get dates from locations
                    foreach (var a in location.AirDatas)
                    {
                        sb.Append(a.ToString() + Environment.NewLine);
                    }

                    sb.Append(sDivider);
                }
            }

            // Add highest particulate first and then other content
            txtResult.Text = sb.ToString();
        }

        private void DateLocalReport()
        {
            // Display one local one date details

            // StringBuilder to build report content
            StringBuilder sb = new StringBuilder();

            // Loop through locations
            foreach (var location in locationList.Locations)
            {
                // Add selected location only
                if (lsbLocations.SelectedItem.ToString() == location.ToString())
                {
                    sb.Append(sDivider);
                    sb.Append("Location:" + location.ToString() + Environment.NewLine);

                    // Get dates from locations
                    foreach (var a in location.AirDatas)
                    {
                        Debug.Print(lsbDate.SelectedItem.ToString() + ", " + a.Date.ToShortDateString());

                        // Add selected date only
                        if (lsbDate.SelectedItem.ToString() == a.Date.ToShortDateString())
                        {
                            sb.Append(a.ToString() + Environment.NewLine);
                        }
                    }

                    sb.Append(sDivider);
                }
            }

            // Add highest particulate first and then other content
            txtResult.Text = sb.ToString();
        }

        private void Form1_FormClosed(object sender, FormClosedEventArgs e)
        {
            Environment.Exit(Environment.ExitCode);
        }

        private void btnExit_Click(object sender, EventArgs e)
        {
            Environment.Exit(Environment.ExitCode);
        }
    }
}
