package com.hammurapi.render.emf;

import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;

import org.eclipse.emf.common.util.TreeIterator;
import org.eclipse.emf.ecore.EAnnotation;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EDataType;
import org.eclipse.emf.ecore.EModelElement;
import org.eclipse.emf.ecore.ENamedElement;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.ETypedElement;
import org.eclipse.gmf.runtime.notation.Diagram;
import org.eclipse.gmf.runtime.notation.Node;

import com.hammurapi.common.Context;
import com.hammurapi.common.IdentityManager;
import com.hammurapi.render.RenderHelper;

/**
 * Helper class for rendering EMF model documentation.
 * @author Pavel Vlasov
 *
 */
public class ModelRenderHelper extends RenderHelper {

	private Map<EObject, List<Diagram>> diagramMap;
	private Model model;
	
	/**
	 * Constructor
	 * @param identityManager IdentityManager.
	 * @param outputDir Output directory.
	 * @param diagramMap Maps diagram to a list of diagram elements.
	 * @param model Model.
	 * @param env Environment.
	 * @param context Context.
	 * @param locale Locale.
	 */
	public ModelRenderHelper(
			IdentityManager<?> identityManager, 
			File outputDir, 
			Map<EObject, 
			List<Diagram>> diagramMap, 
			Model model, 
			Map<String, Object> env, 
			Context context, 
			Locale locale, 
			boolean http) {
		
		super(identityManager, outputDir, env, context, locale, http);
		this.diagramMap = diagramMap;
		this.model = model;
	}
	
	/**
	 * @param obj
	 * @return true if given model element contains sub-elements.
	 */
	public boolean hasChildren(EObject obj) {
		if (obj instanceof EPackage) {
			EPackage pkg = (EPackage) obj;
			List<Diagram> diagrams = diagramMap.get(pkg);
			return !pkg.getESubpackages().isEmpty() || !pkg.getEClassifiers().isEmpty() || (diagrams!=null && !diagrams.isEmpty());				
		} else if (obj instanceof EClass) {
			EClass cls = (EClass) obj;
			return !cls.getEAttributes().isEmpty() 
			|| !cls.getEOperations().isEmpty() 
			|| !cls.getETypeParameters().isEmpty() 
			|| !cls.getEReferences().isEmpty();
		}
		
		return false; // For now
	}

	/**
	 * @param obj
	 * @return List of diagrams in the model.
	 */
	public List<Diagram> getDiagrams(EObject obj) {
		List<Diagram> diagrams = diagramMap.get(obj);
		if (diagrams==null) {
			diagrams = Collections.emptyList();
		}
		
		Collections.sort(diagrams, new Comparator<Diagram>() {

			@Override
			public int compare(Diagram o1, Diagram o2) {
				if (o1==null) {
					return o2==null ? 0 : 1;
				}
				
				if (o2==null) {
					return o1==null ? 0 : -1;
				}
				
				String n1 = o1.getName();
				String n2 = o2.getName();
				
				if (n1==null) {
					return n2==null ? 0 : 1;
				}
				
				return n2==null ? -1 : n1.compareTo(n2);
			}
			
		});
		
		return diagrams;
	}

	/**
	 * @param modelElement
	 * @return Element documentation.
	 */
	public String getDoc(EModelElement modelElement) {
		EAnnotation genModel = modelElement.getEAnnotation("http://www.eclipse.org/emf/2002/GenModel");
		if (genModel==null) {
			return "";
		}
		return genModel.getDetails().get("documentation");
	}
	
	/**
	 * @param modelElement
	 * @return First sentence from element documentation.
	 */
	public String firstSentence(EModelElement modelElement) {
		String ret = getDoc(modelElement);
		int idx = ret.indexOf('.');
		if (idx!=-1 && ret.length()>idx+1) {
			ret = ret.substring(0, idx+1);
		}
		return ret;
	}

	/**
	 * @param pkg
	 * @return List of classes in the package.
	 */
	public List<EClass> classes(EPackage pkg) {
		List<EClass> ret = new ArrayList<EClass>();		
		for (EClassifier c: pkg.getEClassifiers()) {
			if (c instanceof EClass) {
				ret.add((EClass) c);
			}
		}
		return sort(ret);
	}

