package org.geogebra.desktop.gui.color;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.util.ArrayList;

import javax.swing.BorderFactory;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JColorChooser;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.border.Border;
import javax.swing.colorchooser.AbstractColorChooserPanel;

import org.geogebra.common.awt.GColor;
import org.geogebra.common.main.GeoGebraColorConstants;
import org.geogebra.common.main.Localization;
import org.geogebra.desktop.awt.GColorD;
import org.geogebra.desktop.gui.util.LayoutUtil;
import org.geogebra.desktop.main.AppD;
import org.geogebra.desktop.util.GuiResourcesD;

/**
 * A color swatch chooser panel for GeoGebra.
 * 
 * @author G. Sturr
 */
public class GeoGebraColorChooserPanel extends AbstractColorChooserPanel {

	private static final long serialVersionUID = 1L;

	protected AppD app;
	protected GeoGebraColorChooser enclosingChooser;
	protected GeoGebraColorChooserPanel myChooser;

	protected MainSwatchPanel mainSwatchPanel;
	protected RecentSwatchPanel recentSwatchPanel;
	protected PrimarySwatchPanel primarySwatchPanel;
	protected CustomSwatchPanel customSwatchPanel;

	protected SwatchListener swatchListener;
	protected ArrayList<SwatchPanel> swatchPanelList;

	private JButton btnCustomColor;
	private JLabel lblRecent;
	private JLabel lblCustom;
	private JPanel recentPanel;
	private JPanel customPanel;

	protected static final int largeSwatchSize = 16;
	protected static final int smallSwatchSize = 14;

	/**
	 * Constructs a color chooser panel
	 * 
	 * @param app application
	 */
	public GeoGebraColorChooserPanel(AppD app) {
		super();
		this.app = app;
		this.myChooser = this;
	}

	@Override
	public String getDisplayName() {
		return UIManager.getString("ColorChooser.swatchesNameText");
	}

	@Override
	public int getMnemonic() {
		// return getInt("ColorChooser.swatchesMnemonic", -1);
		return -1;
	}

	@Override
	public int getDisplayedMnemonicIndex() {
		// return getInt("ColorChooser.swatchesDisplayedMnemonicIndex", -1);
		return -1;
	}

	@Override
	public Icon getSmallDisplayIcon() {
		return null;
	}

	@Override
	public Icon getLargeDisplayIcon() {
		return null;
	}

	@Override
	public void installChooserPanel(JColorChooser enclosingChooser) {
		super.installChooserPanel(enclosingChooser);
		this.enclosingChooser = (GeoGebraColorChooser) enclosingChooser;
	}

	@Override
	protected void buildChooser() {

		// create the swatch panels and other GUI elements
		createGUIElements();

		// create a GridBagLayout
		GridBagLayout gb = new GridBagLayout();
		GridBagConstraints gbc = new GridBagConstraints();
		gbc.fill = GridBagConstraints.NONE;
		gbc.anchor = GridBagConstraints.NORTHWEST;
		gbc.weightx = 1.0;
		gbc.insets = new Insets(0, 0, 0, 10);

		// create a panel to hold all of our GUI
		JPanel mainPanel = new JPanel(gb);

		// add the primary swatch panel on the far left
		gbc.gridheight = 2;
		gbc.gridx = 0;
		gbc.gridy = 0;
		mainPanel.add(primarySwatchPanel, gbc);

		// add the main swatch panel in the middle
		gbc.gridheight = 2;
		gbc.gridx = 1;
		gbc.gridy = 0;
		mainPanel.add(mainSwatchPanel, gbc);

		// stack the recent panel, custom panel and RGB button on the far right
		gbc.insets = new Insets(0, 0, 0, 0);
		gbc.weighty = 0;
		gbc.gridheight = 1;
		gbc.gridx = 2;
		gbc.gridy = 0;
		mainPanel.add(recentPanel, gbc);

		gbc.insets = new Insets(10, 0, 0, 0);
		gbc.weighty = 1;
		gbc.gridx = 2;
		gbc.gridy = 1;
		mainPanel.add(customPanel, gbc);

		add(mainPanel);

	}

