using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace TEST_DecodePng
{
    internal class PngDecoder
    {
        const int PNG_SIGNATURE_LENGTH = 8;
        readonly byte[] EXPECTED_PNG_SIGNATURE = new byte[] { 137, 80, 78, 71, 13, 10, 26, 10 };

        const int CHUNCK_SIZE_FIELD_LENGTH = 4;
        const int CHUNCK_TYPE_FIELD_LENGTH = 4;
        const int CHUNCK_CRC_FIELD_LENGTH = 4;

        const int IMAGE_WIDTH_FIELD_SIZE = 4;
        const int IMAGE_HEIGHT_FIELD_SIZE = 4;
        const int GAMA_FILED_LENGTH = 4;

        //Critical chunks
        const string FIRST_CHUNK_TYPE = "IHDR";
        const string LAST_CHUNCK_TYPE = "IEND";
        const string DATA_CHUNCK_TYPE = "IDAT";

        const string GAMA_CHUNCK_TYPE = "GAMA";

        public Stopwatch chrono;

        public PngDecoder()
        {
            chrono = new Stopwatch();
            //I start and stop the chronometer so all the methods can just use the Restart method.
            chrono.Start(); chrono.Stop();
        }
        public Bitmap Decode(string filename)
        {
            //Timing Stuff
            chrono.Reset();
            chrono.Start();
            //Timing Stuff

            byte[] content = File.ReadAllBytes(filename);
            int cursor = 0;

            byte[] pngSignature = new byte[PNG_SIGNATURE_LENGTH];
            for (int i = 0; i < PNG_SIGNATURE_LENGTH; i++)
            {
                pngSignature[i] = content[cursor];
                cursor++;
            }

            //Checks if the file has a valid png signature
            if (!compareByteArrays(EXPECTED_PNG_SIGNATURE, pngSignature))
                throw (new InvalidPngHeadersFileException("File signature not recognized"));


            List<Byte[]> chuncks = new List<Byte[]>();
            List<string> chuncksType = new List<string>();
            List<int> chuncksSize = new List<int>();

            int chunckSize = 0;
            string chunckType = "";

            while (chunckType.ToLower() != LAST_CHUNCK_TYPE.ToLower())
            {
                //Reading Chunck Size (Carefull, does not contain the size, type and crc bytes)
                byte[] ckln = new byte[] { content[cursor], content[cursor + 1], content[cursor + 2], content[cursor + 3] };
                Array.Reverse(ckln);
                chunckSize = BitConverter.ToInt32(ckln, 0);

                cursor += CHUNCK_SIZE_FIELD_LENGTH;

                //Reading the chunck type 
                byte[] cktp = new byte[] { content[cursor], content[cursor + 1], content[cursor + 2], content[cursor + 3] };
                chunckType = System.Text.Encoding.UTF8.GetString(cktp);

                cursor += CHUNCK_TYPE_FIELD_LENGTH;

                //Reading the chunck data
                byte[] chunck = new byte[chunckSize];
                for (int i = 0; i < chunckSize; i++)
                {
                    chunck[i] = content[cursor];
                    cursor++;
                }

                //Reading the CRC
                byte[] ckcrc = new byte[] { content[cursor], content[cursor + 1], content[cursor + 2], content[cursor + 3] };
                cursor += CHUNCK_CRC_FIELD_LENGTH;

                //PROCESS THE CRC 

                chuncks.Add(chunck);
                chuncksSize.Add(chunckSize);
                chuncksType.Add(chunckType);
            }

            //Now we have a list of all the chuncks in the PNG file
            //We can now process it

            //The First chunck MUST be a IHDR chunck so we test that
            if (chuncksType[0].ToLower() != FIRST_CHUNK_TYPE.ToLower())
                throw new InvalidPngHeadersFileException("Critial chunck not found : IHDR");
            //The Last chunck MUST be an IEND chunck so we test that
            if (chuncksType[chuncksType.Count - 1].ToLower() != LAST_CHUNCK_TYPE.ToLower())
                throw new InvalidPngHeadersFileException("Critical chunck not found : IEND");
            //There must be at least one IDAT chunck so we check that
            int dataChunckcount = 0;
            foreach(string cktype in chuncksType)
            {
                if (cktype.ToLower() == DATA_CHUNCK_TYPE.ToLower())
                    dataChunckcount++;
            }
            if (dataChunckcount == 0)
                throw new InvalidPngHeadersFileException("No data chuncks where found");


            PngMetaData header = null;
            float gama = 0;

            //Now we can process them
            for (int chunckCounter = 0; chunckCounter < chuncks.Count; chunckCounter++)
            {
                string cktype = chuncksType[chunckCounter];

                switch (cktype.ToUpper())
                {
                    //IHDR
                    case FIRST_CHUNK_TYPE:
                        int chunckCursor = 0;

                        byte[] btaImageWidth = new byte[IMAGE_WIDTH_FIELD_SIZE];
                        byte[] btaImageHeight = new byte[IMAGE_HEIGHT_FIELD_SIZE];

                        for (int i = 0; i < IMAGE_WIDTH_FIELD_SIZE; i++)
                        {
                            btaImageWidth[i] = chuncks[chunckCounter][chunckCursor];
                            chunckCursor++;
                        }
                        for (int i = 0; i < IMAGE_HEIGHT_FIELD_SIZE; i++)
                        {
                            btaImageHeight[i] = chuncks[chunckCounter][chunckCursor];
                            chunckCursor++;
                        }

                        //Those are in little endien notation so we need to reverse it 
                        Array.Reverse(btaImageWidth);
                        Array.Reverse(btaImageHeight);

                        int imageWidth = BitConverter.ToInt32(btaImageWidth, 0);
                        int imageHeight = BitConverter.ToInt32(btaImageHeight, 0);

                        int bitDepth = Convert.ToInt32(chuncks[chunckCounter][chunckCursor]);
                        chunckCursor++;
                        int colorType = Convert.ToInt32(chuncks[chunckCounter][chunckCursor]);
                        chunckCursor++;
                        int compressionMethod = Convert.ToInt32(chuncks[chunckCounter][chunckCursor]);
                        chunckCursor++;
                        int filterMethod = Convert.ToInt32(chuncks[chunckCounter][chunckCursor]);
                        chunckCursor++;
                        int interlaceMethod = Convert.ToInt32(chuncks[chunckCounter][chunckCursor]);
                        chunckCursor++;

                        header = new PngMetaData(imageHeight, imageWidth, (sbyte)bitDepth, (sbyte)colorType, (sbyte)compressionMethod, (sbyte)filterMethod, (sbyte)interlaceMethod);

                        if (!header.IsValid())
                            throw new InvalidPngHeadersFileException("Header invalide");
                        break;

                    case GAMA_CHUNCK_TYPE:
                        byte[] btGamma = new byte[GAMA_FILED_LENGTH];
                        //btGamma = new byte[] { chuncks[chunckCounter][0], chuncks[chunckCounter][1], chuncks[chunckCounter][2], chuncks[chunckCounter][3] };
                        for (int i = 0; i < GAMA_FILED_LENGTH; i++)
                        {
                            btGamma[i] = chuncks[chunckCounter][i];
                        }
                        //btGamma.Reverse();
                        Array.Reverse(btGamma);
                        gama = ((float)BitConverter.ToInt32(btGamma,0) / 1000F);
                        break;
                    default:
                        //Unrecognized chunck, as said in the doc should not stop the decoding
                        break;
                }
            }

            if (header != null)
            {
                MessageBox.Show("Image width: " + header.Width +
               Environment.NewLine + "Image height: " + header.Height +
               Environment.NewLine + "byte depth : " + header.BitDepth +
               Environment.NewLine + "color type : " + header.ColorType +
               Environment.NewLine + "compression method : " + header.CompressionMethod +
               Environment.NewLine + "filter method : " + header.FilterMethod +
               Environment.NewLine + "interface method : " + header.InterlaceMethod +
               Environment.NewLine + "gama : " + gama
               );
            }
           
            //Timing Stuff
            chrono.Stop();
            //Timing Stuff

            //TO REMOVE
            return new Bitmap(100, 100);
        }
        public bool compareByteArrays(byte[] firstArray, byte[] secondArray)
        {
            if (firstArray.Length != secondArray.Length)
                return false;
            bool result = true;
            for (int i = 0; i < firstArray.Length; i++)
            {
                if (firstArray[0] != secondArray[0])
                    result = false;
            }
            return result;
        }

    }
}
