/*
 * Copyright (c) 2005-2015 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.movement.modularosm;

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.awt.Stroke;
import java.awt.geom.Point2D;
import java.util.LinkedHashMap;
import java.util.Map;

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.topology.movement.SimLocationActuator;
import de.tud.kom.p2psim.impl.topology.PositionVector;
import de.tud.kom.p2psim.impl.topology.movement.local.AbstractLocalMovementStrategy;
import de.tud.kom.p2psim.impl.topology.views.VisualizationTopologyView;
import de.tud.kom.p2psim.impl.topology.views.VisualizationTopologyView.VisualizationInjector;
import de.tud.kom.p2psim.impl.topology.views.visualization.ui.VisualizationComponent;
import de.tud.kom.p2psim.impl.topology.views.visualization.world.NodeVisInteractionListener;
import de.tudarmstadt.maki.simonstrator.api.Host;
import de.tudarmstadt.maki.simonstrator.api.Oracle;
import de.tudarmstadt.maki.simonstrator.api.common.graph.INodeID;
import de.tudarmstadt.maki.simonstrator.api.component.ComponentNotAvailableException;
import de.tudarmstadt.maki.simonstrator.api.component.sensor.location.AttractionPoint;
import de.tudarmstadt.maki.simonstrator.api.component.sensor.location.Location;
import de.tudarmstadt.maki.simonstrator.api.component.sensor.route.Route;
import de.tudarmstadt.maki.simonstrator.api.component.sensor.route.Route.RouteSegment;
import de.tudarmstadt.maki.simonstrator.api.component.sensor.route.RouteSensor;

/**
 * Visualization Component of the Attraction Points in the Modular Movement
 * Model.
 * 
 * 
 * @author Martin Hellwig, Bjoern Richerzhagen
 * @version 1.0, 02.07.2015
 */
