package io.github.rowak.nanoleafdesktop.ui.dialog.colorpicker;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

import javax.swing.Box;
import javax.swing.JPanel;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

public class ColorWheel extends JPanel
{
	private int width, height;
	private WheelTracker tracker;
	
	public ColorWheel(int width, int height)
	{
		this.width = width;
		this.height = height;
		add(Box.createRigidArea(new Dimension(width, height)));
		setBackground(Color.DARK_GRAY);
		tracker = new WheelTracker();
	}
	
	public Color getColor()
	{
		return getColor(tracker.getX(), tracker.getY());
	}
	
	public void setColor(Color color)
	{
		tracker.setColor(color);
	}
	
	public void addChangeListener(ChangeListener listener)
	{
	    listenerList.add(ChangeListener.class, listener);
	}

	public void removeChangeListener(ChangeListener listener)
	{
	    listenerList.remove(ChangeListener.class, listener);
	}

	public ChangeListener[] getChangeListeners()
	{
	    return listenerList.getListeners(ChangeListener.class);
	}

	protected void fireChangeListeners()
	{
	    ChangeEvent event = new ChangeEvent(this);
	    for (ChangeListener listener : getChangeListeners())
	    {
	        listener.stateChanged(event);
	    }
	}
	
	@Override
	public void paintComponent(Graphics g)
	{
		super.paintComponent(g);
		
		paintWheel(g);		
		paintWheelBorder(g);
		tracker.paint(g);
	}
	
	public void paintWheel(Graphics g)
	{
		/*
		 * Color wheel algorithm source:
		 * https://rosettacode.org/wiki/Color_wheel
		 */
		float centerX = width/2;
		float centerY = height/2;
		float radius = centerX;
		if (centerY < radius)
		{
			radius = centerY;
		}
		for (int y = 0; y < height; y++)
		{
			float dy = y - centerY;
			for (int x = 0; x < width; x++)
			{
				float dx = x - centerX;
				float dist = (float)Math.sqrt(dx*dx + dy*dy);
				if (dist <= radius)
				{
					float hue = (float)(((Math.atan2(dx, dy) / Math.PI) + 1f) / 2f);
					Color rgb = new Color(Color.HSBtoRGB(hue, 1f, 1f));
					g.setColor(rgb);
					g.drawRect(x, y, 1, 1);
				}
				else
				{
					g.setColor(getBackground());
					g.drawRect(x, y, 1, 1);
				}
			}
		}
	}
	
	public void paintWheelBorder(Graphics g)
	{
		float centerX = width/2;
		float centerY = height/2;
		float radius = centerX;
		for (int y = 0; y < height; y++)
		{
			float dy = y - centerY;
			for (int x = 0; x < width; x++)
			{
				float dx = x - centerX;
				int dist = (int)Math.ceil(Math.sqrt(dx*dx + dy*dy));
				if (dist == radius)
				{
					g.setColor(Color.GRAY);
					g.drawRect(x, y, 1, 1);
				}
			}
		}
	}
	
	private Color getColor(int x, int y)
	{
		float centerX = width/2;
		float centerY = height/2;
		float radius = centerX;
		if (centerY < radius)
		{
			radius = centerY;
		}
		for (int circy = 0; circy < height; circy++)
		{
			float dy = circy - centerY;
			for (int circx = 0; circx < width; circx++)
			{
				if (circx == x && circy == y)
				{
					float dx = circx - centerX;
					float dist = (float)Math.sqrt(dx*dx + dy*dy);
					if (dist <= radius)
					{
						float hue = (float)(((Math.atan2(dx, dy) / Math.PI) + 1f) / 2f);
						return new Color(Color.HSBtoRGB(hue, 1f, 1f));
					}
				}
			}
		}
		return null;
	}
	
	private class WheelTracker
	{
		private int x, y;
		private Color borderColor, trackerColor;
		
		public WheelTracker()
		{
			x = width/2;
			y = width/2;
			borderColor = Color.DARK_GRAY;
			trackerColor = Color.WHITE;

			addMouseListener(new MouseAdapter()
			{
				@Override
				public void mousePressed(MouseEvent e)
				{
					updatePosition(e.getX(), e.getY());
				}
			});
			addMouseMotionListener(new MouseAdapter()
			{
				@Override
				public void mouseDragged(MouseEvent e)
				{
					updatePosition(e.getX(), e.getY());
				}
			});
		}
		
		private void updatePosition(int x, int y)
		{
			int centerX = ColorWheel.this.width/2;
			int centerY = ColorWheel.this.height/2;
			double radius = ColorWheel.this.width/2;
			double dist = Math.sqrt(Math.pow(x - centerX, 2) +
					Math.pow(y - centerY, 2));
			//double xdist = Math.sqrt(Math.pow(e.getX() - centerX, 2));
			//double ydist = Math.sqrt(Math.pow(e.getY() - centerY, 2));
			if (dist <= radius)
			{
				this.x = x;
				this.y = y;
				fireChangeListeners();
				repaint();
			}
		}
		
		public int getX()
		{
			return this.x;
		}
		
		public int getY()
		{
			return this.y;
		}
		
		public void setColor(Color color)
		{
			float[] hsb = new float[3];
			hsb = Color.RGBtoHSB(color.getRed(), color.getGreen(),
					color.getBlue(), hsb);
			int hue = (int)(hsb[0]*360);
			float centerX = width/2;
			float centerY = height/2;
			float radius = centerX;
			if (centerY < radius)
			{
				radius = centerY;
			}
			for (int y = 0; y < height; y++)
			{
				float dy = y - centerY;
				for (int x = 0; x < width; x++)
				{
					float dx = x - centerX;
					float dist = (float)Math.sqrt(dx*dx + dy*dy);
					if (dist <= radius)
					{
						int hueX = (int)((((Math.atan2(dx, dy) / Math.PI) + 1f) / 2f)*360);
						if (hueX == hue)
						{
							this.x = x;
							this.y = y;
							repaint();
						}
					}
				}
			}
		}
		
		public void paint(Graphics g)
		{
			final int DIAMETER = 20;
			Graphics2D g2d = (Graphics2D)g;
			g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
					RenderingHints.VALUE_ANTIALIAS_ON);
			g.setColor(trackerColor);
			g.fillOval(x - DIAMETER/2, y - DIAMETER/2, DIAMETER, DIAMETER);
			g2d.setStroke(new BasicStroke(3));
			g.setColor(borderColor);
			g.drawOval(x - DIAMETER/2, y - DIAMETER/2, DIAMETER, DIAMETER);
			g2d.setStroke(new BasicStroke(1));
		}
	}
}