	private void createGUIElements() {

		// create the swatch panels
		mainSwatchPanel = new MainSwatchPanel();
		recentSwatchPanel = new RecentSwatchPanel();
		primarySwatchPanel = new PrimarySwatchPanel();
		customSwatchPanel = new CustomSwatchPanel();

		swatchPanelList = new ArrayList<>();
		swatchPanelList.add(mainSwatchPanel);
		swatchPanelList.add(primarySwatchPanel);
		swatchPanelList.add(recentSwatchPanel);
		swatchPanelList.add(customSwatchPanel);

		// create a mouse listener and register it with each swatch panel
		swatchListener = new SwatchListener();
		for (SwatchPanel sp : swatchPanelList) {
			sp.addMouseListener(swatchListener);
		}

		// add borders to the swatch panels
		Border border = BorderFactory.createCompoundBorder(
				BorderFactory.createLineBorder(Color.gray, 1),
				BorderFactory.createLineBorder(Color.white, 1));
		for (SwatchPanel sp : swatchPanelList) {
			sp.setBorder(border);
		}

		// create a panel with label to contain the recent swatch panel
		recentPanel = new JPanel(new BorderLayout());
		lblRecent = new JLabel();
		lblRecent.setLabelFor(recentSwatchPanel);
		recentPanel.add(lblRecent, BorderLayout.NORTH);
		recentPanel.add(recentSwatchPanel, BorderLayout.CENTER);
		recentPanel.setMaximumSize(getPreferredSize());

		// create a button to open a RGB color chooser for custom colors
		btnCustomColor = new JButton(app.getImageIcon(GuiResourcesD.LIST_ADD));
		btnCustomColor.addActionListener(new CustomButtonActionListener());
		btnCustomColor.setPreferredSize(new Dimension(24, 18));
		btnCustomColor.setFocusPainted(false);

		// create a panel with label to contain the custom swatch panel
		lblCustom = new JLabel();
		lblCustom.setLabelFor(customSwatchPanel);
		customPanel = new JPanel(new BorderLayout());
		customPanel.add(lblCustom, BorderLayout.NORTH);
		customPanel.add(customSwatchPanel, BorderLayout.CENTER);
		customPanel.add(LayoutUtil.flowPanel(0, 2, 0, btnCustomColor),
				BorderLayout.SOUTH);
		customPanel.setMaximumSize(getPreferredSize());

		// set the labels
		setLabels();

	}

	@Override
	public void uninstallChooserPanel(JColorChooser enclosingChooser) {
		super.uninstallChooserPanel(enclosingChooser);
		for (SwatchPanel sp : swatchPanelList) {
			sp.removeMouseListener(swatchListener);
		}
		swatchListener = null;
		removeAll(); // strip out all the sub-components
	}

	@Override
	public void updateChooser() {
		setSwatchPanelSelection(getColorSelectionModel().getSelectedColor());

	}

	/**
	 * Sets the visual feedback for the swatch panels so that the appropriate
	 * panel shows the current selection.
	 * 
	 * @param color color
	 */
	public void setSwatchPanelSelection(Color color) {

		// clear visual feedback for swatch selection in the swatch panels
		for (SwatchPanel panel : swatchPanelList) {
			panel.setSelectionFromLocation(-1, -1);
		}

		// exit if the chooser null selection flag is set
		// (JColorChooser doesn't handle null color ... see class
		// GeoGebraColorChooser for workaround)
		if (enclosingChooser != null && enclosingChooser.isNullSelection()) {
			return;
		}

		// set the selected swatch visual feedback in the appropriate panel
		if (!primarySwatchPanel.setSelectionFromColor(color)
				&& !mainSwatchPanel.setSelectionFromColor(color)) {
			customSwatchPanel.setSelectionFromColor(color);
		}
	}

	/**
	 * Set localized strings.
	 */
	public void setLabels() {
		Localization loc = app.getLocalization();
		btnCustomColor.setToolTipText(loc.getMenu("AddCustomColor"));
		lblCustom.setText(loc.getMenu("Other") + ":");
		lblRecent.setText(loc.getMenu("RecentColor") + ":");
	}

	/**
	 * Update font type/size
	 */
	public void updateFonts() {
		Font font = app.getPlainFont();
		btnCustomColor.setFont(font);
		lblCustom.setFont(font);
		lblRecent.setFont(font);
	}

