/*
 * 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.movement.modularosm.groups;

import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import de.tud.kom.p2psim.api.topology.movement.SimLocationActuator;
import de.tud.kom.p2psim.impl.util.Tuple;
import de.tudarmstadt.maki.simonstrator.api.Time;

/**
 * This class serves as a container to store variables of groups, which are used by multiple classes and interfaces.
 * 
 * @author Marcel Verst
 * @version 1.0, 22.11.2018
 */
public class MovementGroupContainer {
	private static MovementGroupContainer instance;

	private LinkedHashSet<SocialMovementGroup> groups;

	// Mappings
	private LinkedHashMap<SimLocationActuator, Long> leftGroupAtTime;
	private LinkedHashMap<SimLocationActuator, Boolean> hasMerged;
	private LinkedHashMap<SimLocationActuator, Long> mergedAtTime;
	
	// Waiting Groups, used for encounter strategy Wait. Includes remaining waiting time and last update time
	public LinkedHashMap<SocialMovementGroup, Tuple<Long, Long>> waitingGroups;

	private MovementGroupContainer() {
		groups = new LinkedHashSet<>();
		leftGroupAtTime = new LinkedHashMap<>();		
		hasMerged = new LinkedHashMap<>();
		mergedAtTime = new LinkedHashMap<>();		
		waitingGroups = new LinkedHashMap<SocialMovementGroup, Tuple<Long,Long>>();
	}

	/**
	 * Returns the one and only instance of this class.
	 * 
	 * @return GroupContainer
	 * 
	 * @author Marcel Verst
	 */
	public static MovementGroupContainer getInstance() {
		if(instance == null) {
			synchronized (MovementGroupContainer.class) {
				if (instance == null) {
					instance = new MovementGroupContainer();
				}
			}
		}
		return instance;
	}

	/* 
	 * =====================================================================================================
	 * === GROUP MODIFIER FUNCTIONS
	 * =====================================================================================================
	 */
	/**
	 * Adds a group to the set of groups.
	 * 
	 * @param SocialMovementGroup The group to add.
	 * 
	 * @author Marcel Verst
	 */
	public void addGroup(SocialMovementGroup group) {
		groups.add(group);
	}
	
	public boolean hasGroup(SocialMovementGroup group) {
		return groups.contains(group);
	}

	/**
	 * Removes a group from the set of groups and sets the leftGroupAtTime variable for all group members.
	 * 
	 * @param SocialMovementGroup The group to be removed.
	 * 
	 * @author Marcel Verst
	 */
	public void removeGroup(SocialMovementGroup group) {
		for(SimLocationActuator participant : group.getMembers()) {
			leftGroupAtTime.put(participant, Time.getCurrentTime());			
		}
		waitingGroups.remove(group);
		groups.remove(group);		
	}

	/**
	 * If a group is changed (e.g. a host left the group) the new group is updated here.
	 * 
	 * @param SocialMovementGroup The group which had an update.
	 * 
	 * @author Marcel Verst
	 */
	public void updateGroupEntry(SocialMovementGroup group) {
		if(groups.contains(group)) {
			groups.remove(group);
		}
		groups.add(group);
	}

	/* 
	 * =====================================================================================================
	 * === MAP MODIFIER FUNCTIONS
	 * =====================================================================================================
	 */
	public void addLeftGroupAtTimeEntry(SimLocationActuator host, Long time) {
		leftGroupAtTime.put(host, time);
	}

	public void removeLeftGroupAtTimeEntry(SimLocationActuator host) {
		if(leftGroupAtTime.containsKey(host)) {
			leftGroupAtTime.remove(host);
		}
	}	

	public void addHasMergedEntry(SimLocationActuator host, Boolean value) {
		hasMerged.put(host, value);
	}

	public void removeHasMergedEntry(SimLocationActuator host) {
		if(hasMerged.containsKey(host)) {
			hasMerged.remove(host);
		}
	}

	public void addMergedAtTimeEntry(SimLocationActuator host, Long time) {
		mergedAtTime.put(host, time);
	}

	public void removeMergedAtTimeEntry(SimLocationActuator host) {
		if(mergedAtTime.containsKey(host)) {
			mergedAtTime.remove(host);
		}
	}

