/*
 * IBM Confidential
 * OCO Source Materials
 * JP720130074D
 * (C) Copyright IBM Corp. 2009
 */
package com.ibm.trinity.rhapsody.ecore;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EDataType;
import org.eclipse.emf.ecore.EOperation;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.EParameter;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.EcoreFactory;
import org.eclipse.emf.ecore.EcorePackage;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.xmi.impl.EcoreResourceFactoryImpl;

import com.telelogic.rhapsody.core.IRPCollection;
import com.telelogic.rhapsody.core.IRPGraphElement;
import com.telelogic.rhapsody.core.IRPGraphicalProperty;
import com.telelogic.rhapsody.core.IRPModelElement;

public class ListInterfaceMain {
	
	private static Map<Class<?>, EClassifier> classMap = new HashMap<Class<?>, EClassifier>();
	private static Map<Method, EOperation> operationMap = new HashMap<Method, EOperation>();

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		try {
			List<Class<?>> classes = loadAllClasses();
			List<Class<?>> sortedModelElementClasses = getSortedModelElementClasses(classes);
			
			EPackage rhpInternalPackage = EcoreFactory.eINSTANCE.createEPackage();
			rhpInternalPackage.setName("rhpInternal");
			rhpInternalPackage.setNsPrefix("rhpInternal");
			rhpInternalPackage.setNsURI("http://w3.ibm.com/rhpInternal");
			// create EClasses
			for (Class<?> modelElement : sortedModelElementClasses) {
				addModelElementClass(rhpInternalPackage, modelElement);
			}
			// create methods
			for (Class<?> modelElement : sortedModelElementClasses) {
				EClass clazz = (EClass)rhpInternalPackage.getEClassifier(modelElement.getSimpleName());
				Method[] declaredMethods = modelElement.getDeclaredMethods();
				for (Method method : declaredMethods) {
					addMethodsToClass(clazz, method);
				}
			}
//			// create properties from getter
//			for (Class<?> modelElement : sortedModelElementClasses) {
//				EClass clazz = (EClass)rhpInternalPackage.getEClassifier(modelElement.getSimpleName());
//				Method[] declaredMethods = modelElement.getDeclaredMethods();
//				for (Method method : declaredMethods) {
//					addPropertyToClass(clazz, method);
//				}
//			}
			
			
			
			// dump ecore file
			EcoreResourceFactoryImpl factory = new EcoreResourceFactoryImpl();
			Resource res = factory.createResource(URI.createFileURI("rhpInternalRaw"));
			res.getContents().add(rhpInternalPackage);
			res.save(null);
			
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	private static void addPropertyToClass(EClass clazz, Method method) {
		String methodName = method.getName();
		if (!methodName.startsWith("get"))
			return;
		String propertyName = toLowerFirst(methodName.substring(3));
		EOperation getterOp = operationMap.get(method);
		if (!getterOp.getEParameters().isEmpty())
			return;
		
		EClassifier propType = null;
		boolean isMany = false;
		if (getterOp.isMany() && getterOp.getEType() == EcorePackage.Literals.EJAVA_OBJECT) {
			// IRPCollection
			isMany = true;
			propType = guessTypeFromPropName(propertyName, clazz.getEPackage());
		} else if (getterOp.getEType() == EcorePackage.Literals.EINT) {
			if (propertyName.startsWith("is") || propertyName.startsWith("has")) {
				propType = EcorePackage.Literals.EBOOLEAN;
			}
		}
		
		EOperation setterOp = null;
		EOperation addOp = null;
		EOperation deleteOp = null;
		EOperation removeOp = null;
		
		if (isMany) {
			addOp = findOp(clazz, "add", propertyName);
			propType = updatePropTypeIfPossibleAndNecesary(propType, addOp);
			deleteOp = findOp(clazz, "delete", propertyName);
			propType = updatePropTypeIfPossibleAndNecesary(propType, deleteOp);
			removeOp = findOp(clazz, "remove", propertyName);
			propType = updatePropTypeIfPossibleAndNecesary(propType, removeOp);
		} else {
			setterOp = findOp(clazz, "set", propertyName);
			propType = updatePropTypeIfPossibleAndNecesary(propType, setterOp);
		}
		
		if (propType == null) {
			propType = getterOp.getEType(); // default type
			if (EcorePackage.Literals.EJAVA_OBJECT.equals(propType)) {
				System.out.println("property type," + clazz.getName() + "#" + propertyName + ",");
			}
		}

		EStructuralFeature feature;
		if (propType instanceof EDataType) {
			EAttribute att = EcoreFactory.eINSTANCE.createEAttribute();
			att.setName(propertyName);
			att.setEType(propType);
			att.setUpperBound(isMany ? -1 : 1);
			feature = att;
		} else {
			EReference ref = EcoreFactory.eINSTANCE.createEReference();
			ref.setName(propertyName);
			ref.setEType(propType);
			ref.setUpperBound(isMany ? -1 : 1);
			feature = ref;
		}
		
		if (isMany) {
			if (addOp == null && deleteOp == null && removeOp == null) {
				feature.setChangeable(false);
				feature.setDerived(true);
				feature.setTransient(true);
			}
			if (deleteOp != null && feature instanceof EReference) {
				((EReference)feature).setContainment(true);
			}
			
		} else {
			if (setterOp == null) {
				feature.setChangeable(false);
				feature.setDerived(true);
				feature.setTransient(true);
			}
		}
		
		
		
		clazz.getEStructuralFeatures().add(feature);
	}

	private static EOperation findOp(EClass clazz, String verb, String propertyName) {
		String[] candidates = getSingularPropertyNames(propertyName);
		for (String candidate : candidates) {
			String opNameToFind = "internal" + toUpperFirst(verb) + toUpperFirst(candidate);
			for (EOperation op : clazz.getEOperations()) {
				if (opNameToFind.equals(op.getName())) {
					return op;
				}
			}
		}
		return null;
	}

	private static EClassifier updatePropTypeIfPossibleAndNecesary(EClassifier propType, EOperation setterOp) {
		if (propType == null) {
			if (setterOp != null && setterOp.getEParameters().size() == 1) {
				return setterOp.getEParameters().get(0).getEType();
			}
		}
		return propType;
	}

	private static EClassifier guessTypeFromPropName(String propertyName, EPackage pkg) {
		for (int i=0; i<propertyName.length(); i++) {
			for (int j=0; j<2; j++) {
				String s;
				if (j == 0) {
					s = propertyName.substring(0, propertyName.length() - i);
				} else {
					s = propertyName.substring(i);
				}
				String[] names = getSingularPropertyNames(s);
				for (String t : names) {
					String clsfName = "IRP" + toUpperFirst(t);
					EClassifier clsf = pkg.getEClassifier(clsfName);
					if (clsf != null)
						return clsf;
				}
			}
		}
		return null;
	}
	
	private static String[] getSingularPropertyNames(String propertyName) {
		List<String> result = new ArrayList<String>();
		result.add(propertyName);
		for (String config : new String[] {"ies$-y", "es$-e", "es$-", "s$-"}) {
			// replace "ies" to "y", "es" to "e", ... then try to find eclassifier
			String[] suffix = config.split("-");
			suffix = Arrays.copyOf(suffix, 2);
			for (int k = 0; k < suffix.length; k++) {
				if (suffix[k] == null) suffix[k] = "";
			}
			String s = propertyName.replaceAll(suffix[0], suffix[1]);
			if (!propertyName.equals(s)) {
				result.add(s);
			}
		}
		return (String[]) result.toArray(new String[result.size()]);
	}

	private static String toUpperFirst(String s) {
		return s.length() > 0 ? s.substring(0, 1).toUpperCase() + s.substring(1) : s;
	}

	private static String toLowerFirst(String s) {
		return s.length() > 0 ? s.substring(0, 1).toLowerCase() + s.substring(1) : s;
	}
	
	private static void addMethodsToClass(EClass clazz, Method method) {
		String methodName = method.getName();
//		methodName = "internal" + Character.toUpperCase(methodName.charAt(0)) + methodName.substring(1);
		
		EOperation op = EcoreFactory.eINSTANCE.createEOperation();
		op.setName(methodName);
		clazz.getEOperations().add(op);
		
		Class<?> returnType = method.getReturnType();
		EClassifier eReturnType = findDataType(returnType, clazz.getEPackage());
		if (eReturnType == null && returnType == IRPCollection.class) {
			// TODO special
			op.setEType(EcorePackage.Literals.EJAVA_OBJECT);
			op.setOrdered(true);
			op.setLowerBound(0);
			op.setUpperBound(-1);
		} else {
			op.setEType(eReturnType);
		}
		
		int argCount = 0;
		Class<?>[] parameterTypes = method.getParameterTypes();
		for (Class<?> parameterType : parameterTypes) {
			String argName = "arg" + (++ argCount);
			EParameter param = EcoreFactory.eINSTANCE.createEParameter();
			param.setName(argName);
			EClassifier eParamType = findDataType(parameterType, clazz.getEPackage());
			if (eParamType == null) {
				System.err.println("Class: " + clazz.getName());
				System.err.println("Operation: " + method.getName());
				System.err.println("Cannot find type for paramter class: " + parameterType);
				eParamType = clazz.getEPackage().getEClassifier("IRPModelElement");
				param.setUpperBound(-1);
			}
			param.setEType(eParamType);
			op.getEParameters().add(param);
		}
		operationMap.put(method, op);
	}

	private static EClassifier findDataType(Class<?> parameterType, EPackage ePackage) {
		if (parameterType == Void.TYPE) 
			return null;
//		if (parameterType == IRPCollection.class)
//			return null;
		
		// find from Rhapsody package
		EClassifier result = ePackage.getEClassifier(parameterType.getSimpleName());
		if (result != null)
			return result;
		// find from ecore
		if (classMap.isEmpty()) {
			for (EClassifier clsf : EcorePackage.eINSTANCE.getEClassifiers()) {
				if (clsf.getInstanceClass() != null) {
					classMap.put(clsf.getInstanceClass(), clsf);
				}
			}
		}
		result = classMap.get(parameterType);
		if (result != null)
			return result;
		
		System.err.println("** Cannot find type for class: " + parameterType);
		return null;
	}
	
	

	private static void addModelElementClass(EPackage rhpPackage,
			Class<?> modelElement) {
		EClass clazz = EcoreFactory.eINSTANCE.createEClass();
		// class name
		clazz.setName(modelElement.getSimpleName());
		rhpPackage.getEClassifiers().add(clazz);
		
		// super class
		Class<?>[] superclass = modelElement.getInterfaces();
		for (Class<?> class1 : superclass) {
			EClass superClazz = (EClass)rhpPackage.getEClassifier(class1.getSimpleName());
			if (superClazz != null) {
				clazz.getESuperTypes().add(superClazz);
			}
		}
	}

	private static List<Class<?>> getSortedModelElementClasses(List<Class<?>> allClasses) {
		List<Class<?>> result = new ArrayList<Class<?>>();
		for (Class<?> class1 : allClasses) {
			if (IRPModelElement.class.isAssignableFrom(class1)) {
				result.add((Class<?>)class1);
			}
			if (IRPGraphElement.class.isAssignableFrom(class1)) {
				result.add((Class<?>)class1);
			}
			if (IRPGraphicalProperty.class.isAssignableFrom(class1)) {
				result.add((Class<?>)class1);
			}
			if (IRPCollection.class.isAssignableFrom(class1)) {
				result.add((Class<?>)class1);
			}
		}
		Collections.sort(result, new Comparator<Class<?>>() {
			@Override
			public int compare(Class<?> object1, Class<?> object2) {
				if (object1.isAssignableFrom(object2)) return -1;
				if (object2.isAssignableFrom(object1)) return 1;
				return 0;
			}
		});
		return result;
	}

	private static List<Class<?>> loadAllClasses() throws Exception {
		List<Class<?>> result = new ArrayList<Class<?>>();
		InputStream stream = ListInterfaceMain.class.getResourceAsStream("RhapsodyClasses.txt");
		BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
		String line;
		while ((line = reader.readLine()) != null) {
			result.add(Class.forName("com.telelogic.rhapsody.core." + line));
		}
		return result;
	}
	
}