	/**
	 * MouseListener for the swatch panels.
	 * 
	 */
	class SwatchListener extends MouseAdapter {
		@Override
		public void mousePressed(MouseEvent e) {

			if (e.getSource() instanceof SwatchPanel) {

				SwatchPanel mySwatchPanel = (SwatchPanel) e.getSource();

				// exit if the mouse is not over a color swatch
				if (!mySwatchPanel.isSwatchLocation(e.getX(), e.getY())) {
					return;
				}

				// set the color selection to the color of the cell the mouse is
				// above
				Color color = mySwatchPanel.getColorForLocation(e.getX(),
						e.getY());
				getColorSelectionModel().setSelectedColor(color);

				// update the the recent swatch panel
				if (mySwatchPanel != recentSwatchPanel) {
					recentSwatchPanel.setMostRecentColor(color);
				}
			}
		}
	}

	/**
	 * Action listener for the custom color button. Creates and shows a color
	 * chooser dialog with a RGB color chooser panel.
	 * 
	 */
	class CustomButtonActionListener implements ActionListener {

		JColorChooser chooser;
		ActionListener okActionListener =
				(e) -> customSwatchPanel.addCustomColor(chooser.getColor());

		@Override
		public void actionPerformed(ActionEvent arg0) {

			chooser = new JColorChooser();
			chooser.setColor(myChooser.getColorFromModel());
			DefaultRGBChooserPanel rgb = new DefaultRGBChooserPanel(app);
			AbstractColorChooserPanel[] panels = { rgb };
			chooser.setChooserPanels(panels);
			chooser.setPreviewPanel(rgb.getPreview());

			// show the chooser dialog
			JDialog dialog = JColorChooser.createDialog(app.getMainComponent(),
					app.getLocalization().getMenu("ChooseColor"), true, chooser,
					okActionListener, null);
			dialog.setVisible(true);
		}

	}

	/**********************************************************
	 * 
	 * Base class for all swatch panels.
	 * 
	 **********************************************************/
	class SwatchPanel extends JPanel {

		private static final long serialVersionUID = 1L;

		protected GColor[] colors;

		protected Dimension swatchSize = new Dimension(largeSwatchSize,
				largeSwatchSize);
		protected Dimension gap = new Dimension(1, 1);
		protected Dimension numSwatches;
		protected Dimension selectedSwatch = new Dimension(-1, -1);
		protected Dimension hoverSwatch = new Dimension(-1, -1);
		protected Dimension prevHoverSwatch = new Dimension(-1, -1);

		protected int swatchCount = 0;

		public SwatchPanel() {

			initValues();
			initColors();
			initSwatchCount();
			setToolTipText(""); // register for events
			setOpaque(true);
			setBackground(Color.white);
			setRequestFocusEnabled(false);
			addMouseListener(new SwatchMouseListener());
			addMouseMotionListener(new SwatchMouseMotionListener());

			setMaximumSize(getPreferredSize());
		}

		protected void initValues() {
			// overridden
		}

		protected void initColors() {
			// overridden
		}

		protected void initSwatchCount() {
			swatchCount = colors.length;
		}

		class SwatchMouseMotionListener extends MouseMotionAdapter {
			@Override
			public void mouseMoved(MouseEvent e) {
				updateHoverSwatch(e.getX(), e.getY());
			}
		}

		class SwatchMouseListener extends MouseAdapter {
			@Override
			public void mouseExited(MouseEvent e) {
				updateHoverSwatch(-1, -1);
			}
		}

		protected void updateHoverSwatch(int x, int y) {

			prevHoverSwatch.width = hoverSwatch.width;
			prevHoverSwatch.height = hoverSwatch.height;
			setCellFromLocation(x, y, hoverSwatch);

			if (prevHoverSwatch.width != hoverSwatch.width
					|| prevHoverSwatch.height != hoverSwatch.height) {
				repaint();
			}
		}

