package drawing;


import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.Vector;
import javax.imageio.ImageIO;


/**
 * Diagram class reperesents a UML diagram 
 * @author MC
 *
 */
public class Diagram {
	/**
	 * the image containing the diagram
	 */
	public Image img;
	/**
	 * the height of the image
	 */
	private int height;
	/**
	 * the width of the image
	 */
	private int width;
	/**
	 * the font color
	 */
	static final Color fontColor = Color.BLACK;
	/**
	 * class rectangles color
	 */
	static final Color fillColor = new Color(255, 255, 153);
	/**
	 * borders and links color
	 */
	static final Color BorderColor = Color.RED;
	/**
	 * background diagram color
	 */
	static final Color BackgroundColor = Color.WHITE;
	/**
	 * the classes vector
	 */
	public Vector<UnitClass> vectClass;
	/**
	 * the links vector
	 */
	public Vector<Link> vectLink;
	private Graphics2D g2d;
	
	/**
	 * constructor
	 * @param heightIn image height
	 * @param widthIn image width
	 */
	public Diagram(int heightIn, int widthIn){

		vectClass = new Vector<UnitClass>();
		vectLink = new Vector<Link>();
		height = heightIn;
		width = widthIn;
		img = null;
		init();
	}
	/**
	 * constructor
	 *
	 */
	public Diagram(){

		vectClass = new Vector<UnitClass>();
		vectLink = new Vector<Link>();
		height = 50;
		width = 50;
		img = null;
		init();
	}
	/**
	 * whether or not the diagram contains a class
	 * @param u the class to check
	 * @return true if the diagram contains u and else false 
	 */
	public boolean contains(UnitClass u){
		boolean res = false;
		int i = 0;
		while(i < vectClass.size() && !res){
			UnitClass temp = (UnitClass) vectClass.get(i);
			if(temp.isEqual(u))
				res = true;
			i++;
		}
		return res;
	}
	/**
	 * whether or not the diagram contains a link
	 * @param l the link to check
	 * @return true if the diagram contains l and else false
	 */
	public boolean contains(Link l){
		boolean res = false;
		int i = 0;
		while(i < vectLink.size() && !res){
			Link temp = (Link) vectLink.get(i);
			if(temp.isEqual(l))
				res = true;
			i++;
		}
		return res;
	}
	/**
	 * determines the index of a link in the links vectors 
	 * @param l the link to check
	 * @return the index or -1 if the vector doesn't contain the link 
	 */
	public int indexOf(Link l){
		int res = -1;
		int i = 0;
		while(i < vectLink.size() && res == -1){
			Link temp = (Link) vectLink.get(i);
			if(temp.isEqual(l))
				res = i;
			i++;
		}
		return res;
	}
	/**
	 * adds a class to the diagram
	 * @param u the class to add
	 */
	public void add(UnitClass u){
		vectClass.add(u);
	}
	/**
	 * adds a link to the diagram
	 * @param l the linl to add
	 */
	public void add(Link l){
		int index = indexOf(l);
		if(index == -1)
			vectLink.add(l);
		else{
			Link l2 = (Link)vectLink.get(index);
			l2.nbInstanceTemp ++;
		}			
	}
	/**
	 * draws the subgraph whose root is the parameter
	 * @param root of the graph to draw
	 */
	public void draw(String root){
		Vector<UnitClass> subVectUnitClass = new Vector<UnitClass>();
		Vector<Link> subVectLinks;
		
		subVectUnitClass.add(getUnitClass(new UnitClass(root)));
		
		int h = 0, w = 0;
		int minY = 0, minX = 0;
		
		//init unitclasses
		subVectLinks = getChildren(root);
		for(int i = 0; i < subVectLinks.size(); i++){
			Link l = (Link)subVectLinks.get(i);
			if(!subVectUnitClass.contains(l.startUnit))
				subVectUnitClass.add(l.startUnit);
			if(!subVectUnitClass.contains(l.endUnit))
				subVectUnitClass.add(l.endUnit);
		}
		
		
		GraphHierarchyEnergy graph = new GraphHierarchyEnergy(this, subVectUnitClass, subVectLinks);
		
		if(subVectUnitClass.size() > 1)
			graph.AssignCoordinates();
		
		if(subVectUnitClass.size() > 0){
			Vertice v = (Vertice) graph.Vertices.get(0);
			minX = (int)Math.round(v.x);
			minY = (int)Math.round(v.y);
			
		}
		//set coordinates classUnits
		for(int i = 0; i < graph.Vertices.size(); i++){
			
			Vertice v = (Vertice) graph.Vertices.get(i);
			if(!v.isVirtual){
				UnitClass u = (UnitClass) subVectUnitClass.get(v.index);
				u.x = (int)Math.round(v.x);
				u.y = (int)Math.round(v.y);
				v.x = u.x;
				v.y = u.y;
			
			
				//commpute height image and width
				if( h < u.y + u.getHeight())
					h = u.y + u.getHeight();
				if( w < u.x + u.getWidth())
					w = u.x + u.getWidth();
				if(u.y < minY)
					minY = u.y;
				if(u.x < minX)
					minX = u.x;
			}
		}
		
		//translate classunits if necessary
		
		for(int i = 0; i < subVectUnitClass.size(); i++){
			UnitClass u = (UnitClass) subVectUnitClass.get(i);
			u.x += - minX + 50;
			u.y += - minY + 50;		
		}

		//set coordinate links
		for(int i = 0; i < subVectLinks.size(); i++){
			Link l = (Link) subVectLinks.get(i);
			l.upDateCoordinates();
		}
		
		height = h - minY + 100;
		width = w - minX + 100;
		//init()
		init();
		
		//draw classUnits and links
		for(int i = 0; i < subVectUnitClass.size(); i++){
			UnitClass u = (UnitClass) subVectUnitClass.get(i);
			draw(u);
			
		}
		for(int i = 0; i < subVectLinks.size(); i++){
			Link l = (Link) subVectLinks.get(i);
			//System.out.println(i+"**"+l.startUnit.className+"->"+l.endUnit.className);
			draw(l);
		}
		
	}
	/**
	 * determines the links that are tied to the root
	 * @param c the classname of the root
	 * @return the vector of the links
	 */
	private Vector<Link> getChildren(String c){
		
		Vector<Link> vect = new Vector<Link>();
		for(int i = 0; i < vectLink.size(); i++){
			Link l = (Link)vectLink.get(i);
			if(l.startUnit.className.equals(c) && !vect.contains(l))
			{
				vect.add(l);
				if(!l.autoRefers)
					vect.addAll(getChildren(l.endUnit.className));
			}
		}
		return vect;
	}
	/**
	 * draws the diagram
	 *
	 */
	public void draw(){
		int h = 0, w = 0;
		int minY = 0, minX = 0;
		
		//create graph
		GraphHierarchyEnergy graph = new GraphHierarchyEnergy(this);
//		GraphSpringLayout graph = new GraphSpringLayout(this);
		graph.AssignCoordinates();
		
		System.out.println(graph.evaluate());
		
		if(vectClass.size() > 0){
			Vertice v = (Vertice) graph.Vertices.get(0);
			minX = (int)Math.round(v.x);
			minY = (int)Math.round(v.y);
			
		}
		//set coordinates classUnits
		for(int i = 0; i < graph.Vertices.size(); i++){
			
			Vertice v = (Vertice) graph.Vertices.get(i);
			if(!v.isVirtual){
				UnitClass u = (UnitClass) vectClass.get(v.index);
				u.x = (int)Math.round(v.x);
				u.y = (int)Math.round(v.y);
				v.x = u.x;
				v.y = u.y;
			
			
				//commpute height image and width
				if( h < u.y + u.getHeight())
					h = u.y + u.getHeight();
				if( w < u.x + u.getWidth())
					w = u.x + u.getWidth();
				if(u.y < minY)
					minY = u.y;
				if(u.x < minX)
					minX = u.x;
			}
		}
		
		//translate classunits if necessary
		
		for(int i = 0; i < vectClass.size(); i++){
			UnitClass u = (UnitClass) vectClass.get(i);
			u.x += - minX + 50;
			u.y += - minY + 50;		
		}

		//set coordinate links
		for(int i = 0; i < vectLink.size(); i++){
			Link l = (Link) vectLink.get(i);
			l.upDateCoordinates();
		}
		
		height = h - minY + 100;
		width = w - minX + 100;
		//init()
		init();
		
		//draw classUnits and links
		for(int i = 0; i < vectClass.size(); i++){
			UnitClass u = (UnitClass) vectClass.get(i);
			draw(u);
			
		}
		for(int i = 0; i < vectLink.size(); i++){
			Link l = (Link) vectLink.get(i);
			//System.out.println(i+"**"+l.startUnit.className+"->"+l.endUnit.className);
			draw(l);
		}
	}
	/**
	 * initializes the image
	 *
	 */	
	private void init(){
		
		img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
		g2d = (Graphics2D)img.getGraphics();		
		
		UnitClass.setG2d(g2d);
		
		//set background color
		g2d.fill(new Rectangle(0, 0, width, height));
	}
	/**
	 * save the diagram image on the specified format in the specified file 
	 * @param f the file to save the image
	 * @param typeImg the image type (png, jpeg....)
	 * @throws IOException
	 */
	public void save(File f, String typeImg) throws IOException{
		ImageIO.write((BufferedImage)img, typeImg, f);
	}
	/**
	 * draws a class in the diagram's image
	 * @param unit the class to draw
	 */
	public void draw(UnitClass unit){
		g2d.setColor(fillColor);
		g2d.fill(unit.getRectangle());
		g2d.setColor(BorderColor);
		g2d.draw(unit.getRectangle());
		g2d.setColor(fontColor);
		g2d.drawString(unit.getClassName(), unit.getXName(), unit.getYName());
	}
	/**
	 * draws a link in the diagram's image
	 * @param l the link to draw
	 */
	public void draw(Link l){
		g2d.setColor(BorderColor);
		l.draw(g2d);
	}
	/**
	 * return the reference of a class 
	 * @param u 
	 * @return
	 */
	public UnitClass getUnitClass(UnitClass u) {
		UnitClass unit = null;
		boolean res = false;
		int i = 0;
		while(i < vectClass.size() && !res){
			UnitClass temp = (UnitClass) vectClass.get(i);
			if(temp.isEqual(u))
			{
				res = true;
				unit = temp;
			}
			i++;
		}
		return unit;
	}
	/**
	 * initialize the temporary number of instances of the children of a parent class
	 * @param parent the parent class
	 */
	public void initNbInstancetemp(UnitClass parent){
		for(int i = 0; i < vectLink.size(); i++){
			Link l = (Link)vectLink.get(i);
			if(parent != null){
				if(l.startUnit.isEqual(parent)){
					if(l.nbInstanceTemp > l.nbInstance)
						l.nbInstance = l.nbInstanceTemp;
					l.nbInstanceTemp = 0;
				}
			}
			else
				if(l.nbInstanceTemp > l.nbInstance)
					l.nbInstance = l.nbInstanceTemp;
		}
	}
	
