package de.tud.kom.p2psim.impl.topology.movement.modularosm.transition;

import java.util.HashMap;
import java.util.Map;
import de.tud.kom.p2psim.api.topology.Topology;
import de.tud.kom.p2psim.api.topology.movement.SimLocationActuator;
import de.tud.kom.p2psim.impl.topology.movement.modularosm.attraction.BasicAttractionPoint;
import de.tud.kom.p2psim.impl.topology.movement.modularosm.attraction.IAttractionProvider;
import de.tud.kom.p2psim.impl.topology.util.PositionVector;
import de.tudarmstadt.maki.simonstrator.api.Binder;
import de.tudarmstadt.maki.simonstrator.api.Monitor;
import de.tudarmstadt.maki.simonstrator.api.component.sensor.location.IAttractionPoint;
import de.tudarmstadt.maki.simonstrator.api.component.sensor.location.Location;

/**
 * This {@link IAttractionAssigmentStrategy} makes clients move around randomly in a specified area. You can specify the target
 * area center via the {@link #updateTargetAttractionPoint(SimLocationActuator, IAttractionPoint)} method. The client will then start
 * to roam the area randomly till a new target area is assigned.
 *
 * @author Clemens Krug
 * 
 * Made the transition strategy extend the {@link AbstractAttractionBasedAssignmentStrategy} for a more generalized approach on 
 * the {@link IAttractionAssigmentStrategy} implementation for attraction point based strategies.
 * 
 * @author Julian Zobel
 * @version 1.1, January 2019
 */
public class RandomInAreaTransitionStrategy extends AbstractAttractionBasedAssignmentStrategy
{   
    public RandomInAreaTransitionStrategy() {
		super(0, 0);
	}

	/**
     * These are the current spots inside the target area where the client is currently heading.
     */
    private Map<SimLocationActuator, IAttractionPoint> currentTarget = new HashMap<>();
    private Map<SimLocationActuator, Integer> currentSearchRadius = new HashMap<>();
  
    /**
     * The radius the target area should have. Should be set via XML config file.
     */
    private int defaultRadius;

    @Override
    public void addComponent(SimLocationActuator ms) {

        if(!assignments.containsKey(ms))
        {
            IAttractionPoint aPoint = attractionProvider.getAttractionPoints().iterator().next();
            assignments.put(ms, aPoint);
            currentTarget.put(ms, nextRandomPosition(aPoint, defaultRadius));
            currentSearchRadius.put(ms, defaultRadius);
        }
        notifyListenersOfAssignmentUpdate(ms,  currentTarget.get(ms));
    }

    @Override
    public void reachedAttractionPoint(SimLocationActuator ms, IAttractionPoint attractionPoint) {
        currentTarget.put(ms, nextRandomPosition(assignments.get(ms), currentSearchRadius.get(ms)));
        notifyListenersOfAssignmentUpdate(ms,  currentTarget.get(ms));
    }

    @Override
    public void updateTargetAttractionPoint(SimLocationActuator comp, IAttractionPoint attractionPoint) {
        assignments.put(comp, attractionPoint);
        currentTarget.put(comp, nextRandomPosition(attractionPoint, currentSearchRadius.get(comp)));
        notifyListenersOfAssignmentUpdate(comp, attractionPoint);
    }

    public void setSearchRadiusForComponent(SimLocationActuator ms, int radius) {
        currentSearchRadius.put(ms, radius);
    }

    public void setDefaultRadius(int radius) {
        this.defaultRadius = radius;
    }

    /**
     * Calculates a random point within a given circular area.
     * @param center The center of the area.
     * @param radius The radius of the area.
     * @return A random position within the area.
     */
    private BasicAttractionPoint nextRandomPosition(Location center, int radius) {
        assert radius > 0 : "An area radius must be specified for the RandomInAreaTransitionStrategy! Did you set the 'DefaultRadius' property for this transition?";

        double x = center.getLongitudeOrX();
        double y = center.getLatitudeOrY();

        double newX = -1;
        double newY = -1;

        int tries = 0;

        while(newX < 0.0 || newX > Binder.getComponentOrNull(Topology.class).getWorldDimensions().getX()
                || newY < 0.0 || newY > Binder.getComponentOrNull(Topology.class).getWorldDimensions().getY())
        {
            double calcRadius = rnd.nextDouble() * radius;
            double calcAngle = rnd.nextDouble() * 360;

            newX = x + Math.sin(calcAngle) * calcRadius;
            newY = y + Math.cos(calcAngle) * calcRadius;
            tries++;

            if(tries > 100) throw new AssertionError("Unable to find a valid target destination within <100 tries.");
        }

        Monitor.log(this.getClass(), Monitor.Level.DEBUG, "Next random position is " + newX + " / " + newY);

        return new BasicAttractionPoint("RNDPOS", new PositionVector(newX, newY));
    }
}
