package interpolation;

import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.Insets;
import java.awt.Paint;
import java.awt.RenderingHints;
import java.awt.Toolkit;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;

import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeListener;

import gui.SingleColorChooser;
import gui.TransferableUtils;
import kussmaulUtils.ImageTools;

public class ColorInterpolator extends GraphInterpolator {

	private static final long serialVersionUID = -1873283247289277395L;

	private static int gradPadding = 15;
	
	private ArrayList<Keyframe<Color>> keyframes;
	private Keyframe<Color> selectedFrame;

	private JPanel colorKeyframesPanel;
	
	private JButton manualButton;
	private Color manualColor;

	public ColorInterpolator(Color c0, Color c1, GetSet gs, ChangeListener... listeners) {
		super(gs, listeners);
		keyframes = new ArrayList<Keyframe<Color>>();
		keyframes.add(new Keyframe<Color>(0f, c0));
		keyframes.add(new Keyframe<Color>(1f, c1));
		manualColor = c0;

		initializeComponents();
		//addActionListeners();

		refresh();
		
		gbc.insets = new Insets(5, 5, 5, 5);
		add(colorKeyframesPanel, gbc);

		pack();
	}

	private void initializeComponents() {
		colorKeyframesPanel = new JPanel(); 
		
		manualButton = new JButton("Select color");
		manualButton.addActionListener(ae -> {
			SingleColorChooser.showDialog(manualColor, ce -> {manualColor = SingleColorChooser.getColor(); fireChangeEvent();});
		});
		createMenuBar();
	}
	