	/**
	 * @param pkg
	 * @return List of data types in the package.
	 */
	public List<EDataType> dataTypes(EPackage pkg) {
		List<EDataType> ret = new ArrayList<EDataType>();
		for (EClassifier c: pkg.getEClassifiers()) {
			if (c instanceof EDataType) {
				ret.add((EDataType) c);
			}
		}
		return sort(ret);
	}

	/**
	 * @param modelElement
	 * @return true  if model element is abstract.
	 */
	public boolean isAbstract(EModelElement modelElement) {
		return modelElement instanceof EClass && ((EClass) modelElement).isAbstract();
	}
	
//	static AtomicInteger counter = new AtomicInteger();

	/**
	 * Sorts model elements list by model element name.
	 * @param <T>
	 * @param list
	 * @return
	 */
	public <T extends ENamedElement> List<T> sort(List<T> list) {
//		System.out.println(counter.getAndIncrement());
		List<T> ret = new ArrayList<T>(list);
		Collections.sort(ret, new Comparator<T>() {

			@Override
			public int compare(T o1, T o2) {
				if (o1==null) {
					return o2==null ? 0 : 1;
				}
				
				if (o2==null) {
					return o1==null ? 0 : -1;
				}
				
				String n1 = o1.getName();
				String n2 = o2.getName();
				
				if (n1==null) {
					return n2==null ? 0 : 1;
				}
				
				return n2==null ? -1 : n1.compareTo(n2);
			}
			
		});
		return ret;
	}
	
	/**
	 * @param ete
	 * @return bounds string.
	 */
	public String bounds(ETypedElement ete) {
		if (ete.getLowerBound()==ete.getUpperBound()) {
			return String.valueOf(ete.getLowerBound());
		}
		
		if (ete.getLowerBound()==0 && ete.getUpperBound()==-1) {
			return "*";
		}
		
		if (ete.getUpperBound()==-1) {
			return ete.getLowerBound()+"..*";
		}
		
		return ete.getLowerBound()+".."+ete.getUpperBound();
	}
	
	/**
	 * @param cls
	 * @return List of direct subtypes of given type. 
	 */
	public List<EClass> getSubTypes(EClass cls) {
		List<EClass> ret = new ArrayList<EClass>();
		for (EObject rp: model.getRootPackages()) {
			TreeIterator<EObject> rit = rp.eAllContents();
			while (rit.hasNext()) {
				EObject obj = rit.next();
				if (obj instanceof EClass && ((EClass) obj).getESuperTypes().contains(cls)) {
					ret.add((EClass) obj);
				}
			}
		}
		return ret;
	}

	/**
	 * @param cls
	 * @return List of all (direct and indirect) subtypes of given type.
	 */
	public List<EClass> getAllSubTypes(EClass cls) {
		List<EClass> ret = new ArrayList<EClass>();
		for (EObject rp: model.getRootPackages()) {
			TreeIterator<EObject> rit = rp.eAllContents();
			while (rit.hasNext()) {
				EObject obj = rit.next();
				if (obj instanceof EClass && ((EClass) obj).getEAllSuperTypes().contains(cls) && !ret.contains(obj)) {
					ret.add((EClass) obj);
				}
			}
		}
		return ret;
	}

	/**
	 * @param diagram
	 * @return Collection of diagram elements.
	 * @throws Exception
	 */
	public Collection<EObject> getDiagramElements(Diagram diagram) throws Exception {
		List<EObject> diagramElements = new ArrayList<EObject>();
		for (Object o: diagram.getChildren()) {
			if (o instanceof Node) {
				EObject de = ((Node) o).getElement();
				if (de!=null) {
					if (de instanceof ENamedElement && ((ENamedElement) de).getName()!=null) { 
						diagramElements.add(de);
					}
				}
			}
		}
		return diagramElements;
	}

//  It is very hard to make it work standalone. It is kept here for the future 
//  implementations when it is invoked from Eclipse UI.
//	public void copyDiagramToImageFile(Diagram diagram) throws CoreException {
//		CopyToImageUtil ctiu = new CopyToImageUtil();
//		ImageFileFormat format = ImageFileFormat.GIF;
//		IPath destination = new Path(new File(getOutputDir(), getId(diagram)+".gif").getAbsolutePath());
//		ctiu.copyToImage(diagram, destination, format, new NullProgressMonitor(), PreferencesHint.USE_DEFAULTS);
//	}
		
}
