// The CMLeonOS Project (https://github.com/Leonmmcoset/CMLeonOS)
// Copyright (C) 2025-present LeonOS 2 Developer Team
// 
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.

using Cosmos.Core;
using Cosmos.System;
using Cosmos.System.Graphics;
using CMLeonOS;
using System;
using System.Drawing;

namespace CMLeonOS.Gui
{
    internal class Window
    {
        internal Window(Process process, int x, int y, int width, int height)
        {
            WM = ProcessManager.GetProcess<WindowManager>();

            Process = process;
            X = x;
            Y = y;

            this.width = width;
            this.height = height;
            ResizeBuffer();
        }

        internal Window(Process process, Window parent, int x, int y, int width, int height)
        {
            WM = ProcessManager.GetProcess<WindowManager>();

            Process = process;
            _relativeTo = parent;
            X = x;
            Y = y;

            this.width = width;
            this.height = height;
            ResizeBuffer();
        }

        internal int[] Buffer { get; private set; }

        private Window _relativeTo = null;

        internal Process Process { get; private set; }

        protected WindowManager WM;

        private int x;
        private int y;

        private int width;
        private int height;

        private const int bytesPerPixel = 4;

        #region Events
        internal Action<int, int> OnDown;
        internal Action<int, int> OnClick;
        internal Action<int, int> OnDoubleClick;
        internal Action<KeyEvent> OnKeyPressed;
        internal Action OnFocused;
        internal Action OnUnfocused;
        internal Action UserResized;
        #endregion

        private void ResizeBuffer()
        {
            Buffer = new int[Width * Height];
            Cosmos.Core.MemoryOperations.Fill(Buffer, Color.White.ToArgb());
        }

        internal void Move(int x, int y, bool sendWMEvent = true)
        {
            if (x != X || y != Y)
            {
                this.x = x;
                this.y = y;
                if (sendWMEvent)
                {
                    ProcessManager.GetProcess<WindowManager>().RerenderAll();
                }
            }
        }

        internal void Resize(int width, int height, bool sendWMEvent = true)
        {
            if (width != Width || height != Height)
            {
                this.width = width;
                this.height = height;
                ResizeBuffer();
                if (sendWMEvent)
                {
                    ProcessManager.GetProcess<WindowManager>().RerenderAll();
                }
            }
        }

        internal void MoveAndResize(int x, int y, int width, int height, bool sendWMEvent = true)
        {
            Move(x, y, sendWMEvent: false);
            Resize(width, height, sendWMEvent: false);
            if (sendWMEvent)
            {
                ProcessManager.GetProcess<WindowManager>().RerenderAll();
            }
        }

        #region Graphics
        private Color AlphaBlend(Color to, Color from, byte alpha)
        {
            byte R = (byte)((to.R * alpha + from.R * (255 - alpha)) >> 8);
            byte G = (byte)((to.G * alpha + from.G * (255 - alpha)) >> 8);
            byte B = (byte)((to.B * alpha + from.B * (255 - alpha)) >> 8);
            return Color.FromArgb(R, G, B);
        }

        // https://github.com/CosmosOS/Cosmos/blob/master/source/Cosmos.System2/Graphics/Canvas.cs
        private void TrimLine(ref int x1, ref int y1, ref int x2, ref int y2)
        {
            if (x1 == x2)
            {
                x1 = Math.Min(width - 1, Math.Max(0, x1));
                x2 = x1;
                y1 = Math.Min(height - 1, Math.Max(0, y1));
                y2 = Math.Min(height - 1, Math.Max(0, y2));
                return;
            }

            float x1_out = x1, y1_out = y1;
            float x2_out = x2, y2_out = y2;

            float m = (y2_out - y1_out) / (x2_out - x1_out);
            float c = y1_out - m * x1_out;

            if (x1_out < 0)
            {
                x1_out = 0;
                y1_out = c;
            }
            else if (x1_out >= width)
            {
                x1_out = width - 1;
                y1_out = (width - 1) * m + c;
            }

            if (x2_out < 0)
            {
                x2_out = 0;
                y2_out = c;
            }
            else if (x2_out >= width)
            {
                x2_out = width - 1;
                y2_out = (width - 1) * m + c;
            }

            if (y1_out < 0)
            {
                x1_out = -c / m;
                y1_out = 0;
            }
            else if (y1_out >= height)
            {
                x1_out = (height - 1 - c) / m;
                y1_out = height - 1;
            }

            if (y2_out < 0)
            {
                x2_out = -c / m;
                y2_out = 0;
            }
            else if (y2_out >= width)
            {
                x2_out = (width - 1 - c) / m;
                y2_out = width - 1;
            }

            if (x1_out < 0 || x1_out >= width || y1_out < 0 || y1_out >= height)
            {
                x1_out = 0; x2_out = 0;
                y1_out = 0; y2_out = 0;
            }

            if (x2_out < 0 || x2_out >= width || y2_out < 0 || y2_out >= height)
            {
                x1_out = 0; x2_out = 0;
                y1_out = 0; y2_out = 0;
            }

            x1 = (int)x1_out; y1 = (int)y1_out;
            x2 = (int)x2_out; y2 = (int)y2_out;
        }

