using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace final_project
{
    public partial class RunAlgorithm : Form
    {
        /*************************************GLOBAL CONSTANTS******************************************/
        //NAME: TOP_BEST
        //TYPE: double
        //PURPOSE: 'TOP_BEST' is used to determine the range of the "best" individuals in a generation. Used with RndNumGen.
        private const double TOP_BEST = .60;
        //NAME: POINT_LIMIT
        //TYPE: int
        //PURPOSE: 'POINT_LIMIT' is used to determine the range by which a point coordinate can shift. Used for Triangle.Mutate
        private const int POINT_LIMIT = 20;
        //NAME: HUE_LIMIT
        //TYPE: int
        //PURPOSE: 'HUE_LIMIT' is used to determine the range by which a color value can change. Used for Triangle.Mutate
        private const int HUE_LIMIT = 40;
        //NAME: BEST
        //TYPE: int
        //PURPOSE: 'BEST' is the index of the best individual in a population
        private const int BEST = 0;
        /****************************************DATA MEMBERS*******************************************/
        //NAME: filePath
        //TYPE: string
        //PURPOSE: 'filePath' stores the file path of the user selecte target image
        private string filePath;
        public string FilePath
        { set { filePath = value; } get {return filePath;} }
        //NAME: individuals
        //TYPE: int
        //PURPOSE: 'individuals' stores the number of individuals the algorithm will run on
        private int individuals;
        public int Individuals
        { set { individuals = value; } get { return individuals; } }
        //NAME: polygons
        //TYPE: int
        //PURPOSE: 'polygons' stores the number of polygons per individual
        private int polygons;
        public int Polygons
        { set { polygons = value; } get { return polygons; } }
        //NAME: best
        //TYPE: int
        //PURPOSE: 'best' stores the number of individuals to advance from the current generation to the next without any changes to it
        private int best;
        public int Best
        { set { best = value; } get { return best; } }
        //NAME: mutants
        //TYPE: int
        //PURPOSE: 'mutants' stores the number of individuals to advance from the current generation to the next with changes to points and color
        private int mutants;
        public int Mutants
        { set { mutants = value; } get { return mutants; } }
        //NAME: crossovers
        //TYPE: int
        //PURPOSE: 'crossovers' store the number of individuals to create via crossover operation with parents from the current generation for the next 
        private int crossovers;
        public int Crossovers
        { set { crossovers = value; } get { return crossovers; } }
        /***************************************data members*********************************************/
        //NAME: currentPopulation
        //TYPE: Individual[]
        //PURPOSE: 'currentPopulation' is an array, storing a collection of Individuals from which 'nextPopulation' will be created
        private Individual[] currentPopulation;
        //NAME: nextPopulation
        //TYPE: Individual[]
        //PURPOSE: 'nextPopulation' is an array, storing a collection of Individuals created from 'currentPopulation'
        private Individual[] nextPopulation;
        //NAME: parentAindex
        //TYPE: int
        //PURPOSE: 'parentAindex' stores the index of the 'Individual' from 'currentGeneration' to be used in the crossover function
        private int parentAindex;
        //NAME: parentBindex
        //TYPE: int
        //PURPOSE: 'parentBindex' stores the index of the 'Individual' from 'currentGeneration' to be used in the crossover function
        private int parentBindex;
        //NAME: generationCount
        //TYPE: int
        //PURPOSE: 'generationCount' is a counter, tracking the number of generations passed
        private int generationCount;
        //NAME: imageWidth
        //TYPE: int
        //PURPOSE: 'imageWidth' is the width of both the target and all 'Individual' bitmaps
        private int imageWidth;
        //NAME: imageHeight
        //TYPE: int
        //PURPOSE: 'imageHeight' is the height of both the target and all 'Individual' bitmaps
        private int imageHeight;
        //NAME: nextPopIndex
        //TYPE: int
        //PURPOSE: 'nextPopIndex' is the index counter variables for 'nextPopluation'
        private int nextPopIndex;
        //NAME: currentPopIndex
        //TYPE: int
        //PURPOSE: 'currentPopIndex' is used when selecting a random index for 'currentPopIndex'
        private int currentPopIndex;
        //NAME: first
        //TYPE: bool
        //PURPOSE: 'first' is a boolean used at the first generation to set the DateTime variable 'start'
        private bool first;
        //NAME: startTime
        //TYPE: DateTime
        //PURPOSE: 'startTime' stores the time at which the algorithm began to run
        private DateTime startTime;
        //NAME: currentTime
        //TYPE: DateTime
        //PURPOSE: 'currentTime' stores the time when a generation is complete. Used to compute time per generation and is added to 'elapsedTime'
        private DateTime currentTime;
        //NAME: elapsedTime
        //TYPE: TimeSpan
        //PURPOSE: 'elapsedTime' is used to compute the time from 'currentTime' to 'startTime'
        private TimeSpan elapsedTime;
        //NAME: cancel
        //TYPE: CancellationTokenSource
        //PURPOSE: 'cancel' is used while the algorithm is running in the event the user decides to end the program. 
        //         This causes a CancellationRequest exception.
        private CancellationTokenSource cancel;
        //NAME: closed
        //TYPE: bool
        //PURPOSE: bool flag to indicate the form as been closed
        private bool closedForm;
        public bool ClosedForm
        { set { closedForm = value; } get { return closedForm; } }
        //NAME: folderPath
        //TYPE: string
        //PURPOSE: 'folderPath' store the path to the folder created specifically for each run of the program. This is where the best images of every 50th generation can be found.
        private string folderPath;
        //NAME: bestScore
        //TYPE: int
        //PURPOSE: 'bestScore' stores the initial best score from currentPopulation 0.
        private int bestScore;

        private int toStop, currentBest;
        /********************************************************************************************************************************/
        public RunAlgorithm()
        {
            InitializeComponent();
            this.WindowState = FormWindowState.Maximized;
        }
        //EVENT HANDLER: LoadAlgorithm, load
        //PURPOSE: This event handler loads data onto the form, and prepared to run the algorithm: setting the times and calling the task
        private void LoadAlgorithm(object sender, EventArgs e)
        {
            InitializeForm();

            this.elapsedTime = TimeSpan.Zero;                                //set elapsedTime to 0
            tbxTimeElapsed.Text = this.elapsedTime.ToString();
            this.imageWidth = Image.FromFile(this.FilePath).Width;           //get width of the target image
            this.imageHeight = Image.FromFile(this.FilePath).Height;         //get height of the target image
            this.generationCount = 0;                                        //set generations count to 1
            tbxGenerations.Text = this.generationCount.ToString();
            this.first = true;

            //create a folder to store every 50th best image
            string dateTime = DateTime.Now.ToString("yyyy-MM-dd");
            string folderName = Path.GetFileNameWithoutExtension(this.FilePath) + dateTime;
            folderPath = Path.Combine(Environment.CurrentDirectory, folderName);
            Directory.CreateDirectory(folderPath);
            InitializeCurrentPop();
        }
        //EVENT HANDLER: Button button, click
        //PURPOSE: This event handler begins the task or pauses the task 
        private async void ButtonClick(object sender, EventArgs e)
        {
            tbxGenerations.Text = this.generationCount.ToString();      //display starting count on the form

            button.Enabled = false;
 
            this.cancel = new CancellationTokenSource();
            CancellationToken token = cancel.Token;

            Task running = RunningAlgorithm(token);                     //call the task
            await running;

            button.Enabled = true;
        }
        //EVENT HANDLER: Cancel button, click
        //PURPOSE: This event handler throws a cancellation request
        private void cancelClick(object sender, EventArgs e)
        {
            OpenDoneForm();
            //cancel.Cancel();
        }
        /**********************************************THE TASK***************************************/
        public async Task RunningAlgorithm(CancellationToken token)
        {
            if (this.first) { this.startTime = DateTime.Now; first = false; }

            //for (int i = 0; i < 100; i++)
            //{
            while (currentBest > toStop)
            {
                this.nextPopIndex = 0;
                this.nextPopulation = new Individual[this.Individuals];
                for (int j = 0; j < this.Best; j++)
                {
                    this.nextPopulation[this.nextPopIndex] = new Individual(this.currentPopulation[this.nextPopIndex], this.imageWidth, this.imageHeight, this.Polygons);
                    this.nextPopIndex++;
                }
                for (int k = 0; k < this.Mutants; k++)
                {
                    this.currentPopIndex = RndNumGen.Next(0, (int)(this.Individuals * TOP_BEST));
                    this.nextPopulation[this.nextPopIndex] = new Individual(this.currentPopulation[this.currentPopIndex], this.imageWidth, this.imageHeight, this.Polygons);
                    this.nextPopulation[this.nextPopIndex].mutateIndividual(this.imageWidth, this.imageHeight, Image.FromFile(this.FilePath), this.Polygons, POINT_LIMIT, HUE_LIMIT);
                    this.nextPopIndex++;
                }
                for (int l = 0; l < this.Crossovers; l++)
                {
                    this.nextPopulation[this.nextPopIndex] = new Individual(this.imageWidth, this.imageHeight, this.Polygons, Image.FromFile(this.FilePath));
                    SelectParents();
                    this.nextPopulation[this.nextPopIndex].crossoverIndividual(this.currentPopulation[this.parentAindex], this.currentPopulation[this.parentBindex], Image.FromFile(this.FilePath), this.Polygons);
                    this.nextPopIndex++;
                }
                token.ThrowIfCancellationRequested();
                await Task.Delay(5);

                SortArray(this.nextPopulation);

                if (this.generationCount % 50 == 0)
                {
                    string fileName = this.generationCount + "_" + this.nextPopulation[BEST].Score + ".png";
                    string fullPath = Path.Combine(folderPath, fileName);

                    this.nextPopulation[BEST].TheImage.Save(fullPath, ImageFormat.Png);
                }
                if(this.generationCount % 20 == 0)
                {
                    pbBest.Image = this.nextPopulation[BEST].TheImage;
                    tbxBestScore.Text = this.nextPopulation[BEST].Score.ToString();
                }
                NextGeneration(this.nextPopulation, this.currentPopulation, this.Individuals);
                currentBest = this.currentPopulation[BEST].Score;
                this.generationCount++;

                this.currentTime = DateTime.Now;
                this.elapsedTime = currentTime - startTime;
                tbxTimeElapsed.Text = this.elapsedTime.ToString();

                tbxGenerations.Text = this.generationCount.ToString();
            }
            OpenDoneForm();
            //}
        }
        /*********************************************FUNCTIONS************************************/
        //FUNCTION: InitializeForm
        //PARAMETERS: Image , number of individuals, triangles, best, mutants, and crossovers
        //PURPOSE: 'InitializeForm' will fill in all of the textboxes on the form with the corresponding information
        private void InitializeForm()
        {
            pbTarget.Image = Image.FromFile(this.FilePath);             //set information on the form
            tbxIndividuals.Text = this.Individuals.ToString();
            tbxPolygons.Text = this.Polygons.ToString();
            tbxBest.Text = this.Best.ToString();
            tbxMutants.Text = this.Mutants.ToString();
            tbxCrossovers.Text = this.Crossovers.ToString();
        }
        //FUNCTION: InitializeCurrentPop
        //PARAMETERS: Image width, Image height, number of triangles, Image target
        //PURPOSE: 'InitializeCurrentPop' initializes the 'currentPopulation', sorts it, and sets the image of the best in the right pictureBox on the form
        private void InitializeCurrentPop()
        {
            currentPopulation = new Individual[this.Individuals];       //initialize 'currentPopulation'
            for (int i = 0; i < this.Individuals; i++)
            {
                currentPopulation[i] = new Individual(this.imageWidth, this.imageHeight, this.Polygons, Image.FromFile(this.FilePath));
            }
            SortArray(this.currentPopulation);
            pbBest.Image = this.currentPopulation[BEST].TheImage;
            Console.WriteLine("Initial best score..." + this.currentPopulation[BEST].Score.ToString());
            tbxBestScore.Text = this.currentPopulation[BEST].Score.ToString();
            bestScore = this.currentPopulation[BEST].Score;
            currentBest = this.currentPopulation[BEST].Score;
            toStop = (int)(bestScore * .10);

            string fileName = this.generationCount + "BEST_" + this.currentPopulation[BEST].Score + ".png";
            string fullPath = Path.Combine(folderPath, fileName);

            this.currentPopulation[BEST].TheImage.Save(fullPath, ImageFormat.Png);
        }
        //FUNCTION: SortArray
        //PARAMETERS: array of Individuals
        //PURPOSE: Call the Array class Sort function on the parameter to sort the array based on Scores...sorting from least to greatest
        private void SortArray(Individual[] array)
        { Array.Sort(array, (x, y) => x.Score.CompareTo(y.Score)); }
        //FUNCTION: NextGeneration
        //PARAMETERS: array of Individuals to copy, array of Individuals to copy into, size of the arrays
        //PURPOSE: Call the Array class Copy function with the paramters to copy 'nextPopulation' into 'currentPopulation'...to prepare for the
        //         next generation
        private void NextGeneration(Individual[] toCopy, Individual[] toReplace, int size)
        { Array.Copy(toCopy, toReplace, size); }
        //FUNCTION: SelectParents
        //PARAMETERS: --
        //PURPOSE: The function 'SelectParents' is used to select the indices for parentA and parentB in the crossover function. The indices are
        //         selected between the following range: 0 and the best 
        private void SelectParents()
        {
            parentAindex = RndNumGen.Next(0, (int)(this.Individuals * TOP_BEST));
            parentBindex = RndNumGen.Next(0, (int)(this.Individuals * TOP_BEST));
            while (parentBindex == parentAindex)
                parentBindex = RndNumGen.Next(0, (int)(this.Individuals * TOP_BEST));
        }
        //FUNCTION: OpenDoneForm
        //PARAMETERS: --
        //PURPOSE: The function 'OpenDoneForm' will open the AlgorithmDone form when either the cancel button is clicked or the algoritm reaches its goal
        private void OpenDoneForm()
        {

            MessageBox.Show("Algorithm will stop running now.");
            AlgorithmDone done = new AlgorithmDone();

            done.FilePath = this.FilePath;
            done.LastBestImage = this.currentPopulation[BEST].TheImage;
            done.LastBestScore = this.currentPopulation[BEST].Score;
            done.Individuals = this.Individuals;
            done.Polygons = this.Polygons;
            done.Best = this.Best;
            done.Mutants = this.Mutants;
            done.Crossovers = this.Crossovers;
            done.GenerationCount = this.generationCount;
            done.ElapsedTime = this.elapsedTime;

            done.ShowDialog();
            if (done.ClosedForm)
            {
                this.ClosedForm = true;
                this.Close();
            }
        }
    }
}