public class ModularMovementModelViz extends JComponent
		implements VisualizationComponent, NodeVisInteractionListener {

	protected ModularMovementModel movementModel;

	protected boolean showAttractionPoints = true;

	protected boolean showNodePositions = true;

	protected boolean showTrajectories = false;
	
	protected boolean setupCalculatesTrajectories = false;

	private JMenu menu;

	private Map<Long, TrajectoryVis> trajectoryVisualizations = new LinkedHashMap<>();

	private final static int NODE_PAD = 2;

	private final static int ATTR_PAD = 5;

	private static Color COLOR_ATTR_POINT = Color.decode("#4A7B9D");

	public ModularMovementModelViz() {
        init();
	}

	protected void init() {
        setBounds(0, 0, VisualizationInjector.getWorldX(),
                VisualizationInjector.getWorldY());
		VisualizationInjector.addInteractionListener(this);
        setOpaque(true);
        setVisible(true);

        menu = new JMenu("Mobility Model");
        menu.add(createCheckboxAp());
        menu.add(createCheckboxNodePositions());
		menu.add(createCheckboxTrajectories());
    }

	public ModularMovementModelViz(ModularMovementModel model) {
		init();
		this.movementModel = model;
	}

	private JCheckBoxMenuItem createCheckboxAp() {
		final JCheckBoxMenuItem checkBox = new JCheckBoxMenuItem(
				"show attraction points", showAttractionPoints);
		checkBox.addChangeListener(new ChangeListener() {
			@Override
			public void stateChanged(ChangeEvent e) {
				showAttractionPoints = checkBox.isSelected();
				VisualizationInjector.invalidate();
			}
		});
		return checkBox;
	}

	private JCheckBoxMenuItem createCheckboxNodePositions() {
		final JCheckBoxMenuItem checkBox = new JCheckBoxMenuItem(
				"show node positions", showNodePositions);
		checkBox.addChangeListener(new ChangeListener() {
			@Override
			public void stateChanged(ChangeEvent e) {
				showNodePositions = checkBox.isSelected();
				VisualizationInjector.invalidate();
			}
		});
		return checkBox;
	}

	private JCheckBoxMenuItem createCheckboxTrajectories() {
		setupCalculatesTrajectories = AbstractLocalMovementStrategy.isCalculatingRouteSegments();
		final JCheckBoxMenuItem checkBox = new JCheckBoxMenuItem(
				"show node trajectories", showTrajectories);
		checkBox.addChangeListener(new ChangeListener() {
			@Override
			public void stateChanged(ChangeEvent e) {
				showTrajectories = checkBox.isSelected();
				if (!setupCalculatesTrajectories) {
					AbstractLocalMovementStrategy
							.setCalculationOfRouteSegments(showTrajectories);
				}
				VisualizationInjector.invalidate();
			}
		});
		if (!setupCalculatesTrajectories) {
			AbstractLocalMovementStrategy
					.setCalculationOfRouteSegments(showTrajectories);
		}
		return checkBox;
	}

	@Override
	public void paint(Graphics g) {
		super.paintComponent(g);
		Graphics2D g2 = (Graphics2D) g;
		g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
				RenderingHints.VALUE_ANTIALIAS_ON);

		if (showAttractionPoints) {
			drawAttractionPoints(g2);
		}

		if (showNodePositions) {
            for (SimLocationActuator comp : movementModel
                    .getAllLocationActuators()) {
                drawNodePosition(g2, comp);
            }
		}

		if (showTrajectories) {
			for (TrajectoryVis tVis : trajectoryVisualizations.values()) {
				tVis.drawTrajectory(g2);
			}
		}
	}

	@Override
	public void onHostClick(long hostID, boolean isActive) {
		if (isActive) {
			Host h = Oracle.getHostByID(INodeID.get(hostID));
			try {
				RouteSensor routeSensor = h.getComponent(RouteSensor.class);
				trajectoryVisualizations.put(hostID,
						new TrajectoryVis(routeSensor));
			} catch (ComponentNotAvailableException e) {
				// ignore.
			}

		} else {
			trajectoryVisualizations.remove(hostID);
		}
	}

	/**
	 * Visualizes the movement trajectory of a given node.
	 * 
	 * @author Bjoern Richerzhagen
	 * @version 1.0, Aug 10, 2017
	 */
	private class TrajectoryVis {

		private final RouteSensor routeSensor;

		public TrajectoryVis(RouteSensor routeSensor) {
			this.routeSensor = routeSensor;
		}

		public void drawTrajectory(Graphics2D g2) {
			Route rt = routeSensor.getRoute();
			if (!rt.hasSegmentInformation()) {
				return;
			}
			for (RouteSegment segment : rt.getSegmentsAhead()) {
				Location lastLoc = null;
				for (Location loc : segment.getSegmentLocations()) {
					if (lastLoc == null) {
						lastLoc = loc;
						continue;
					}
					g2.setColor(Color.MAGENTA);
					g2.setStroke(new BasicStroke(3.0f));
					g2.drawLine(
							VisualizationInjector
									.scaleValue(lastLoc.getLongitude()),
							VisualizationInjector
									.scaleValue(lastLoc.getLatitude()),
							VisualizationInjector
									.scaleValue(loc.getLongitude()),
							VisualizationInjector
									.scaleValue(loc.getLatitude()));
					lastLoc = loc;
				}
			}
		}

	}

    /**
     * Provides access super.paintComponent() for extending classes.
     * @param g the Graphics object for painting.
     */
    protected void paintSuper(Graphics g)
    {
        super.paintComponent(g);
    }

    /**
     * Draws the attraction points. This method has been extracted from paint()
     * to make it possible for extending class to override only this bit while leaving everything
     * else untouched.
     * @param g2 the Graphics2D object for painting.
     */
	protected void drawAttractionPoints(Graphics2D g2)
    {
        for (AttractionPoint aPoint : movementModel.getAttractionPoints()) {
            Point point = ((PositionVector) aPoint).asPoint();
            // draw border
            g2.setColor(Color.BLACK);
            g2.setFont(VisualizationTopologyView.FONT_MEDIUM);
            g2.drawString(aPoint.getName(),
                    VisualizationInjector.scaleValue(point.x) - ATTR_PAD,
                    VisualizationInjector.scaleValue(point.y) - ATTR_PAD);
            g2.setColor(COLOR_ATTR_POINT);
            float alpha = 0.25f + (float) (aPoint.getWeight() / 2);
            g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha));
            g2.fillOval(
                    VisualizationInjector.scaleValue(point.x) - ATTR_PAD,
                    VisualizationInjector.scaleValue(point.y) - ATTR_PAD,
                    ATTR_PAD * 2 + 1, ATTR_PAD * 2 + 1);
            g2.setColor(COLOR_ATTR_POINT);
            int radius = VisualizationInjector.scaleValue(aPoint.getRadius()) + ATTR_PAD;
            g2.drawOval(VisualizationInjector.scaleValue(point.x) - radius,
                    VisualizationInjector.scaleValue(point.y) - radius,
                    radius * 2 + 1, radius * 2 + 1);

        }
    }

    /**
     * Draws the position of the node. This method has been extracted from paint()
     * to make it possible for extending class to override only this bit while leaving everything
     * else untouched. Instead of painting all of the nodes, only a specific one gets painted. This allows
     * extending class to fall back to this implementation if one specific node could not be drawn for whatever reason.
     * @param g2 the Graphics2D object for painting.
     */
	protected void drawNodePosition(Graphics2D g2, SimLocationActuator comp)
	{
        Point2D pt = comp.getRealPosition().asPoint();
        g2.setColor(Color.GRAY);
        g2.fillOval(VisualizationInjector.scaleValue(pt.getX()) - NODE_PAD,
                VisualizationInjector.scaleValue(pt.getY()) - NODE_PAD, NODE_PAD * 2 + 1,
                NODE_PAD * 2 + 1);
	}

	protected void setMovementModel(ModularMovementModel model)
    {
        movementModel = model;
    }

	@Override
	public String getDisplayName() {
		return "Mobility Model";
	}

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

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

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