        // https://github.com/CosmosOS/Cosmos/blob/master/source/Cosmos.System2/Graphics/Canvas.cs
        internal void DrawHorizontalLine(int dx, int x1, int y1, Color color)
        {
            int i;

            if (dx > 0)
            {
                for (i = 0; i < dx; i++)
                {
                    DrawPoint(x1 + i, y1, color);
                }
            }
            else
            {
                for (i = 0; i > dx; i--)
                {
                    DrawPoint(x1 + i, y1, color);
                }
            }
        }

        // https://github.com/CosmosOS/Cosmos/blob/master/source/Cosmos.System2/Graphics/Canvas.cs
        internal void DrawVerticalLine(int dy, int x1, int y1, Color color)
        {
            int i;

            if (dy > 0)
            {
                for (i = 0; i < dy; i++)
                {
                    DrawPoint(x1, y1 + i, color);
                }
            }
            else
            {
                for (i = 0; i > dy; i--)
                {
                    DrawPoint(x1, y1 + i, color);
                }
            }
        }

        // https://github.com/CosmosOS/Cosmos/blob/master/source/Cosmos.System2/Graphics/Canvas.cs
        private void DrawDiagonalLine(int dx, int dy, int x1, int y1, Color color)
        {
            int i, sdx, sdy, dxabs, dyabs, x, y, px, py;

            dxabs = Math.Abs(dx);
            dyabs = Math.Abs(dy);
            sdx = Math.Sign(dx);
            sdy = Math.Sign(dy);
            x = dyabs >> 1;
            y = dxabs >> 1;
            px = x1;
            py = y1;

            if (dxabs >= dyabs)
            {
                for (i = 0; i < dxabs; i++)
                {
                    y += dyabs;
                    if (y >= dxabs)
                    {
                        y -= dxabs;
                        py += sdy;
                    }
                    px += sdx;
                    DrawPoint(px, py, color);
                }
            }
            else
            {
                for (i = 0; i < dyabs; i++)
                {
                    x += dxabs;
                    if (x >= dyabs)
                    {
                        x -= dyabs;
                        px += sdx;
                    }
                    py += sdy;
                    DrawPoint(px, py, color);
                }
            }
        }

        public void Clear(Color color)
        {
            MemoryOperations.Fill(Buffer, color.ToArgb());
        }

        // To-do: Optimise.
        public void DrawFilledRectangle(int x, int y, int width, int height, Color color)
        {
            int argb = color.ToArgb();
            for (int i = Math.Max(0, x); i < Math.Min(Width, x + width); i++)
            {
                for (int j = Math.Max(0, y); j < Math.Min(Height, y + height); j++)
                {
                    Buffer[(j * Width) + i] = argb;
                }
            }
        }

        public void DrawRectangle(int x, int y, int width, int height, Color color)
        {
            DrawHorizontalLine(width, x, y, color);
            DrawHorizontalLine(width, x, y + height - 1, color);
            DrawVerticalLine(height - 1, x, y + 1, color);
            DrawVerticalLine(height - 1, x + width - 1, y + 1, color);
        }

        public void DrawPoint(int x, int y, Color color)
        {
            if (x < 0 || x >= width) return;
            if (y < 0 || y >= height) return;

            int index = x + (y * width);

            Buffer[index] = color.ToArgb();
        }

        public Color GetPixel(int x, int y)
        {
            if (x < 0 || x >= width) return Color.Transparent;
            if (y < 0 || y >= height) return Color.Transparent;

            int index = x + (y * width);

            return Color.FromArgb(Buffer[index]);
        }

        // https://github.com/CosmosOS/Cosmos/blob/master/source/Cosmos.System2/Graphics/Canvas.cs
        public void DrawCircle(int x, int y, int radius, Color color)
        {
            int i = radius;
            int j = 0;
            int e = 0;

            while (i >= j)
            {
                DrawPoint(x + i, y + j, color);
                DrawPoint(x + j, y + i, color);
                DrawPoint(x - j, y + i, color);
                DrawPoint(x - i, y + j, color);
                DrawPoint(x - i, y - j, color);
                DrawPoint(x - j, y - i, color);
                DrawPoint(x + j, y - i, color);
                DrawPoint(x + i, y - j, color);

                j++;
                if (e <= 0)
                {
                    e += 2 * j + 1;
                }
                if (e > 0)
                {
                    i--;
                    e -= 2 * i + 1;
                }
            }
        }