	/**
	 * save the actual cordinalities of the children of a parent class
	 * @param parent
	 * @return vector of points, the absciss is the index of the link, the ordinate is the number of instances
	 */
	public Vector getNbInstanceTemp(UnitClass parent){
		Vector<Point> result = new Vector<Point>();
		for(int i = 0; i < vectLink.size(); i++){
			Link l = (Link)vectLink.get(i);
			if(parent != null){
				if(l.startUnit.isEqual(parent)){
					result.add(new Point(i, l.nbInstanceTemp));
				}
			}
		}
		return result;
	}
	/**
	 * reinstore the cardinality relative to a parent saved with getNbInstanceTemp
	 * @param vectPoints vector of points whose abscis is a link index and the coordinate the value of nbInstanceTemp
	 */
	public void setNbInstancesTemp(Vector vectPoints){
		for(int i = 0; i < vectPoints.size(); i++){
			Point p = (Point)vectPoints.get(i);
			Link l = (Link)vectLink.get(p.x);
				l.nbInstanceTemp = p.y;
		}
	}
	/**
	 * determines a vector of classes names
	 * @return
	 */
	public Vector getClassNames(){
		Vector<String> vect = new Vector<String>();
		for(int i = 0; i < vectClass.size(); i++){
			UnitClass u = (UnitClass)vectClass.get(i);
			vect.add(u.className);
		}
		Collections.sort(vect);
		return vect;
	}
}