		@Override
		public void paintComponent(Graphics g) {
			super.paintComponent(g);
			Graphics2D g2d = (Graphics2D) g;

			// g2d.setStroke(new BasicStroke(2));

			Insets insets = getInsets();
			g2d.setColor(getBackground());
			g2d.fillRect(0, 0, getWidth(), getHeight());

			for (int row = 0; row < numSwatches.height; row++) {
				for (int column = 0; column < numSwatches.width; column++) {

					g2d.setColor(getColorForCell(column, row));
					int x;
					if ((!this.getComponentOrientation().isLeftToRight())
							&& (this instanceof RecentSwatchPanel)) {
						x = (numSwatches.width - column - 1)
								* (swatchSize.width + gap.width) + insets.left;
					} else {
						x = column * (swatchSize.width + gap.width)
								+ insets.left;
					}
					int y = row * (swatchSize.height + gap.height) + insets.top;

					g2d.fill3DRect(x + 1, y + 1, swatchSize.width - 1,
							swatchSize.height - 1, true);

					if (isSwatchCell(column, row)) {

						if (selectedSwatch != null
								&& row == selectedSwatch.height
								&& column == selectedSwatch.width) {
							g2d.setColor(Color.DARK_GRAY);
							g2d.drawRect(x, y, swatchSize.width,
									swatchSize.height);

							if (app != null) {
								g2d.drawImage(
										app.getImageIcon(
												GuiResourcesD.COLOR_CHOOSER_CHECK)
												.getImage(),
										x + 3, y + 3, null);
							}
						}

						if (hoverSwatch != null && row == hoverSwatch.height
								&& column == hoverSwatch.width) {
							g2d.setColor(Color.DARK_GRAY);
							g2d.drawRect(x, y, swatchSize.width,
									swatchSize.height);
						}
					}

				}
			}
		}

		@Override
		public Dimension getPreferredSize() {
			Insets insets = getInsets();
			int x = numSwatches.width * (swatchSize.width + gap.width)
					+ insets.left + insets.right;
			int y = numSwatches.height * (swatchSize.height + gap.height)
					+ insets.top + insets.bottom;
			return new Dimension(x, y);
		}

		@Override
		public String getToolTipText(MouseEvent e) {

			if (!isSwatchLocation(e.getX(), e.getY())) {
				return "";
			}

			Color color = getColorForLocation(e.getX(), e.getY());
			String name = GeoGebraColorConstants.getGeogebraColorName(app,
					GColor.newColor(color.getRed(), color.getGreen(),
							color.getBlue(), color.getAlpha()));
			String rgbStr = color.getRed() + ", " + color.getGreen() + ", "
					+ color.getBlue();
			if (name != null) {
				return name + "  " + rgbStr;
			}
			return rgbStr;
		}

		public Color getColorForLocation(int x, int y) {
			int column;
			if ((!this.getComponentOrientation().isLeftToRight())
					&& (this instanceof RecentSwatchPanel)) {
				column = numSwatches.width - x / (swatchSize.width + gap.width)
						- 1;
			} else {
				column = x / (swatchSize.width + gap.width);
			}
			int row = y / (swatchSize.height + gap.height);

			return getColorForCell(column, row);
		}

		public void setCellFromLocation(int x, int y, Dimension p) {
			if (x == -1 || y == -1) {
				p.width = -1;
				p.height = -1;
				return;
			}

			int column;
			if ((!this.getComponentOrientation().isLeftToRight())
					&& (this instanceof RecentSwatchPanel)) {
				column = numSwatches.width - x / (swatchSize.width + gap.width)
						- 1;
			} else {
				column = x / (swatchSize.width + gap.width);
			}
			int row = y / (swatchSize.height + gap.height);
			p.width = column;
			p.height = row;
		}

		private Color getColorForCell(int column, int row) {
			if ((row * numSwatches.width) + column < colors.length) {
				return GColorD.getAwtColor(
						colors[(row * numSwatches.width) + column]);
			}
			return Color.WHITE;
		}

		private boolean getCellForColor(Color color, Dimension cell) {

			for (int i = 0; i < colors.length; i++) {
				if (color.getRed() == colors[i].getRed()
						&& color.getGreen() == colors[i].getGreen()
						&& color.getBlue() == colors[i].getBlue()) {
					cell.height = i / numSwatches.width;
					cell.width = i % numSwatches.width;
					return true;
				}

			}
			cell.height = -1;
			cell.width = -1;
			return false;
		}

		protected boolean setSelectionFromColor(Color color) {
			if (selectedSwatch == null) {
				selectedSwatch = new Dimension();
			}

			boolean success;

			if (color == null) {
				selectedSwatch.width = -1;
				selectedSwatch.height = -1;
				success = true;
			} else {
				success = getCellForColor(color, selectedSwatch);
			}

			repaint();
			return success;
		}

		protected void setSelectionFromLocation(int xLoc, int yLoc) {
			if (xLoc < 0 || yLoc < 0) {
				selectedSwatch.width = -1;
				selectedSwatch.height = -1;
			} else {
				setCellFromLocation(xLoc, yLoc, selectedSwatch);
			}
			this.repaint();
		}