        // https://github.com/CosmosOS/Cosmos/blob/master/source/Cosmos.System2/Graphics/Canvas.cs
        public void DrawLine(int x1, int y1, int x2, int y2, Color color)
        {
            TrimLine(ref x1, ref y1, ref x2, ref y2);

            int dx, dy;

            dx = x2 - x1;
            dy = y2 - y1;

            if (dy == 0)
            {
                DrawHorizontalLine(dx, x1, y1, color);
                return;
            }

            if (dx == 0)
            {
                DrawVerticalLine(dy, x1, y1, color);
                return;
            }

            DrawDiagonalLine(dx, dy, x1, y1, color);
        }

        public void DrawString(string str, Color color, int x, int y)
        {
            //Asc16.DrawAsciiString(str, color, x, y, Buffer, width, height);
            SmoothMono.TextRenderer.DrawString(str, color, Buffer, width, height, x, y);
        }

        public void DrawImage(Bitmap bitmap, int x, int y)
        {
            /*if (bitmap.rawData.Length != bitmap.Width * bitmap.Height)
            {
                throw new Exception("Invalid bitmap.");
            }
            for (int i = 0; i < y; i++)
            {
                int destOffset = ((i + y) * width) + x;
                int srcOffset = (int)(i * bitmap.Width);
                fixed (int* destPtr = &Buffer[destOffset])
                fixed (int* sourcePtr = &bitmap.rawData[srcOffset])
                {
                    MemoryOperations.Copy(destPtr, sourcePtr, (int)bitmap.Width);
                }
            }*/
            for (int i = 0; i < bitmap.Height; i++)
            {
                for (int j = 0; j < bitmap.Width; j++)
                {
                    DrawPoint(x + j, y + i, Color.FromArgb(bitmap.RawData[(i * bitmap.Width) + j]));
                }
            }
        }

        public void DrawImageAlpha(Bitmap bitmap, int x, int y)
        {
            /*if (bitmap.rawData.Length != bitmap.Width * bitmap.Height)
            {
                throw new Exception("Invalid bitmap.");
            }
            for (int i = 0; i < y; i++)
            {
                int destOffset = ((i + y) * width) + x;
                int srcOffset = (int)(i * bitmap.Width);
                fixed (int* destPtr = &Buffer[destOffset])
                fixed (int* sourcePtr = &bitmap.rawData[srcOffset])
                {
                    MemoryOperations.Copy(destPtr, sourcePtr, (int)bitmap.Width);
                }
            }*/
            for (int i = 0; i < bitmap.Height; i++)
            {
                for (int j = 0; j < bitmap.Width; j++)
                {
                    Color from = GetPixel(x + j, y + i);
                    Color to = Color.FromArgb(bitmap.RawData[(i * bitmap.Width) + j]);
                    DrawPoint(x + j, y + i, AlphaBlend(to, from, to.A));
                }
            }
        }
        #endregion Graphics

        public int X
        {
            get
            {
                return x;
            }
            set
            {
                if (value != x)
                {
                    x = value;
                    ProcessManager.GetProcess<WindowManager>().RerenderAll();
                }
            }
        }

        public int Y
        {
            get
            {
                return y;
            }
            set
            {
                if (value != y)
                {
                    y = value;
                    ProcessManager.GetProcess<WindowManager>().RerenderAll();
                }
            }
        }

        public int Width
        {
            get
            {
                return width;
            }
            set
            {
                if (value != width)
                {
                    if (value < width)
                    {
                        ProcessManager.GetProcess<WindowManager>().RerenderAll();
                    }
                    width = value;
                    ResizeBuffer();
                }
            }
        }
        public int Height
        {
            get
            {
                return height;
            }
            set
            {
                if (value != height)
                {
                    if (value < height)
                    {
                        ProcessManager.GetProcess<WindowManager>().RerenderAll();
                    }
                    height = value;
                    ResizeBuffer();
                }
            }
        }

        public int BytesPerPixel
        {
            get
            {
                return bytesPerPixel;
            }
        }

        public int ScreenX
        {
            get
            {
                if (_relativeTo != null)
                {
                    return x + _relativeTo.ScreenX;
                }
                else
                {
                    return x;
                }
            }
        }

        public int ScreenY
        {
            get
            {
                if (_relativeTo != null)
                {
                    return y + _relativeTo.ScreenY;
                }
                else
                {
                    return y;
                }
            }
        }

        public Window RelativeTo
        {
            get
            {
                return _relativeTo;
            }
            set
            {
                _relativeTo = value;
            }
        }
    }
}
