using OpenTK.Graphics.OpenGL4;
using OpenTK.Windowing.Common;
using OpenTK.Windowing.Desktop;
using OpenTK.Mathematics;
using OpenTK.Windowing.GraphicsLibraryFramework;

namespace fluidsC_;

public class FluidWindow : GameWindow
{
	private FluidSimulation _fluid;
	private int _vertexArrayObject;
	private int _vertexBufferObject;
	private int _elementBufferObject;
	private int _shaderProgram;
	private int _texture;

	private readonly int _fluidSizeX = 128;
	private readonly int _fluidSizeY = 128;
	private float[] _pixels;

	private bool _mouseDown = false;
	private Vector2 _lastMousePos;
	private MouseButton _activeButton;

	public FluidWindow(GameWindowSettings gameWindowSettings, NativeWindowSettings nativeWindowSettings)
		: base(gameWindowSettings, nativeWindowSettings)
	{
		_fluid = new FluidSimulation(_fluidSizeX, _fluidSizeY);
		_pixels = new float[_fluidSizeX * _fluidSizeY * 4];
	}

	protected override void OnLoad()
	{
		base.OnLoad();

		GL.ClearColor(0.1f, 0.1f, 0.1f, 1.0f);

		SetupShaders();
		SetupTexture();
		SetupQuad();

		Console.WriteLine("Fluid Simulation Controls:");
		Console.WriteLine("• Click and drag to add fluid");
		Console.WriteLine("• Right click to add velocity");
		Console.WriteLine("• R key to reset simulation");
		Console.WriteLine("• ESC to exit");
	}

	private void SetupShaders()
	{
		const string vertexShaderSource = @"
            #version 330 core
            layout (location = 0) in vec2 aPosition;
            layout (location = 1) in vec2 aTexCoord;
            
            out vec2 texCoord;
            
            void main()
            {
                gl_Position = vec4(aPosition, 0.0, 1.0);
                texCoord = aTexCoord;
            }";

		const string fragmentShaderSource = @"
            #version 330 core
            in vec2 texCoord;
            out vec4 fragColor;
            
            uniform sampler2D fluidTexture;
            
            void main()
            {
                vec4 color = texture(fluidTexture, texCoord);
                // Create a nice blue-to-cyan color gradient based on density
                fragColor = vec4(color.r * 0.2, color.r * 0.5, color.r, 1.0);
            }";

		var vertexShader = GL.CreateShader(ShaderType.VertexShader);
		GL.ShaderSource(vertexShader, vertexShaderSource);
		GL.CompileShader(vertexShader);

		var fragmentShader = GL.CreateShader(ShaderType.FragmentShader);
		GL.ShaderSource(fragmentShader, fragmentShaderSource);
		GL.CompileShader(fragmentShader);

		_shaderProgram = GL.CreateProgram();
		GL.AttachShader(_shaderProgram, vertexShader);
		GL.AttachShader(_shaderProgram, fragmentShader);
		GL.LinkProgram(_shaderProgram);

		GL.DeleteShader(vertexShader);
		GL.DeleteShader(fragmentShader);
	}