	/* 
	 * =====================================================================================================
	 * === HELPER FUNCTIONS
	 * =====================================================================================================
	 */
	/**
	 * Returns true, if the host is the leader of a group, false else.
	 * 
	 * @param SimLocationActuator The host to be checked.
	 * @return Boolean
	 * 
	 * @author Marcel Verst
	 */
	public boolean isLeader(SimLocationActuator host) {
		boolean result = false;
		SocialMovementGroup group = getGroupOfHost(host);
		if(group != null && group.getLeader() == host) {
			result = true;
		}
		return result;
	}

	/**
	 * Returns true, if a host is a group member, false else.
	 * 
	 * @param SimLocationActuator The host to be checked.
	 * @return Boolean
	 * 
	 * @author Marcel Verst
	 */
	public boolean isGroupMember(SimLocationActuator host) {
		for(SocialMovementGroup group : groups) {
			if(group.hasMember(host)) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Returns true, if the host is a group member, but not a leader.
	 * 
	 * @param SimLocationActuator The host to be checked.
	 * @return Boolean
	 * 
	 * @author Marcel Verst
	 */
	public boolean isDependent(SimLocationActuator host) {
		return isGroupMember(host) && !isLeader(host);
	}

	/**
	 * Returns the GroupInstance of which a host is a member of.
	 * 
	 * @param SimLocationActuator The host to be checked.
	 * @return GroupInstance
	 * 
	 * @author Marcel Verst
	 */
	public SocialMovementGroup getGroupOfHost(SimLocationActuator host) {
		SocialMovementGroup result = null;
		for(SocialMovementGroup group : groups) {
			if(group.hasMember(host)) {
				result = group;
			}
		}
		return result;
	}

	/**
	 * Updates the variables if a host has already merged before and when the last merging process happened.
	 * This function HAS to be called before doing any encounter process.
	 * 
	 * @param SocialMovementGroup The first group.
	 * @param SocialMovementGroup The second group.
	 * 
	 */
	public void updateMergeVars(SocialMovementGroup group1, SocialMovementGroup group2) {
		updateMergeVars(group1);
		updateMergeVars(group2);
	}
	
	public void updateMergeVars(SocialMovementGroup group) {		
		if(group == null || group.getMembers().isEmpty()) {
			return;
		}
		
		for(SimLocationActuator host : group.getMembers()) {
			addHasMergedEntry(host, true);
			addMergedAtTimeEntry(host, Time.getCurrentTime());
		}
	}
	
	/**
	 * Returns true, if the given group is currently waiting.
	 * 
	 * @param SocialMovementGroup The group to check.
	 * 
	 * @return Boolean
	 * 
	 * @author Marcel Verst
	 */
	public boolean isWaiting(SocialMovementGroup group) {
		if(waitingGroups.containsKey(group)) {
			return true;
		}
		return false;
	}

	/* 
	 * =====================================================================================================
	 * === GETTER AND SETTER FUNCTIONS
	 * =====================================================================================================
	 */
	/**
	 * Returns a set of SimLocationActuators including the host itself which belong to the group of the host.
	 * 
	 * @param SimLocationActuator The host to be checked.
	 * @return Set<SimLocationActuator>
	 * 
	 * @author Marcel Verst
	 */
	public LinkedHashSet<SimLocationActuator> getGroupMembers(SimLocationActuator host){
		return getGroupOfHost(host).getMembers();
	}

	public LinkedHashSet<SocialMovementGroup> getGroups() {
		return groups;
	}

	public void setGroups(LinkedHashSet<SocialMovementGroup> groups) {
		this.groups = groups;
	}

	public boolean hostWasGroupMember(SimLocationActuator host) {
		return this.leftGroupAtTime.containsKey(host);
	}
	
	public long getTimeSinceHostLeftLastGroup(SimLocationActuator host) {
		if(hostWasGroupMember(host)) {
			return Time.getCurrentTime() - this.leftGroupAtTime.get(host);
		}
		else {
			return 0;
		}
	}

	public void setLeftGroupAtTime(LinkedHashMap<SimLocationActuator, Long> leftGroupAtTime) {
		this.leftGroupAtTime = leftGroupAtTime;
	}

	public LinkedHashMap<SimLocationActuator, Boolean> getHasMerged() {
		return hasMerged;
	}

	public void setHasMerged(LinkedHashMap<SimLocationActuator, Boolean> hasMerged) {
		this.hasMerged = hasMerged;
	}

	public LinkedHashMap<SimLocationActuator, Long> getMergedAtTime() {
		return mergedAtTime;
	}

	public void setMergedAtTime(LinkedHashMap<SimLocationActuator, Long> mergedAtTime) {
		this.mergedAtTime = mergedAtTime;
	}
}