using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace S04_Projet
{
    class MultiThreadedTask
    {
        #region Attributs
        public enum Operation
        {
            Load,
            Filter
        }

        private enum RGB
        {
            R,
            G,
            B
        }

        private byte[] file;
        private int nbPixel;
        private int nbProcessors;
        private Pixel[,] Pixels;
        private Pixel[,] NewPixels;
        private Options opt;
        private short[,] filter;
        private double factor;
        private int size;
        #endregion

        public void setNbPixel(int nbPixel) => this.nbPixel = nbPixel;

        public void setFile(byte[] file) => this.file = file;

        private Operation ToDo;

        public MultiThreadedTask(Operation ToDo, byte[] file, Options opt)
        {
            this.ToDo = ToDo;
            this.opt = opt;
            this.file = file;
            nbPixel = opt.width * opt.height;
            nbProcessors = Environment.ProcessorCount;
        }

        public MultiThreadedTask(Operation ToDo, Pixel[,] Pixels, Options opt, short[,] filter, double factor)
        {
            this.ToDo = ToDo;
            this.opt = opt;
            this.Pixels = Pixels;
            this.filter = filter;
            this.factor = factor;
            size = filter.GetLength(0);
        }

        public void Start()
        {
            if (ToDo == Operation.Load) LoadImage();
            else if (ToDo == Operation.Filter) ApplyFilter();
        }

        #region LoadImage
        public void LoadImage()
        {
            Pixels = new Pixel[opt.width, opt.height];
            Thread[] threads = new Thread[nbProcessors];
            for (int i = 0; i < nbProcessors; i++)
            {
                threads[i] = new Thread(new ParameterizedThreadStart(LoadImageTask));
                threads[i].Start(i);
            }

            for (int i = 0; i < nbProcessors; i++)
                threads[i].Join();
        }

        public void LoadImageTask(object i)
        {
            if (file != null && Pixels != null)
            {
                int start = nbPixel / nbProcessors * (int)i;
                int end = nbPixel / nbProcessors * ((int)i + 1);

                int x, y;
                byte r, g, b;

                for (int j = start; j < end; j++)
                {
                    x = j % opt.width;
                    y = j / opt.width;
                    b = file[j * 3 + opt.padding * y + opt.offset];
                    g = file[j * 3 + opt.padding * y + opt.offset + 1];
                    r = file[j * 3 + opt.padding * y + opt.offset + 2];

                    Pixels[x, y] = new Pixel(r, g, b);
                }
            }
        }
        #endregion

        #region Filter
        public void ApplyFilter()
        {
            NewPixels = new Pixel[opt.width, opt.height];
            int pixelNumber = opt.width * opt.height;
            nbProcessors = Environment.ProcessorCount;
            int pixelPerThread = pixelNumber / nbProcessors;
            Thread[] threads = new Thread[nbProcessors];

            Console.WriteLine("Génération de {0} threads", nbProcessors);

            for (int i = 0; i < nbProcessors; i++)
            {
                threads[i] = new Thread(new ParameterizedThreadStart(ApplyFilterTask));
                threads[i].Start(i);
            }

            for (int i = 0; i < nbProcessors; i++)
            {
                threads[i].Join();
                Console.WriteLine("Thread {0} done", i);
            }
        }

        public void ApplyFilterTask(object i)
        {
            int x, y;
            int start = opt.width * opt.height / nbProcessors * (int)i;
            int end = opt.width * opt.height / nbProcessors * ((int)i + 1);
            byte[,] rMatrix, gMatrix, bMatrix;
            byte r, g, b;

            for (int j = start; j < end; j++)
            {
                x = j % opt.width;
                y = j / opt.width;

                rMatrix = GetMatrix(x, y, size, RGB.R);
                gMatrix = GetMatrix(x, y, size, RGB.G);
                bMatrix = GetMatrix(x, y, size, RGB.B);

                r = ConvolutionalResult(rMatrix, filter, size, factor);
                g = ConvolutionalResult(gMatrix, filter, size, factor);
                b = ConvolutionalResult(bMatrix, filter, size, factor);

                NewPixels[x, y] = new Pixel(r, g, b);
            }
        }

        /// <summary>
        /// Retourne la matrice de pixels de taille size ,de milieu (x0,y0) et de couleur RGB
        /// </summary>
        /// <param name="x0">Coordonnée verticale du pixel milieu</param>
        /// <param name="y0">Coordonnée horizontale du pixel milieu</param>
        /// <param name="size">Taille de la matrice de pixel désirée</param>
        /// <param name="rgb">Couleur souhaitée</param>
        /// <returns>Matrice de pixels de taille size ,de milieu (x0,y0) et de couleur RGB</returns>
        private byte[,] GetMatrix(int x0, int y0, int size, RGB rgb)
        {
            byte[,] matrix = new byte[size, size];

            for (int i = 0; i < size * size; i++)
            {
                int x = x0 + (i % size) - ((size - 1) / 2);
                int y = y0 + (i / size) - ((size - 1) / 2);

                if (x < 0) x = 0;
                else if (x >= opt.width) x = opt.width - 1;
                if (y < 0) y = 0;
                else if (y >= opt.height) y = opt.height - 1;

                if (rgb == RGB.R)
                    matrix[(i % size), (i / size)] = Pixels[x, y].r;
                else if (rgb == RGB.G)
                    matrix[(i % size), (i / size)] = Pixels[x, y].g;
                else if (rgb == RGB.B)
                    matrix[(i % size), (i / size)] = Pixels[x, y].b;
            }

            return matrix;
        }

        /// <summary>
        /// Calcul convolutionnel
        /// </summary>
        /// <param name="matrix">Matrice</param>
        /// <param name="conv">Convolution</param>
        /// <param name="size">Taille du filtre</param>
        /// <param name="factor">Factor de normalisation</param>
        /// <returns>Résultat du calcul convolutinnel</returns>
        public static byte ConvolutionalResult(byte[,] matrix, short[,] conv, int size, double factor)
        {
            short r = 0; ;
            for (int x = 0; x < size; x++)
                for (int y = 0; y < size; y++)
                    r += (short)(matrix[x, y] * conv[x, y]);
            r = (short)Math.Round(r * factor);
            if (r > 255) return 255;
            else if (r < 0) return 0;
            else return (byte)r;
        }
        #endregion

        public Pixel[,] getPixelMatrix()
        {
            return Pixels;
        }

        public Pixel[,] getFilteredMatrix()
        {
            return NewPixels;
        }
    }
}