	private void SetupTexture()
	{
		_texture = GL.GenTexture();
		GL.BindTexture(TextureTarget.Texture2D, _texture);

		GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear);
		GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);
		GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.ClampToEdge);
		GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.ClampToEdge);

		// Initialize with empty data
		GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba32f,
					 _fluidSizeX, _fluidSizeY, 0, PixelFormat.Rgba, PixelType.Float, IntPtr.Zero);
	}

	private void SetupQuad()
	{
		float[] vertices = {
            // positions     // texture coords
            -1.0f,  1.0f,   0.0f, 1.0f,
			-1.0f, -1.0f,   0.0f, 0.0f,
			 1.0f, -1.0f,   1.0f, 0.0f,
			 1.0f,  1.0f,   1.0f, 1.0f
		};

		uint[] indices = {
			0, 1, 2,
			2, 3, 0
		};

		_vertexArrayObject = GL.GenVertexArray();
		_vertexBufferObject = GL.GenBuffer();
		_elementBufferObject = GL.GenBuffer();

		GL.BindVertexArray(_vertexArrayObject);

		GL.BindBuffer(BufferTarget.ArrayBuffer, _vertexBufferObject);
		GL.BufferData(BufferTarget.ArrayBuffer, vertices.Length * sizeof(float), vertices, BufferUsageHint.StaticDraw);

		GL.BindBuffer(BufferTarget.ElementArrayBuffer, _elementBufferObject);
		GL.BufferData(BufferTarget.ElementArrayBuffer, indices.Length * sizeof(uint), indices, BufferUsageHint.StaticDraw);

		// Position attribute
		GL.VertexAttribPointer(0, 2, VertexAttribPointerType.Float, false, 4 * sizeof(float), 0);
		GL.EnableVertexAttribArray(0);

		// Texture coordinate attribute
		GL.VertexAttribPointer(1, 2, VertexAttribPointerType.Float, false, 4 * sizeof(float), 2 * sizeof(float));
		GL.EnableVertexAttribArray(1);
	}

	protected override void OnRenderFrame(FrameEventArgs e)
	{
		base.OnRenderFrame(e);

		GL.Clear(ClearBufferMask.ColorBufferBit);

		UpdateTextureFromFluid();

		GL.UseProgram(_shaderProgram);
		GL.BindTexture(TextureTarget.Texture2D, _texture);
		GL.BindVertexArray(_vertexArrayObject);
		GL.DrawElements(PrimitiveType.Triangles, 6, DrawElementsType.UnsignedInt, 0);

		SwapBuffers();
	}

	protected override void OnUpdateFrame(FrameEventArgs e)
	{
		base.OnUpdateFrame(e);

		// Update fluid simulation
		_fluid.Step(0.1f, 0.0001f, 0.0001f);
		_fluid.FadeDensity(0.002f);

		Title = $"Fluid Simulation :3 - FPS: {1.0 / e.Time:0}";
	}

	private void UpdateTextureFromFluid()
	{
		// Convert fluid density to pixel data
		for (int y = 0; y < _fluidSizeY; y++)
		{
			for (int x = 0; x < _fluidSizeX; x++)
			{
				int index = (y * _fluidSizeX + x) * 4;
				float density = Math.Clamp(_fluid.GetDensity(x, y), 0, 1);

				_pixels[index] = density;         // R
				_pixels[index + 1] = density;     // G  
				_pixels[index + 2] = density;     // B
				_pixels[index + 3] = 1.0f;        // A
			}
		}

		GL.BindTexture(TextureTarget.Texture2D, _texture);
		GL.TexSubImage2D(TextureTarget.Texture2D, 0, 0, 0, _fluidSizeX, _fluidSizeY,
						PixelFormat.Rgba, PixelType.Float, _pixels);
	}

	protected override void OnMouseDown(MouseButtonEventArgs e)
	{
		base.OnMouseDown(e);
		_mouseDown = true;
		_activeButton = e.Button;
		_lastMousePos = new Vector2(MousePosition.X, MousePosition.Y);
	}

	protected override void OnMouseUp(MouseButtonEventArgs e)
	{
		base.OnMouseUp(e);
		_mouseDown = false;
	}

	protected override void OnMouseMove(MouseMoveEventArgs e)
	{
		base.OnMouseMove(e);

		if (_mouseDown)
		{
			var mousePos = new Vector2(e.X, e.Y);
			var delta = mousePos - _lastMousePos;

			int fluidX = (int)(e.X / Size.X * _fluidSizeX);
			int fluidY = (int)((Size.Y - e.Y) / Size.Y * _fluidSizeY);

			// Ensure coordinates are within bounds
			fluidX = Math.Clamp(fluidX, 1, _fluidSizeX - 2);
			fluidY = Math.Clamp(fluidY, 1, _fluidSizeY - 2);

			if (_activeButton == MouseButton.Left)
			{
				// Add density in a small area
				for (int i = -2; i <= 2; i++)
				{
					for (int j = -2; j <= 2; j++)
					{
						_fluid.AddDensity(fluidX + i, fluidY + j, 10.0f);
					}
				}
			}
			else if (_activeButton == MouseButton.Right)
			{
				// Add velocity
				_fluid.AddVelocity(fluidX, fluidY, delta.X * 0.5f, -delta.Y * 0.5f);
			}

			_lastMousePos = mousePos;
		}
	}

	protected override void OnKeyDown(KeyboardKeyEventArgs e)
	{
		base.OnKeyDown(e);

		if (e.Key == Keys.Escape)
			Close();
		else if (e.Key == Keys.R)
		{
			_fluid = new FluidSimulation(_fluidSizeX, _fluidSizeY);
			Console.WriteLine("Simulation reset!");
		}
	}

	protected override void OnResize(ResizeEventArgs e)
	{
		base.OnResize(e);
		GL.Viewport(0, 0, e.Width, e.Height);
	}

	protected override void OnUnload()
	{
		GL.DeleteBuffer(_vertexBufferObject);
		GL.DeleteBuffer(_elementBufferObject);
		GL.DeleteVertexArray(_vertexArrayObject);
		GL.DeleteProgram(_shaderProgram);
		GL.DeleteTexture(_texture);

		base.OnUnload();
	}
}