using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Drawing;
using System.IO;
using System.Runtime.ExceptionServices;
using System.Diagnostics;
using System.Security.Cryptography.X509Certificates;



namespace BMP_Benchmark
{
    enum Enum { Addition, Subtraction, Multiplication, Division, bitShift, byteOr, byteAnd, byteNot, Cast, Logical, Pow, SinCos }
    class Operations
    {
        public string Name;
        public Dictionary<Enum, int> subOperations { get; set; }
        public Operations(string newName)
        {
            Name = newName;
            subOperations = new Dictionary<Enum, int>();
            foreach (Enum op in Enum.GetValues(typeof(Enum))) subOperations[op] = 0;
        }
        public void addtoSubOperationByName(Enum Which, int amount) => subOperations[Which] += amount;
        public int getSubOperationByName(Enum Which) => subOperations[Which];
    }
    
    static class Globals
    {
        public static Operations All = new Operations("All");
        public static void AddToCount(int Addition = 0, int Subtraction = 0, int Multiplication = 0, int Division = 0, int bitShift = 0, int byteOr = 0, int byteAnd = 0, int Cast = 0, int Logical = 0, int Pow = 0, int SinCos = 0)
        {
            All.addtoSubOperationByName(Enum.Addition, Addition);
            All.addtoSubOperationByName(Enum.Subtraction, Subtraction);
            All.addtoSubOperationByName(Enum.Multiplication, Multiplication);
            All.addtoSubOperationByName(Enum.Division, Division);
            All.addtoSubOperationByName(Enum.bitShift, bitShift);
            All.addtoSubOperationByName(Enum.byteOr, byteOr);
            All.addtoSubOperationByName(Enum.byteAnd, byteAnd);
            All.addtoSubOperationByName(Enum.Cast, Cast); 
            All.addtoSubOperationByName(Enum.Logical, Logical); 
            All.addtoSubOperationByName(Enum.Pow, Pow); 
            All.addtoSubOperationByName(Enum.SinCos, SinCos); 

        }
    }

    class Coordinate
    {
        public int X { get; set; }
        public int Y { get; set; }
        public Coordinate(int x, int y) { X = x; Y = y; }
    }

    class BMPfile
    {
        private int sizeX_pixels, sizeY, bytesInRow;
        private string fileName;
        private byte[] mapArray;
        
        public BMPfile(int sizeX, int sizeY, string fileName)
        {
            this.sizeX_pixels = sizeX;
            this.sizeY = sizeY;
            this.fileName = fileName;
            this.bytesInRow = (sizeX * 2 + 3) & ~3;
            mapArray = new byte[this.bytesInRow * sizeY];

            for (int i = 0; i<this.bytesInRow * sizeY; i++)
            {
                mapArray[i] = 0xFF;
            }
        }

        public void drawFilledEllipse(Coordinate center, int radiusX, int radiusY, ushort color)
        {
            for (int y = -radiusY; y <= radiusY; y++)
            {
                for (int x = -radiusX; x <= radiusX; x++)
                {
                    if ((x * x) / (double)(radiusX * radiusX) + (y * y) / (double)(radiusY * radiusY) <= 1)
                    {
                        writePixel(center.X + x, center.Y + y, color);
                        Globals.AddToCount(3, 0, 3, 2, 0, 0, 0, 2, 1);
                    }
                    Globals.AddToCount(1, 0, 0, 0, 0, 0, 0, 0, 1);
                }

                Globals.AddToCount(1, 1, 0, 0, 0, 0, 0, 0, 1);
            }
            Globals.AddToCount(0, 1, 0, 0, 0, 0, 0, 0, 0);
        }

        public void writePixel(int x, int y, ushort color)
        {
            if (x >= sizeX_pixels || x < 0 || y >= sizeY || y < 0) return;

            int byteLoc = (x * 2) + (y * bytesInRow);
            
            mapArray[byteLoc] = (byte)(color & 0xFF);
            mapArray[byteLoc + 1] = (byte)((color >> 8) & 0xFF);


            Globals.AddToCount(1, 0, 2, 0, 8, 0, 2, 2, 7);
        }

        public void setResoliution(byte[] header, int width, int height) {
            header[18] = (byte)(width & 0xFF);
            header[19] = (byte)((width >> 8) & 0xFF);
            header[20] = (byte)((width >> 16) & 0xFF);
            header[21] = (byte)((width >> 24) & 0xFF);

            header[22] = (byte)(height & 0xFF);
            header[23] = (byte)((height >> 8) & 0xFF);
            header[24] = (byte)((height >> 16) & 0xFF);
            header[25] = (byte)((height >> 24) & 0xFF);
        }

