/**
 * Copyright (c) 2011-2012 Eclipse contributors and others.
 * All rights reserved.   This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v2.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v20.html
 */
package org.eclipse.emf.ecore.xcore.ui.contentassist;


import java.util.Set;

import org.eclipse.emf.codegen.ecore.genmodel.GenClass;
import org.eclipse.emf.codegen.ecore.genmodel.GenDataType;
import org.eclipse.emf.codegen.ecore.genmodel.GenFeature;
import org.eclipse.emf.codegen.ecore.genmodel.GenTypeParameter;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.xcore.XAttribute;
import org.eclipse.emf.ecore.xcore.XReference;
import org.eclipse.emf.ecore.xcore.XStructuralFeature;
import org.eclipse.emf.ecore.xcore.XcorePackage;
import org.eclipse.emf.ecore.xcore.mappings.XcoreMapper;
import org.eclipse.emf.ecore.xcore.services.XcoreGrammarAccess;
import org.eclipse.jface.text.contentassist.ICompletionProposal;
import org.eclipse.jface.viewers.StyledString;
import org.eclipse.swt.graphics.Image;
import org.eclipse.xtext.AbstractElement;
import org.eclipse.xtext.Assignment;
import org.eclipse.xtext.CrossReference;
import org.eclipse.xtext.GrammarUtil;
import org.eclipse.xtext.common.types.xtext.ui.JdtVariableCompletions;
import org.eclipse.xtext.common.types.xtext.ui.JdtVariableCompletions.VariableType;
import org.eclipse.xtext.conversion.impl.QualifiedNameValueConverter;
import org.eclipse.xtext.naming.IQualifiedNameConverter;
import org.eclipse.xtext.resource.IEObjectDescription;
import org.eclipse.xtext.scoping.IScope;
import org.eclipse.xtext.scoping.IScopeProvider;
import org.eclipse.xtext.ui.editor.contentassist.ConfigurableCompletionProposal;
import org.eclipse.xtext.ui.editor.contentassist.ConfigurableCompletionProposal.IReplacementTextApplier;
import org.eclipse.xtext.ui.editor.contentassist.ContentAssistContext;
import org.eclipse.xtext.ui.editor.contentassist.ICompletionProposalAcceptor;

import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.inject.Inject;


/**
 */
public class XcoreProposalProvider extends AbstractXcoreProposalProvider
{
  @Inject
  IScopeProvider xcoreScopeProvider;

  @Inject
  private IQualifiedNameConverter qualifiedNameConverter;

  @Inject
  QualifiedNameValueConverter qualifiedNameValueConverter;

  @Inject
  private XcoreMapper mapper;

  @Inject
  XcoreGrammarAccess xcoreGrammarAccess;

  @Inject
  private JdtVariableCompletions completions;

  @Override
  public void completeXReference_Opposite(EObject model, Assignment assignment, ContentAssistContext context, ICompletionProposalAcceptor acceptor)
  {
    final IReplacementTextApplier textApplier =
      new OppositeReplacementTextApplier
        ((XReference)model,
         context.getViewer(),
         xcoreScopeProvider.getScope(model, XcorePackage.Literals.XREFERENCE__OPPOSITE),
         mapper,
         qualifiedNameConverter,
         qualifiedNameValueConverter);
    ICompletionProposalAcceptor oppositeAware =
      new ICompletionProposalAcceptor.Delegate(acceptor)
      {
        @Override
        public void accept(ICompletionProposal proposal)
        {
          if (proposal instanceof ConfigurableCompletionProposal)
          {
            ((ConfigurableCompletionProposal)proposal).setTextApplier(textApplier);
          }
          super.accept(proposal);
        }
      };
    super.completeXReference_Opposite(model, assignment, context, oppositeAware);
  }

  @Override
  public void completeXGenericType_Type(EObject model, Assignment assignment, ContentAssistContext context, ICompletionProposalAcceptor acceptor)
  {
    IScope scope = xcoreScopeProvider.getScope(model, XcorePackage.Literals.XGENERIC_TYPE__TYPE);
    super.completeXGenericType_Type(model, assignment, context, getImportingProposalAcceptor(scope, context, acceptor));
  }

  @Override
  public void completeXAnnotation_Source(EObject model, Assignment assignment, ContentAssistContext context, ICompletionProposalAcceptor acceptor)
  {
    IScope scope = xcoreScopeProvider.getScope(model, XcorePackage.Literals.XANNOTATION__SOURCE);
    super.completeXAnnotation_Source(model, assignment, context, getImportingProposalAcceptor(scope, context, acceptor));
  }

  private ICompletionProposalAcceptor getImportingProposalAcceptor(IScope scope, ContentAssistContext context, ICompletionProposalAcceptor acceptor)
  {
    final IReplacementTextApplier textApplier =
      new ImportingTypesProposalProvider.FQNImporter
        (context.getResource(),
         context.getViewer(),
         scope,
         qualifiedNameConverter,
         qualifiedNameValueConverter,
         qualifiedNameValueConverter);
    ICompletionProposalAcceptor scopeAwareAcceptor =
      new ICompletionProposalAcceptor.Delegate(acceptor)
      {
        @Override
        public void accept(ICompletionProposal proposal)
        {
          if (proposal instanceof ConfigurableCompletionProposal)
          {
            ((ConfigurableCompletionProposal)proposal).setTextApplier(textApplier);
          }
          super.accept(proposal);
        }
      };
    return scopeAwareAcceptor;
  }

