package com.rockwellcollins.atc.agree.linking;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IProjectDescription;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.xtext.linking.impl.IllegalNodeException;
import org.eclipse.xtext.nodemodel.INode;
import org.eclipse.xtext.resource.IEObjectDescription;
import org.osate.aadl2.Aadl2Package;
import org.osate.aadl2.DataType;
import org.osate.aadl2.Element;
import org.osate.aadl2.PropertyValue;
import org.osate.aadl2.UnitLiteral;
import org.osate.aadl2.UnitsType;
import org.osate.aadl2.util.Aadl2Util;
import org.osate.xtext.aadl2.properties.linking.PropertiesLinkingService;
import org.osate.xtext.aadl2.properties.util.EMFIndexRetrieval;

import com.rockwellcollins.atc.agree.agree.CalenStatement;
import com.rockwellcollins.atc.agree.agree.GetPropertyExpr;
import com.rockwellcollins.atc.agree.agree.NestedDotID;
import com.rockwellcollins.atc.agree.agree.NodeEq;
import com.rockwellcollins.atc.agree.agree.RecordExpr;
import com.rockwellcollins.atc.agree.agree.RecordType;
import com.rockwellcollins.atc.agree.agree.RecordUpdateExpr;

public class AgreeLinkingService extends PropertiesLinkingService {
    public AgreeLinkingService() {
        super();
    }

    @Override
    public List<EObject> getLinkedObjects(EObject context, EReference reference, INode node)
            throws IllegalNodeException {
        String name = getCrossRefNodeAsString(node);

        if (context instanceof PropertyValue) {
            return findUnitLiteralAsList((Element) context, name);
        }

        if (context instanceof NestedDotID
                || context instanceof NodeEq
                || context instanceof CalenStatement
                || context instanceof RecordExpr
                || context instanceof RecordType
                || context instanceof GetPropertyExpr
                || context instanceof RecordUpdateExpr) {

            EObject e = findClassifier(context, reference, name);
            
            //hack to fix some strange linking behavior by osate
            if(e instanceof DataType){
            	e = null;
            }
            if (e != null) {
                return Collections.singletonList(e);
            }

            // This code will only link to objects in the projects visible from the current project
            Iterable<IEObjectDescription> allObjectTypes = EMFIndexRetrieval
                    .getAllEObjectsOfTypeInWorkspace(context, reference.getEReferenceType());

            String contextProject = context.eResource().getURI().segment(1);
            List<String> visibleProjects = getVisibleProjects(contextProject);

            for (IEObjectDescription eod : allObjectTypes) {
                if (eod.getName().toString().equalsIgnoreCase(name)) {
                    EObject res = eod.getEObjectOrProxy();
                    res = EcoreUtil.resolve(res, context.eResource().getResourceSet());
                    if (!Aadl2Util.isNull(res)) {
                        URI linkUri = res.eResource().getURI();
                        String linkProject = linkUri.segment(1);
                        if (visibleProjects.contains(linkProject)) {
                            return Collections.singletonList(res);
                        }
                    }
                }
            }
        }

        return super.getLinkedObjects(context, reference, node);
    }

    private static List<String> getVisibleProjects(String contextProjectName) {
        List<String> result = new ArrayList<>();
        result.add(contextProjectName);

        IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
        IProject contextProject = root.getProject(URI.decode(contextProjectName));
        try {
            IProjectDescription description = contextProject.getDescription();
            for (IProject referencedProject : description.getReferencedProjects()) {
                result.add(URI.encodeSegment(referencedProject.getName(), false));
            }
        } catch (CoreException ex) {
            ex.printStackTrace();
        }

        return result;
    }

    private static List<EObject> findUnitLiteralAsList(Element context, String name) {
        EObject e = findUnitLiteral(context, name);
        if (e == null) {
            return Collections.<EObject> emptyList();
        }
        return Collections.singletonList(e);
    }

    final private static EClass UNITS_TYPE = Aadl2Package.eINSTANCE.getUnitsType();

    private static UnitLiteral findUnitLiteral(Element context, String name) {
        // TODO: Scope literals by type, but how to do we know the type of an
        // expression?
        for (IEObjectDescription desc : EMFIndexRetrieval
                .getAllEObjectsOfTypeInWorkspace(UNITS_TYPE)) {
            UnitsType unitsType = (UnitsType) EcoreUtil.resolve(desc.getEObjectOrProxy(), context);
            UnitLiteral literal = unitsType.findLiteral(name);
            if (literal != null) {
                return literal;
            }
        }

        return null;
    }
}

@@ -1842,16 +1842,59 @@
 g(node);
+%0A        name = name.replaceAll(%22::%22, %22.%22);
 %0A%0A      

Replace :: with . when linking OSATE objects