	@SuppressWarnings("unchecked")
	private void createMenuBar() {
		JMenuItem copy = new JMenuItem("Copy");
		copy.addActionListener(ae -> TransferableUtils.copyObject(Keyframe.deepCopy(keyframes)));
		copy.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, ActionEvent.META_MASK));
		
		JMenuItem paste = new JMenuItem("Paste");
		paste.addActionListener(ae -> {
			Transferable t = Toolkit.getDefaultToolkit().getSystemClipboard().getContents(this);
			try {
				ArrayList<Keyframe<Serializable>> data = (ArrayList<Keyframe<Serializable>>) t.getTransferData(TransferableUtils.objectDataFlavor);
				if(data.isEmpty())
					return;
				//Yes this is ugly. There is really no better way of doing it though. Forgive me
				if(data.get(0).getValue() instanceof Color)
					keyframes = (ArrayList<Keyframe<Color>>) (Object) data;
				refresh();
			} catch (UnsupportedFlavorException | IOException e1) {
				System.err.println("Invalid data copied");
			}
		});
		paste.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_V, ActionEvent.META_MASK));
		
		JMenuBar bar = new JMenuBar();
		JMenu edit = new JMenu("Edit");
		edit.add(copy);
		edit.add(paste);
		bar.add(edit);
		setJMenuBar(bar);
	}
	
	@Override
	protected void graphDragged(float x, float y, boolean rightClick) {
		selectedFrame.setTime(Math.min(1f, Math.max(0f, x)));
		refresh();
	}
	
	@Override
	public boolean isKeyframable() {
		return true;
	}

	@Override
	public Object getAnimationValue(float time) {
		if(time <= keyframes.get(0).getTime())
			return keyframes.get(0).getValue();
		if(time >= keyframes.get(keyframes.size()-1).getTime() || keyframes.size() == 1)
			return keyframes.get(keyframes.size()-1).getValue();

		for(int i = 0; i < keyframes.size()-1; i++) {
			if(keyframes.get(i).getTime() <= time && keyframes.get(i + 1).getTime() > time) {
				float distTo0 = time - keyframes.get(i).getTime();
				float distTo1 = keyframes.get(i + 1).getTime() - time;
				float sum = distTo0 + distTo1;
				return new Color(ImageTools.gradientRGB(keyframes.get(i+1).getValue().getRGB(), keyframes.get(i).getValue().getRGB(), distTo0 / sum)); 
			}
		}
		return Color.white;
	}
	
	@Override
	public Object getStaticValue() {
		return manualColor;
	}

	@Override
	public String getInstructions() {
		return "Create a gradient for the variable to traverse over time. Click and drag to move a keyframe, right click to create a new keyframe.";
	}

	@Override
	public JComponent getManualController() {
		return manualButton;
	}

	private void refresh() {
		Collections.sort(keyframes);
		refreshColorKeyframePanelPanel(false, false);
		animationButton.repaint();
		repaint();
		fireChangeEvent();
	}

	private void refreshColorKeyframePanelPanel(boolean add, boolean sub) {

		colorKeyframesPanel.removeAll();
		colorKeyframesPanel.setLayout(new GridLayout(keyframes.size(), 1));

		for(int i = 0; i < keyframes.size(); i++) {
			ColorKeyframePanel p = new ColorKeyframePanel(keyframes.get(i));
			p.setCloseable(keyframes.size() != 1);
			colorKeyframesPanel.add(p);
		}
		if(!(add||sub))
			colorKeyframesPanel.revalidate();
		else
			SwingUtilities.invokeLater(() -> {
				setSize(getWidth(), getHeight() + (add ? 1 : -1) * colorKeyframesPanel.getComponent(0).getPreferredSize().height);
			});
	}

	private void addKeyframe(Keyframe<Color> key) {
		keyframes.add(key);
		refreshColorKeyframePanelPanel(true, false);
		refresh();
	}

	private void removeKeyframe(Keyframe<Color> key) {
		keyframes.remove(key);
		refreshColorKeyframePanelPanel(false, true);
		refresh();
	}

	private Keyframe<Color> getNearestKeyframe(float time) {

		if (time <= keyframes.get(0).getTime())
			return keyframes.get(0);
		if (time >= keyframes.get(keyframes.size()-1).getTime())
			return keyframes.get(keyframes.size()-1);

		int bestIdx = 0;

		for(int i = 1; i < keyframes.size(); i++) {

			if(Math.abs(keyframes.get(i).getTime() - time) > Math.abs(keyframes.get(bestIdx).getTime() - time))
				break;
			else bestIdx = i;
		}
		return keyframes.get(bestIdx);
	}

	class ColorKeyframePanel extends JPanel {

		private static final long serialVersionUID = -329577250085593950L;
		private JButton changeButton;
		private JButton deleteButton;
		private Keyframe<Color> key;

		public ColorKeyframePanel(Keyframe<Color> keyframe) {
			super(new BorderLayout());
			this.key = keyframe;
			
			changeButton = new JButton("change color");
			deleteButton = new JButton("×");
			add(changeButton, BorderLayout.WEST);
			add(deleteButton, BorderLayout.EAST);

			deleteButton.addActionListener(ae -> {
				if(keyframes.size() > 1) {
					removeKeyframe(keyframe);
				}
			});

			changeButton.addActionListener(ae -> {
				SingleColorChooser.showDialog(key.getValue(), ce -> {keyframe.setValue(SingleColorChooser.getColor()); refresh();});
//				Color c = JColorChooser.showDialog(null, "Change keyframe color", keyframe.getValue());
//				if(c != null) {
//					keyframe.setValue(c);
//					refresh();
//				}
			});
		}
		
		@Override
		public void paintComponent(Graphics g2) {
			Graphics2D g = (Graphics2D) g2;
			
			g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
			
			int padding = 0;
			int osxBottomFix = System.getProperty("os.name").toLowerCase().contains("mac") ? 3 : 0;
			
			g.setColor(key.getValue());
			g.fillRoundRect(padding, padding, getWidth()-2*padding, getHeight()-2*padding-osxBottomFix, 10, 10);
			g.setColor(key.getValue().darker());
			g.drawRoundRect(padding, padding, getWidth()-2*padding, getHeight()-2*padding-osxBottomFix, 10, 10);
		}

		public void setCloseable(boolean closeable) {
			deleteButton.setEnabled(closeable);
		}
	}

	@Override
	public Dimension getGraphSize() {
		return new Dimension(500, 100);
	}

	@Override
	public void paintGraph(Graphics2D g, int width, int height) {
		g.setColor(keyframes.get(0).getValue());
		g.fillRect(0, gradPadding, (int) (keyframes.get(0).getTime()*width), height-2*gradPadding);
		g.setColor(keyframes.get(keyframes.size()-1).getValue());
		g.fillRect((int) ((keyframes.get(keyframes.size()-1).getTime())*width), gradPadding, width - (int) ((keyframes.get(keyframes.size()-1).getTime())*width), height-2*gradPadding);

		Paint origP = g.getPaint();

		for(int i = 0; i < keyframes.size()-1; i++) {

			Keyframe<Color> k0 = keyframes.get(i), k1 = keyframes.get(i+1);

			g.setPaint(new GradientPaint(k0.getTime()*width, 0, k0.getValue(), k1.getTime()*width, 0, k1.getValue()));
			g.fillRect((int) (k0.getTime()*width), gradPadding-3, (int) (k1.getTime()*width-k0.getTime()*width), height-2*(gradPadding-3));
		}
		g.setPaint(origP);

		g.setStroke(new BasicStroke(3)); //Draw red bar at each keyframe
		g.setColor(Color.BLACK);//new Color(255, 0, 0, 32));
		for(int i = 0; i < keyframes.size(); i++)
			g.drawLine((int) (keyframes.get(i).getTime() * width), 0, (int) (keyframes.get(i).getTime() * width), height);
		g.setStroke(new BasicStroke(1)); //Draw red bar at each keyframe
		g.setColor(Color.WHITE);//new Color(255, 0, 0, 32));
		for(int i = 0; i < keyframes.size(); i++)
			g.drawLine((int) (keyframes.get(i).getTime() * width), 0, (int) (keyframes.get(i).getTime() * width), height);
	}

	@Override
	public void paintButton(Graphics2D g, int width, int height) {
		g.setColor(keyframes.get(0).getValue());
		g.fillRect(0, gradPadding, (int) (keyframes.get(0).getTime()*width), height-2*gradPadding);
		g.setColor(keyframes.get(keyframes.size()-1).getValue());
		g.fillRect((int) ((keyframes.get(keyframes.size()-1).getTime())*width), gradPadding, width - (int) ((keyframes.get(keyframes.size()-1).getTime())*width), height-2*gradPadding);

		
		for(int i = 0; i < keyframes.size()-1; i++) {

			Keyframe<Color> k0 = keyframes.get(i), k1 = keyframes.get(i+1);

			g.setPaint(new GradientPaint(k0.getTime()*width, 0, k0.getValue(), k1.getTime()*width, 0, k1.getValue()));
			g.fillRect((int) (k0.getTime()*width), gradPadding-3, (int) (k1.getTime()*width-k0.getTime()*width), height-2*(gradPadding-3));
		}
		
	}
	
	@Override
	protected void graphPressed(float x, float y, boolean rightClick) {

		if(rightClick) {
			selectedFrame = new Keyframe<Color>(x, (Color) getAnimationValue(x));
			addKeyframe(selectedFrame);
		}
		else 
			selectedFrame = getNearestKeyframe(x);
		selectedFrame.setTime(x);

		refresh();
		fireChangeEvent();
	}
}