  @Override
  public void completeXReference_Name(EObject model, Assignment assignment, ContentAssistContext context, ICompletionProposalAcceptor acceptor)
  {
    completeXStructuralFeature_Name(model, assignment, context, acceptor);
    super.completeXReference_Name(model, assignment, context, acceptor);
  }

  @Override
  public void completeXAttribute_Name(EObject model, Assignment assignment, ContentAssistContext context, ICompletionProposalAcceptor acceptor)
  {
    completeXStructuralFeature_Name(model, assignment, context, acceptor);
    super.completeXAttribute_Name(model, assignment, context, acceptor);
  }

  protected Set<String> getAllKeywords()
  {
    return GrammarUtil.getAllKeywords(xcoreGrammarAccess.getGrammar());
  }

  public void completeXStructuralFeature_Name(EObject model, Assignment assignment, final ContentAssistContext context, final ICompletionProposalAcceptor acceptor)
  {
    if (model instanceof XStructuralFeature)
    {
      XStructuralFeature xStructuralFeature = (XStructuralFeature)model;
      EStructuralFeature eStructuralFeature = (EStructuralFeature)mapper.getEcore(xStructuralFeature);
      Set<String> alreadyTaken = Sets.newHashSet(getAllKeywords());
      if (eStructuralFeature != null)
      {
        EClass eContainingClass = eStructuralFeature.getEContainingClass();
        alreadyTaken.addAll
          (Lists.transform
            (eContainingClass.getEAllStructuralFeatures(),
             new Function<EStructuralFeature, String>()
             {
               public String apply(EStructuralFeature input)
               {
                 String name = input.getName();
                 return name == null ? "_" : name;
               }
             }));
      }
      completions.getVariableProposals
        (model,
         XcorePackage.Literals.XTYPED_ELEMENT__TYPE,
         VariableType.INSTANCE_FIELD,
         alreadyTaken,
         new JdtVariableCompletions.CompletionDataAcceptor()
         {
           public void accept(String replaceText, StyledString label, Image image)
           {
             acceptor.accept(createCompletionProposal(replaceText, label, image, context));
          }
        });
    }
  }

  @Override
  protected void lookupCrossReference(CrossReference crossReference, EReference reference, ContentAssistContext contentAssistContext, ICompletionProposalAcceptor acceptor, Predicate<IEObjectDescription> filter)
  {
    if (reference == XcorePackage.Literals.XREFERENCE__OPPOSITE)
    {
      XReference xReference = (XReference)contentAssistContext.getCurrentModel();
      final EStructuralFeature eReference = mapper.getMapping(xReference).getEStructuralFeature();
      final EClass eClass = eReference.getEContainingClass();
      final Predicate<IEObjectDescription> baseFilter = filter;
      filter = new Predicate<IEObjectDescription>()
        {
          public boolean apply(IEObjectDescription input)
          {
            // Filter out features that aren't of the correct type to be a valid opposite.
            //
            GenFeature genFeature = (GenFeature)input.getEObjectOrProxy();
            EStructuralFeature eStructuralFeature = genFeature.getEcoreFeature();
            return eStructuralFeature.getEType() == eClass && eStructuralFeature != eReference && baseFilter.apply(input);
          }
        };
    }
    else if (reference == XcorePackage.Literals.XGENERIC_TYPE__TYPE)
    {
      EObject eObject = contentAssistContext.getCurrentModel();
      ImmutableList<AbstractElement> firstSetGrammarElements = contentAssistContext.getFirstSetGrammarElements();
      if (firstSetGrammarElements.contains(xcoreGrammarAccess.getXAttributeAccess().getTypeAssignment_3()) ||
            firstSetGrammarElements.contains(xcoreGrammarAccess.getXAttributeAccess().getNameAssignment_5()) ||
            firstSetGrammarElements.contains(xcoreGrammarAccess.getXGenericTypeAccess().getTypeAssignment_0()) && eObject instanceof XAttribute)
      {
        final Predicate<IEObjectDescription> baseFilter = filter;
        filter = new Predicate<IEObjectDescription>()
          {
            public boolean apply(IEObjectDescription input)
            {
              // Filter out types that aren't data types.
              //
              EObject eObjectOrProxy = input.getEObjectOrProxy();
              return (eObjectOrProxy instanceof GenDataType || eObjectOrProxy instanceof GenTypeParameter) && baseFilter.apply(input);
            }
          };
      }
      else if (firstSetGrammarElements.contains(xcoreGrammarAccess.getXReferenceAccess().getTypeAssignment_4()) ||
                firstSetGrammarElements.contains(xcoreGrammarAccess.getXGenericTypeAccess().getTypeAssignment_0()) && eObject instanceof XReference ||
                firstSetGrammarElements.contains(xcoreGrammarAccess.getXClassAccess().getSuperTypesAssignment_5_1()) ||
                firstSetGrammarElements.contains(xcoreGrammarAccess.getXClassAccess().getSuperTypesAssignment_5_2_1()))
      {
        final Predicate<IEObjectDescription> baseFilter = filter;
        filter = new Predicate<IEObjectDescription>()
          {
            public boolean apply(IEObjectDescription input)
            {
              // Filter out types that aren't classes.
              //
              EObject eObjectOrProxy = input.getEObjectOrProxy();
              return (eObjectOrProxy instanceof GenClass || eObjectOrProxy instanceof GenTypeParameter) && baseFilter.apply(input);
            }
          };
      }
    }
    super.lookupCrossReference(crossReference, reference, contentAssistContext, acceptor, filter);
  }
}