        public void writeToFile()
        {
            using (FileStream file = new FileStream(fileName, FileMode.Create, FileAccess.Write))
            {
                byte[] header = new byte[54];
                header[0] = 0x42; header[1] = 0x4D; 
                int fileSize = 54 + mapArray.Length;
                byte[] fSize = BitConverter.GetBytes(fileSize);
                Array.Copy(fSize, 0, header, 2, 4);
                header[10] = 54; 
                header[14] = 40; 
                setResoliution(header, sizeX_pixels, sizeY);
                header[26] = 1;  
                header[28] = 16;
                
                file.Write(header, 0, header.Length);
                file.Write(mapArray, 0, mapArray.Length);
            }
        }
    }
    class Program
    {
        static Random rand = new Random();

        static ushort GetRandomColor565()
        {
            int r = rand.Next(0, 32);
            int g = rand.Next(0, 64);
            int b = rand.Next(0, 32);
            Globals.AddToCount(0,0,0,0,16,2,0,0,0,0,0);
            return (ushort)((r << 11) | (g << 5) | b);
        }
        static void PrinttableAllBasics(List<Operations> list, string Head)
        {
            const int sizeOfCell = 20;

            // Console.WriteLine("");
            // Console.WriteLine("");

            // Console.WriteLine("┌─────────────────────────────────────────────┐");
            // Console.WriteLine( $"│ {Head,((sizeOfCell + 3) * -2)+3} │");
            // Console.WriteLine("└─────────────────────────────────────────────┘");

            Operations fullCount = new Operations("All");
            foreach (Enum op in Enum.GetValues(typeof(Enum)))
            {
                int amount = 0;
                foreach (Operations ops in list)
                {
                    amount += ops.getSubOperationByName(op);
                }

                fullCount.addtoSubOperationByName(op, amount);
            }



            string header = "│                      ";

            header += $"│ {fullCount.Name,-sizeOfCell} ";

            header += "│";

            int cur = 0;
            // Console.WriteLine("┌──────────────────────┬──────────────────────┐");
            foreach (Enum op in Enum.GetValues(typeof(Enum)))
            {
                string curLine = "";
                curLine += $"│ {op.ToString(),-sizeOfCell} ";

                cur += fullCount.getSubOperationByName(op);
                curLine += $"│ {fullCount.getSubOperationByName(op).ToString(),sizeOfCell} ";
                curLine += "│";
                // Console.WriteLine(curLine);
            }
            // Console.WriteLine("└──────────────────────┴──────────────────────┘");
            Console.WriteLine(cur);
        }
        static void Recursion(BMPfile BMP, int PictureSize, Coordinate Pos, int maxDepth, int curDepth)
        {
            if (maxDepth < curDepth) { 
                Globals.AddToCount(0, 0, 0, 0, 0, 0, 0, 0, 1);
                return; 
            }
            int length = (int)(PictureSize * 0.55 / Math.Pow(2, curDepth));

            double radius = PictureSize * 0.2 / Math.Pow(2, curDepth);
            BMP.drawFilledEllipse(Pos, (int)(radius*0.7), (int)radius, GetRandomColor565());

            double height = Math.Sin(30 * (Math.PI / 180)) * length;
            double width = Math.Cos(30 * (Math.PI / 180)) * length;


            Recursion(BMP, PictureSize, new Coordinate((int)(Pos.X + width), (int)(Pos.Y + height)), maxDepth, curDepth + 1);
            Recursion(BMP, PictureSize, new Coordinate((int)(Pos.X - width), (int)(Pos.Y + height)), maxDepth, curDepth + 1);
            Recursion(BMP, PictureSize, new Coordinate((int)(Pos.X), (int)(Pos.Y - length)), maxDepth, curDepth + 1);
            Globals.AddToCount(6, 2, 7, 4, 0, 0, 0, 8, 0, 2, 2);

        }
        static void Main(string[] args)
        {
            Console.WriteLine("Program started");

            for (int i = 1; i <= 11; i++)
            {
                int CurSize1 = 5000; 
                BMPfile BMP1 = new BMPfile(CurSize1,CurSize1, "sample.bmp"); 
                Globals.All = new Operations("All");


                Recursion(BMP1, CurSize1, new Coordinate(CurSize1/2, (int)(CurSize1*0.6)), i, 1);

                BMP1.writeToFile();


                List<Operations> list = [Globals.All];
                Console.WriteLine("curr recursion: " + i.ToString());
                PrinttableAllBasics(list, "Basic Math call for each of the functions");
            }
            

            


            Console.WriteLine("Program finished");
        }        
    }
}
