/**
 * Copyright (c) 2013 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.generator;


import org.eclipse.emf.codegen.ecore.genmodel.GenClass;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.xcore.XClass;
import org.eclipse.emf.ecore.xcore.mappings.XcoreMapper;
import org.eclipse.xtext.common.types.JvmGenericType;
import org.eclipse.xtext.common.types.JvmParameterizedTypeReference;
import org.eclipse.xtext.common.types.JvmType;
import org.eclipse.xtext.common.types.JvmTypeReference;
import org.eclipse.xtext.xbase.compiler.XbaseCompiler;
import org.eclipse.xtext.xbase.compiler.output.ITreeAppendable;
import org.eclipse.xtext.xbase.lib.IterableExtensions;

import com.google.inject.Inject;


public class XcoreCompiler extends XbaseCompiler
{
  private final static String REASSIGNED_THIS_IN_LAMBDA = "!reassigned_this_for_lambda!";

  @Inject
  XcoreMapper mappings;

  @Override
  protected void reassignThisInClosure(final ITreeAppendable b, JvmType rawClosureType)
  {
    boolean registerClosureAsThis = rawClosureType instanceof JvmGenericType;
    boolean isAlreadyInALambda = b.hasObject(REASSIGNED_THIS_IN_LAMBDA);
    if (b.hasObject("this") && !isAlreadyInALambda)
    {
      Object element = b.getObject("this");
      if (element instanceof JvmType)
      {
        final String proposedName = "<%this%>";
        if (!b.hasObject(proposedName))
        {
          b.declareSyntheticVariable(element, proposedName);
          if (b.hasObject("super"))
          {
            Object superElement = b.getObject("super");
            if (superElement instanceof JvmType)
            {
              b.declareSyntheticVariable(superElement, "<%super%>");
            }
          }
        }
      }
      else
      {
        registerClosureAsThis = false;
      }
    }
    if (!isAlreadyInALambda)
    {
      // add a synthetic marker so we don't reassign this and super more than once.
      b.declareSyntheticVariable(REASSIGNED_THIS_IN_LAMBDA, REASSIGNED_THIS_IN_LAMBDA);
    }
    if (registerClosureAsThis)
    {
      b.declareVariable(rawClosureType, "this");
    }
  }
  
  @Override
  protected void serialize(JvmTypeReference type, EObject context, ITreeAppendable appendable, boolean withoutConstraints, boolean paramsToWildcard, boolean paramsToObject, boolean allowPrimitives)
  {
    if (type instanceof JvmParameterizedTypeReference)
    {
      JvmParameterizedTypeReference jvmParameterizedTypeReference = (JvmParameterizedTypeReference)type;
      if (jvmParameterizedTypeReference.getArguments().isEmpty())
      {
        JvmType referencedType = jvmParameterizedTypeReference.getType();
        EObject xcoreElement = mappings.getXcoreElement(referencedType);
        if (xcoreElement instanceof XClass)
        {
          GenClass genClass = (GenClass)mappings.getGen((XClass)xcoreElement);
          if (genClass != null && genClass.isExternalInterface())
          {
            JvmTypeReference superType = IterableExtensions.head(((JvmGenericType)referencedType).getSuperTypes());
            if (superType != null)
            {
              serialize(superType, context, appendable, withoutConstraints, paramsToWildcard, paramsToObject, allowPrimitives);
              return;
            }
          }
        }
      }
    }

    super.serialize(type, context, appendable, withoutConstraints, paramsToWildcard, paramsToObject, allowPrimitives);
  }
}
