/*
 * Created on 29-apr-2004
 */
package jaCop.nodeStatus;

import convFramework.domain.*;
import convFramework.exceptions.*;

import jaCop.domain.*;
import jaCop.exceptions.*;
import convFramework.exceptions.BadInvocationArgumentException;

import java.util.*;
import java.net.*;

/**
 * @author Lompa
 */
public class CommunityStatusStore {
	
	/* campi */
	private Vector neighbors = new Vector();
	private Neighborhood junctureNeighborhood = null;
	private String community = null;
	private ILoadFactorCalculator calc = null;
	private int structureBreaks = 0;
	private int connectionRank = 2;
	private int baseStructureSize = 2;
	
	/**
	 * @param community
	 */
	public CommunityStatusStore(String community, ILoadFactorCalculator calc, int connectionRank){
		this.community = community;
		this.calc = calc;
		this.connectionRank = connectionRank;
	}
	
	/**
	 * @param node
	 * @param isJoined
	 */
	public synchronized void addNeighbor(COPNode node, boolean isJoined, boolean isStructure){
		try {
			if (!existsNeighborhood(node)){
				neighbors.add(new Neighborhood(new UDPNode(node.getAddress(), node.getPort()), node.getLoadFactor(), isJoined, isStructure));
			}
		}
		catch(convFramework.exceptions.BadInvocationArgumentException e){
			throw new JaCOPError(e.getMessage());
		}
	}
	
	/**
	 * @param node
	 */
	public synchronized void removeNeighbor(COPNode node){
		/* rimozioane del vicino, se è presente */
		Neighborhood removedNeighborhood = getNeighborhood(node);
		if (removedNeighborhood != null)
			neighbors.remove(removedNeighborhood);
		/* eliminazione del riferimento al punto di giunzione */
		if(this.junctureNeighborhood == removedNeighborhood)
			this.junctureNeighborhood = null;
	}
	
	/**
	 * @param node
	 * @return
	 */
	public synchronized boolean isNeighbor(COPNode node){
		return existsNeighborhood(node);
	}
	
	/**
	 * @param node
	 * @return
	 * @throws jaCop.exceptions.BadInvocationArgumentException
	 */
	public synchronized boolean isJoined(COPNode node) {
		if (!existsNeighborhood(node))
			return false;
		return getNeighborhood(node).isJoined;
	}
	
	/**
	 * @param node
	 * @return
	 * @throws jaCop.exceptions.BadInvocationArgumentException
	 */
	public synchronized void setJoined(COPNode node, boolean joined) {
		if (!existsNeighborhood(node)) return;
		getNeighborhood(node).isJoined = joined;
	}
	
	/**
	 * @param node
	 * @return
	 * @throws jaCop.exceptions.BadInvocationArgumentException
	 */
	public synchronized boolean isStructure(COPNode node) {
		if (!existsNeighborhood(node))
			return false;
		return getNeighborhood(node).getNode().isStructure();
	}
	
	/**
	 * @param node
	 * @return
	 * @throws jaCop.exceptions.BadInvocationArgumentException
	 */
	public synchronized void setStructure(COPNode node, boolean isStructure) {
		if (!existsNeighborhood(node)) return;
		Neighborhood n = getNeighborhood(node);
		
		n.isStructure = isStructure;
	}
	
	/**
	 * @param node
	 * @return
	 * @throws jaCop.exceptions.BadInvocationArgumentException
	 */
	public synchronized void setLoadFactor(COPNode node, float loadFactor) {
		if (!existsNeighborhood(node)) return;
		Neighborhood n = getNeighborhood(node);
		n.loadFactor = loadFactor;
	}
	
	/**
	 * @param node
	 * @throws jaCop.exceptions.BadInvocationArgumentException
	 */
	public synchronized void  setJuncturePoint(COPNode node) {
		if (!existsNeighborhood(node)) return;
		else
			this.junctureNeighborhood = getNeighborhood(node);
	}
	
	/**
	 * @return
	 */
	public synchronized COPNode getJuncturePoint(){
		if (junctureNeighborhood == null)
			return null;
		else {
			return junctureNeighborhood.getNode();
		}
	}
	
	/**
	 * @return
	 */
	public synchronized COPNode getNeighbor(Node node){
		Neighborhood n = getNeighborhood(node);
		if (n!=null)
			return n.getNode();
		else
			return null;
	}
	
	/**
	 * @return
	 */
	public synchronized COPNode[] getNeighbors(){
		COPNode[] nodes = new COPNode[neighbors.size()];
		for(int i = 0; i< neighbors.size(); i++){
			nodes[i] = ((Neighborhood) neighbors.get(i)).getNode();
		}
		return nodes;
	}
	
	/**
	 * @return
	 */
	public synchronized COPNode[] getStructureNeighbors(){
		Vector structureNeighbors = new Vector();
		for(int i = 0; i< neighbors.size(); i++){
			Neighborhood n = (Neighborhood) neighbors.get(i);
			if (n.isStructure) structureNeighbors.add(n.getNode());
		}
		return (COPNode[]) structureNeighbors.toArray(new COPNode[structureNeighbors.size()]);
	}
	
