/*
 * Copyright (c) 2005-2010 KOM – Multimedia Communications Lab
 *
 * This file is part of PeerfactSim.KOM.
 *
 * PeerfactSim.KOM 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
 * any later version.
 *
 * PeerfactSim.KOM 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 PeerfactSim.KOM.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

package de.tud.kom.p2psim.impl.topology.views.visualization.world;

import java.awt.AlphaComposite;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;

import javax.swing.JCheckBoxMenuItem;
import javax.swing.JComponent;
import javax.swing.JMenu;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

import de.tud.kom.p2psim.api.common.SimHost;
import de.tud.kom.p2psim.impl.topology.util.PositionVector;
import de.tud.kom.p2psim.impl.topology.views.VisualizationTopologyView.VisNodeInformation;
import de.tud.kom.p2psim.impl.topology.views.VisualizationTopologyView.VisualizationInjector;
import de.tud.kom.p2psim.impl.topology.views.visualization.ui.VisualizationComponent;
import de.tudarmstadt.maki.simonstrator.api.Event;
import de.tudarmstadt.maki.simonstrator.api.EventHandler;
import de.tudarmstadt.maki.simonstrator.api.Host;
import de.tudarmstadt.maki.simonstrator.api.Oracle;
import de.tudarmstadt.maki.simonstrator.api.Time;
import de.tudarmstadt.maki.simonstrator.api.component.ComponentNotAvailableException;
import de.tudarmstadt.maki.simonstrator.api.component.HostComponent;
import de.tudarmstadt.maki.simonstrator.api.component.overlay.NodeInformation;

/**
 * Generic component that visualizes information from nodes implementing the
 * {@link NodeInformation} interface.
 *
 * @author Bjoern Richerzhagen
 * @version 1.0, Jul 9, 2015
 */