		Dimension cell = new Dimension();

		protected boolean isSwatchLocation(int xLoc, int yLoc) {
			setCellFromLocation(xLoc, yLoc, cell);
			return isSwatchCell(cell.width, cell.height);
		}

		protected boolean isSwatchCell(int column, int row) {
			int count = row * numSwatches.width + column + 1;
			return count <= swatchCount;
		}

	}

	/*******************************************************
	 * Recent swatch panel. This holds recently selected colors.
	 * 
	 */
	class RecentSwatchPanel extends SwatchPanel {

		private static final long serialVersionUID = 1L;

		@Override
		protected void initValues() {
			numSwatches = new Dimension(6, 4);
			swatchSize = new Dimension(smallSwatchSize, smallSwatchSize);
		}

		@Override
		protected void initColors() {
			Color defaultRecentColor = UIManager
					.getColor("ColorChooser.swatchesDefaultRecentColor");
			int numColors = numSwatches.width * numSwatches.height;

			colors = new GColor[numColors];
			for (int i = 0; i < numColors; i++) {
				colors[i] = GColorD.newColor(defaultRecentColor);
			}
		}

		@Override
		protected void initSwatchCount() {
			swatchCount = 0;
		}

		public void setMostRecentColor(Color c) {

			System.arraycopy(colors, 0, colors, 1, colors.length - 1);
			colors[0] = GColorD.newColor(c);
			if (swatchCount < swatchSize.width * swatchSize.height) {
				swatchCount++;
			}

			repaint();
		}

	}

	/*******************************************************
	 * Custom swatch panel. This holds user defined RGB colors.
	 * 
	 */
	class CustomSwatchPanel extends SwatchPanel {

		private static final long serialVersionUID = 1L;

		@Override
		protected void initValues() {
			numSwatches = new Dimension(5, 2);
			// swatchSize = new Dimension(smallSwatchSize,smallSwatchSize);
		}

		@Override
		protected void initSwatchCount() {
			swatchCount = 0;
		}

		@Override
		protected void initColors() {
			Color defaultRecentColor = UIManager
					.getColor("ColorChooser.swatchesDefaultRecentColor");
			int numColors = numSwatches.width * numSwatches.height;

			colors = new GColor[numColors];
			for (int i = 0; i < numColors; i++) {
				colors[i] = GColorD.newColor(defaultRecentColor);
			}
		}

		public void addCustomColor(Color color) {

			if (color == null) {
				selectedSwatch.width = -1;
				selectedSwatch.height = -1;
				repaint();
			} else {
				System.arraycopy(colors, 0, colors, 1, colors.length - 1);
				colors[0] = GColorD.newColor(color);
				myChooser.setSwatchPanelSelection(color);
				repaint();

				if (swatchCount < swatchSize.width * swatchSize.height) {
					swatchCount++;
				}

				getColorSelectionModel().setSelectedColor(color);
				primarySwatchPanel.setSelectionFromLocation(-1, -1);
				mainSwatchPanel.setSelectionFromLocation(-1, -1);
				recentSwatchPanel.setMostRecentColor(color);

			}

		}

		@Override
		protected boolean setSelectionFromColor(Color color) {
			if (!super.setSelectionFromColor(color)) {
				addCustomColor(color);
			}
			return true;
		}

	}

	/*******************************************************
	 * Primary swatch panel. This holds primary (and near primary) colors and
	 * grays.
	 * 
	 */
	class PrimarySwatchPanel extends SwatchPanel {

		private static final long serialVersionUID = 1L;

		@Override
		protected void initValues() {
			numSwatches = new Dimension(2, 9);
		}

		@Override
		protected void initColors() {
			colors = GeoGebraColorConstants.getPrimarySwatchColors();
		}

	}

	/********************************************************
	 * Main swatch panel. This panel sits in the middle and allows a set of
	 * colors to be picked which will move to the recent swatch panel.
	 */
	class MainSwatchPanel extends SwatchPanel {

		private static final long serialVersionUID = 1L;

		@Override
		protected void initValues() {
			numSwatches = new Dimension(8, 9);
		}

		@Override
		protected void initColors() {
			colors = GeoGebraColorConstants.getMainColorSwatchColors();
		}

	}

}
