using System;
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];
            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();
            }
        }

        public unsafe 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 = new byte[size, size];
            byte[,] gMatrix = new byte[size, size];
            byte[,] bMatrix = new byte[size, size];
            byte r, g, b;

            fixed (short* filterPtr = filter)
            {
                fixed (byte* rMatrixPtr = rMatrix, gMatrixPtr = gMatrix, bMatrixPtr = bMatrix)
                {
                    for (int j = start; j < end; j++)
                    {
                        x = j % opt.width;
                        y = j / opt.width;

                        GetMatrix(x, y, size, RGB.R, rMatrixPtr);
                        GetMatrix(x, y, size, RGB.G, gMatrixPtr);
                        GetMatrix(x, y, size, RGB.B, bMatrixPtr);
                        r = ConvolutionalResult(rMatrixPtr, filterPtr, size, factor);
                        g = ConvolutionalResult(gMatrixPtr, filterPtr, size, factor);
                        b = ConvolutionalResult(bMatrixPtr, filterPtr, 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 unsafe void GetMatrix(int x0, int y0, int size, RGB rgb, byte* matrixPtr)
        {
            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)
                    *(matrixPtr + i) = Pixels[x, y].r;
                else if (rgb == RGB.G)
                    *(matrixPtr + i) = Pixels[x, y].g;
                else if (rgb == RGB.B)
                    *(matrixPtr + i) = Pixels[x, y].b;
            }
        }

        /// <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 unsafe byte ConvolutionalResult(byte* matrix, short* conv, int size, double factor)
        {
            short r = 0; ;

            for (int i = 0; i < size * size; i++)
                r += (short)(*(matrix + i) * *(conv + i));

            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;
        }
    }
}