	/**
	 * @return
	 */
	public synchronized COPNode[] getJoinedNeighbors(){
		Vector joinedNeighbors = new Vector();
		for(int i = 0; i< neighbors.size(); i++){
			Neighborhood n = (Neighborhood) neighbors.get(i);
			if (n.isJoined) joinedNeighbors.add(n.getNode());
		}
		return (COPNode[]) joinedNeighbors.toArray(new COPNode[joinedNeighbors.size()]);
	}
	
	/**
	 * @return
	 */
	public synchronized COPNode[] getStructureNeighbors(COPNode node){
		Neighborhood n = getNeighborhood(node);
		if (n == null) return new COPNode[0];
		
		return getNeighborhood(node).getStructureNeighbors(); 
	}
	
	/**
	 * @return
	 */
	public synchronized void addStructureNeighbor(COPNode node, COPNode newNode){
		Neighborhood n = getNeighborhood(node);
		if (n == null) return;
		COPNode nNode = newNode;
		if (!existsNeighborhood(newNode)){
			try { nNode = new COPNode(newNode.getAddress(), newNode.getPort(), newNode.getLoadFactor(), false); }
			catch(BadInvocationArgumentException e){
				throw new JaCOPError(e.getMessage());
			}
		}
		n.addStructureNeighbor(nNode);
	}
	
	public synchronized void setStructureNeighbors(COPNode node, COPNode[] nodes){
		Neighborhood n = getNeighborhood(node);
		n.structureNeighbors.clear();
		for(int i = 0; i < nodes.length; i++)
			addStructureNeighbor(node, nodes[i]);
	}
	
	/**
	 * @return
	 */
	public synchronized void removeStructureNeighbor(COPNode node, COPNode oldNode){
		Neighborhood n = getNeighborhood(node);
		if (n == null) return;
		n.removeStructureNeighbor(oldNode);
	}
	
	/**
	 * @return
	 */
	public synchronized int getNeighborsNumber(){
		return this.neighbors.size();
	}
	
	/**
	 * @return
	 */
	public synchronized float getCurrentLoadFactor(){
		return calc.calculateLoadFactor(this);
	}
	
	/* metodi privati di utilità *******************************************************************/
	
	private synchronized Neighborhood getNeighborhood(Node node){
		for(int i = 0; i < neighbors.size(); i++){
			if (((Neighborhood) neighbors.get(i)).node.isSame(node))
				return (Neighborhood) neighbors.get(i);
		}
		return null;
	}
	
	private synchronized boolean existsNeighborhood(Node node){
		return getNeighborhood(node) != null;
	}
	
	/* classe privata che descrive un vicino */
	private static class Neighborhood {
		
		public UDPNode node = null;
		public float loadFactor = -1;
		public boolean isJoined = false;
		public boolean isStructure = false;
		public Vector structureNeighbors = new Vector(); 
		
		/**
		 * @param node
		 * @param loadFactor
		 * @param isJoined
		 */
		public Neighborhood(UDPNode node, float loadFactor, boolean isJoined, boolean isStructure) {
			this.node = node;
			this.isJoined = isJoined;
			this.loadFactor = loadFactor;
			this.isStructure = isStructure;
		}
		
		public COPNode getNode(){
			try { return new COPNode(node.getAddress(), node.getPort(), loadFactor, isStructure); }
			catch(convFramework.exceptions.BadInvocationArgumentException e){
				throw new JaCOPError(e.getMessage());
			}			
		}
		
		public void addStructureNeighbor(COPNode n){
			if (!structureNeighbors.contains(n)){
				structureNeighbors.add(n);
			}
		}
		
		public void removeStructureNeighbor(COPNode n){
			if (structureNeighbors.contains(n)){
				structureNeighbors.remove(n);
			}
		}
		
		public COPNode[] getStructureNeighbors(){
			return (COPNode[]) structureNeighbors.toArray(new COPNode[structureNeighbors.size()]);
		}
		
	}
	
	/**
	 * @return Returns the calc.
	 */
	public synchronized ILoadFactorCalculator getLoadFactorCalcuclator() {
		return calc;
	}
	/**
	 * @return Returns the restructuring.
	 */
	public synchronized boolean hasCompleteStructure() {
		return this.structureBreaks == 0;
	}
	
	public synchronized void breakStructure(){
		this.structureBreaks++;
	}
	
	public synchronized void repairStructure(int breaks) {
		if (this.structureBreaks != 0) this.structureBreaks = Math.min(0, this.structureBreaks - breaks);
		/* se la struttura è stata completamente riparata aggiorno la dimensiona base di strruttura  */
		if (this.structureBreaks == 0) this.baseStructureSize = this.getStructureNeighbors().length;
	}
	
	public synchronized int getStructureBreaksNumber(){
		return this.structureBreaks;
	}
	
	public boolean isCoreNode(){
		return this.baseStructureSize < this.connectionRank;
	}
	/**
	 * @return Returns the connectionRank.
	 */
	public synchronized int getConnectionRank() {
		return connectionRank;
	}
}