public class NodeInfoComponentVis extends JComponent
implements VisualizationComponent {

	protected Collection<NodeVis> nodes = new LinkedList<>();

	private JMenu menu = new JMenu("Node Info");

	protected boolean[] activeLayers = new boolean[0];

	boolean hideInactiveNodes = false;

	private final String name;

	public <T extends HostComponent> NodeInfoComponentVis(
			final Class<T> componentClass) {
		setBounds(0, 0, VisualizationInjector.getWorldX(),
				VisualizationInjector.getWorldY());
		setOpaque(true);
		setVisible(true);
		this.name = componentClass.getSimpleName();
		menu.setText("Info: "+name);

		Event.scheduleWithDelay(1 * Time.MICROSECOND, new EventHandler() {
			@Override
			public void eventOccurred(Object content, int type) {
				for (Host host : Oracle.getAllHosts()) {
					try {
						HostComponent c = host.getComponent(componentClass);
						if (c instanceof NodeInformation) {
							nodes.add(new NodeVis(host, (NodeInformation) c));
						}
					} catch (ComponentNotAvailableException e) {
						// don't care
					}
				}
				initializeMenu();
			}
		}, null, 0);
	}

	/**
	 * Hide rings for inactive nodes.
	 * @param hideOfflineNodes
	 */
	public void setHideInactiveNodes(boolean hideInactiveNodes) {
		this.hideInactiveNodes = hideInactiveNodes;
	}

	@Override
	protected void paintComponent(Graphics g) {
		super.paintComponent(g);
		Graphics2D g2 = (Graphics2D) g;
		g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
				RenderingHints.VALUE_ANTIALIAS_ON);
		boolean first = true;
		for (NodeVis vis : new ArrayList<>(nodes)) {
			vis.draw(g2);
			if (first) {
				first = false;
				vis.drawLegend(g2);
			}
		}
	}

	protected void initializeMenu() {
		for (NodeVis vis : nodes) {
			String[] dimensions = vis.nodeInfo
					.getNodeColorDimensionDescriptions();
			activeLayers = new boolean[dimensions.length];
			for (int dim = 0; dim < dimensions.length; dim++) {
				JCheckBoxMenuItem item = new JCheckBoxMenuItem(
						dimensions[dim]);
				item.setSelected(true);
				item.addChangeListener(new ChangeListenerImpl(dim));
				menu.add(item);
				activeLayers[dim] = true;
			}
			break;
		}
	}

	private class ChangeListenerImpl implements ChangeListener {

		private final int dim;

		public ChangeListenerImpl(int dim) {
			this.dim = dim;
		}

		@Override
		public void stateChanged(ChangeEvent e) {
			activeLayers[dim] = !activeLayers[dim];
		}

	}

	/**
	 * Visualization-fragments for Node-centric visualiation-information.
	 *
	 * @author Bjoern Richerzhagen
	 * @version 1.0, Sep 22, 2013
	 */
	private class NodeVis {

		public final VisNodeInformation visNodeInfo;

		public final NodeInformation nodeInfo;

		private final SimHost host;

		private final PositionVector loc;

		private final Color activeGreen = new Color(0, 200, 0);

		private final Color[] baseColors = { Color.ORANGE, Color.BLUE, Color.RED,
				Color.PINK, Color.GRAY, Color.GREEN, Color.CYAN,
				Color.WHITE };

		private final Color[] tuColors = {
				new Color(93, 133, 195), // 1a
				new Color(80, 182, 149), // 3a
				//				new Color(221,223,72), // 5a
				new Color(248,186,60), // 7a
				new Color(233,80,62), // 9a
				new Color(128, 69, 151), // 11a
				new Color(0, 78, 138), // 1c
				new Color(0, 136, 119), // 3c
				//				new Color(177, 189, 0), // 5c
				new Color(210, 135, 0), // 7c
				new Color(185, 15, 34), // 9c
				new Color(97, 28, 115), // 11c
		};

		private Color[][] colors = null;

		public NodeVis(Host host, NodeInformation nodeInfo) {
			this.nodeInfo = nodeInfo;
			this.host = (SimHost) host;
			this.loc = this.host.getTopologyComponent().getRealPosition();
			this.visNodeInfo = VisualizationInjector.getNodeInformation(host.getId());

			/*
			 * Create per-info-option colors by deriving the color from the base color
			 */
			colors = new Color[nodeInfo.getNodeColorDimensions()][];
			for (int dim = 0; dim < colors.length; dim++) {
				Color baseColor = baseColors[dim];
				int dimensionSize = nodeInfo
						.getNodeColorDescriptions(dim).length;
				colors[dim] = new Color[dimensionSize];
				/*
				 * http://stackoverflow.com/questions/2355157/dynamically-
				 * creating-colors-with-different-brightness
				 */
				//				float hsbVals[] = Color.RGBtoHSB(baseColor.getRed(),
				//						baseColor.getGreen(), baseColor.getBlue(), null);
				for (int i = 0; i < dimensionSize; i++) {
					float hue = i / (float) dimensionSize;
					//					colors[dim][i] = Color.getHSBColor(hue, hsbVals[1],
					//							hsbVals[2]);
											
					colors[dim][i] = getColor(i);
				}
			}

		}

		private Color getColor(int i) {
			if(i < tuColors.length) {
				return tuColors[i];
			}
			else {
				return new Color(255, 0, 0);
			}
		}
		
		/**
		 * Called on one of the nodes to draw global objects such as a legend.
		 * Called before draw.
		 *
		 * @param g2
		 */
		public void drawLegend(Graphics2D g2) {
			String[] dimensions = nodeInfo.getNodeColorDimensionDescriptions();

			int segments = dimensions.length;
			int segmentDegrees = (int) (360 / (double) segments);
			int arcSize = 8;

			g2.setStroke(new BasicStroke(3));
			for (int color = 0; color < segments; color++) {
				if (activeLayers.length <= color || !activeLayers[color]) {
					continue;
				}
				g2.setColor(Color.DARK_GRAY);
				g2.drawArc(10, 20 * (color + 1)+10, 2*arcSize, 2*arcSize, color*segmentDegrees, segmentDegrees);
				String[] colorDescs = nodeInfo.getNodeColorDescriptions(color);
				for (int i = 0; i < colorDescs.length; i++) {
					g2.setColor(colors[color][i]);
					g2.fillRect(40 + i * 90, 20 * (color + 1)+10, 8, 8);
					g2.setColor(Color.DARK_GRAY);
					g2.drawString(colorDescs[i], 50 + i * 90,
							20 * (color + 2));
				}
			}
			g2.setStroke(new BasicStroke(1));
		}

		public void draw(Graphics2D g2) {

			if (hideInactiveNodes && !nodeInfo.isActive()) {
				visNodeInfo.disableClickListener = true;
				return;
			}
			visNodeInfo.disableClickListener = false;

			Point center = loc.asPoint();
			// Draw active (green) over underlay vis.
			g2.setColor(nodeInfo.isActive() ? activeGreen : Color.LIGHT_GRAY);
			int radius = 3;
			g2.drawOval(VisualizationInjector.scaleValue(center.x) - radius, VisualizationInjector.scaleValue(center.y) - radius, radius * 2,
					radius * 2);

			if (!nodeInfo.isActive()) {
				return;
			}

			g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
					1.0f));


			int segments = nodeInfo.getNodeColorDimensions();
			int segmentDegrees = (int) (360 / (double) segments);
			int arcSize = 8;

			g2.setStroke(new BasicStroke(8, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL));
			for (int dim = 0; dim < segments; dim++) {
				int value = nodeInfo.getNodeColor(dim);
				if (value < 0 || !activeLayers[dim]) {
					continue;
				}
				g2.setColor(colors[dim][value]);
				g2.drawArc(VisualizationInjector.scaleValue(center.x)-arcSize, VisualizationInjector.scaleValue(center.y)-arcSize, 2*arcSize, 2*arcSize, dim*segmentDegrees, segmentDegrees);
			}
			g2.setStroke(new BasicStroke(1));

			String nodeDesc = nodeInfo.getNodeDescription();
			g2.drawString(nodeDesc, center.x + 4, center.y + 4);
		}

	}

	@Override
	public JComponent getComponent() {
		return this;
	}

	@Override
	public JMenu getCustomMenu() {
		return menu;
	}

	@Override
	public boolean isHidden() {
		return false;
	}

	@Override
	public String getDisplayName() {
		return "Info: "+name;
	}

}
