/**
 * 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.generator;

import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import com.google.inject.Inject;
import com.google.inject.Provider;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import org.eclipse.emf.codegen.ecore.genmodel.GenModel;
import org.eclipse.emf.codegen.ecore.genmodel.GenModelPackage;
import org.eclipse.emf.codegen.ecore.genmodel.generator.GenBaseGeneratorAdapter;
import org.eclipse.emf.codegen.util.CodeGenUtil;
import org.eclipse.emf.common.util.BasicMonitor;
import org.eclipse.emf.common.util.Diagnostic;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EDataType;
import org.eclipse.emf.ecore.EModelElement;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EOperation;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.ETypedElement;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.emf.ecore.xcore.XClass;
import org.eclipse.emf.ecore.xcore.XClassifier;
import org.eclipse.emf.ecore.xcore.XDataType;
import org.eclipse.emf.ecore.xcore.XOperation;
import org.eclipse.emf.ecore.xcore.XPackage;
import org.eclipse.emf.ecore.xcore.XStructuralFeature;
import org.eclipse.emf.ecore.xcore.generator.XcoreAppendable;
import org.eclipse.emf.ecore.xcore.generator.XcoreGeneratorImpl;
import org.eclipse.emf.ecore.xcore.mappings.XOperationMapping;
import org.eclipse.emf.ecore.xcore.mappings.XcoreMapper;
import org.eclipse.xtext.common.types.JvmDeclaredType;
import org.eclipse.xtext.common.types.JvmFormalParameter;
import org.eclipse.xtext.common.types.JvmOperation;
import org.eclipse.xtext.common.types.JvmTypeReference;
import org.eclipse.xtext.generator.IFileSystemAccess;
import org.eclipse.xtext.generator.IGenerator;
import org.eclipse.xtext.linking.impl.XtextLinkingDiagnostic;
import org.eclipse.xtext.xbase.XBlockExpression;
import org.eclipse.xtext.xbase.compiler.XbaseCompiler;
import org.eclipse.xtext.xbase.compiler.output.ITreeAppendable;
import org.eclipse.xtext.xbase.lib.CollectionLiterals;
import org.eclipse.xtext.xbase.lib.Exceptions;
import org.eclipse.xtext.xbase.lib.Extension;
import org.eclipse.xtext.xbase.lib.IterableExtensions;

@SuppressWarnings("all")
public class XcoreGenerator implements IGenerator {
  @Inject
  @Extension
  private XcoreMapper mappings;
  
  @Inject
  private XbaseCompiler compiler;
  
  @Inject
  private Provider<XcoreGeneratorImpl> xcoreGeneratorImplProvider;
  
  public void generateBodyAnnotations(final XPackage pack) {
    final LinkedHashMap<EObject, String> errors = this.getErrors(pack);
    final HashSet<ETypedElement> processed = CollectionLiterals.<ETypedElement>newHashSet();
    EList<XClassifier> _classifiers = pack.getClassifiers();
    for (final XClassifier xClassifier : _classifiers) {
      if ((xClassifier instanceof XDataType)) {
        final XDataType xDataType = ((XDataType)xClassifier);
        final EDataType eDataType = this.mappings.getMapping(xDataType).getEDataType();
        final XBlockExpression createBody = xDataType.getCreateBody();
        final JvmOperation creator = this.mappings.getMapping(xDataType).getCreator();
        if (((createBody != null) && (creator != null))) {
          final XcoreAppendable appendable = this.createAppendable();
          appendable.declareVariable(creator.getParameters().get(0), "it");
          this.compile(eDataType, "create", appendable, errors, createBody, creator.getReturnType(), Collections.<JvmTypeReference>emptySet());
        }
        final XBlockExpression convertBody = xDataType.getConvertBody();
        final JvmOperation converter = this.mappings.getMapping(xDataType).getConverter();
        if (((convertBody != null) && (converter != null))) {
          final XcoreAppendable appendable_1 = this.createAppendable();
          appendable_1.declareVariable(converter.getParameters().get(0), "it");
          this.compile(eDataType, "convert", appendable_1, errors, convertBody, converter.getReturnType(), Collections.<JvmTypeReference>emptySet());
        }
      } else {
        final XClass xClass = ((XClass) xClassifier);
        final EClass eClass = this.mappings.getMapping(xClass).getEClass();
        EList<EStructuralFeature> _eAllStructuralFeatures = eClass.getEAllStructuralFeatures();
        for (final EStructuralFeature eStructuralFeature : _eAllStructuralFeatures) {
          boolean _add = processed.add(eStructuralFeature);
          if (_add) {
            final XStructuralFeature xFeature = this.mappings.getXFeature(eStructuralFeature);
            if ((xFeature != null)) {
              final XBlockExpression getBody = xFeature.getGetBody();
              if ((getBody != null)) {
                final JvmOperation getter = this.mappings.getMapping(xFeature).getGetter();
                final XcoreAppendable appendable_2 = this.createAppendable();
                appendable_2.declareVariable(getter.getDeclaringType(), "this");
                final JvmTypeReference superType = IterableExtensions.<JvmTypeReference>head(getter.getDeclaringType().getSuperTypes());
                if ((superType != null)) {
                  appendable_2.declareVariable(superType.getType(), "super");
                }
                this.compile(eStructuralFeature, "get", appendable_2, errors, getBody, getter.getReturnType(), Collections.<JvmTypeReference>emptySet());
              }
            }
          }
        }
        EList<EOperation> _eAllOperations = eClass.getEAllOperations();
        for (final EOperation eOperation : _eAllOperations) {
          boolean _add_1 = processed.add(eOperation);
          if (_add_1) {
            final XOperation xOperation = this.mappings.getXOperation(eOperation);
            if ((xOperation != null)) {
              final XBlockExpression body = xOperation.getBody();
              if ((body != null)) {
                final XOperationMapping xOperationMapping = this.mappings.getMapping(xOperation);
                final JvmOperation jvmOperation = xOperationMapping.getJvmOperation();
                if ((jvmOperation != null)) {
                  final XcoreAppendable appendable_3 = this.createAppendable();
                  JvmDeclaredType declaringType = jvmOperation.getDeclaringType();
                  boolean _isExternalInterface = xOperationMapping.getGenOperation().getGenClass().isExternalInterface();
                  if (_isExternalInterface) {
                    final EList<JvmTypeReference> superTypes = declaringType.getSuperTypes();
                    final JvmTypeReference effectiveTypeReference = IterableExtensions.<JvmTypeReference>head(superTypes);
                    if ((effectiveTypeReference != null)) {
                      appendable_3.declareVariable(effectiveTypeReference.getType(), "this");
                    }
                  } else {
                    appendable_3.declareVariable(declaringType, "this");
                    final JvmTypeReference superType_1 = IterableExtensions.<JvmTypeReference>head(declaringType.getSuperTypes());
                    if ((superType_1 != null)) {
                      appendable_3.declareVariable(superType_1.getType(), "super");
                    }
                  }
                  EList<JvmFormalParameter> _parameters = jvmOperation.getParameters();
                  for (final JvmFormalParameter parameter : _parameters) {
                    appendable_3.declareVariable(parameter, parameter.getName());
                  }
                  JvmTypeReference _returnType = jvmOperation.getReturnType();
                  EList<JvmTypeReference> _exceptions = jvmOperation.getExceptions();
                  HashSet<JvmTypeReference> _hashSet = new HashSet<JvmTypeReference>(_exceptions);
                  this.compile(eOperation, "body", appendable_3, errors, body, _returnType, _hashSet);
                }
              }
            }
          }
        }
      }
    }
  }
  
  @Override
  public void doGenerate(final Resource resource, final IFileSystemAccess fsa) {
    EObject _head = IterableExtensions.<EObject>head(resource.getContents());
    this.generateBodyAnnotations(((XPackage) _head));
    this.generateGenModel(IterableExtensions.<GenModel>head(Iterables.<GenModel>filter(resource.getContents(), GenModel.class)), fsa);
  }
  
  public void compile(final EModelElement target, final String key, final ITreeAppendable appendable, final Map<EObject, String> errors, final XBlockExpression body, final JvmTypeReference returnType, final Set<JvmTypeReference> exceptions) {
    try {
      Set<Map.Entry<EObject, String>> _entrySet = errors.entrySet();
      for (final Map.Entry<EObject, String> error : _entrySet) {
        boolean _isAncestor = EcoreUtil.isAncestor(body, error.getKey());
        if (_isAncestor) {
          String _value = error.getValue();
          throw new RuntimeException(_value);
        }
      }
      this.compiler.compile(body, appendable, returnType, exceptions);
      EcoreUtil.setAnnotation(target, GenModelPackage.eNS_URI, key, this.extractBody(appendable.toString()));
    } catch (final Throwable _t) {
      if (_t instanceof Throwable) {
        final Throwable throwable = (Throwable)_t;
        String _xifexpression = null;
        String _message = throwable.getMessage();
        boolean _tripleEquals = (_message == null);
        if (_tripleEquals) {
          _xifexpression = "throw new <%java.lang.Error%>(\"Unresolved compilation problem\");";
        } else {
          String _unicodeEscapeEncode = CodeGenUtil.unicodeEscapeEncode(throwable.getMessage());
          String _plus = ("throw new <%java.lang.Error%>(\"Unresolved compilation problems: " + _unicodeEscapeEncode);
          _xifexpression = (_plus + "\");");
        }
        final String message = _xifexpression;
        EcoreUtil.setAnnotation(target, GenModelPackage.eNS_URI, key, message);
      } else {
        throw Exceptions.sneakyThrow(_t);
      }
    }
  }
  
  public LinkedHashMap<EObject, String> getErrors(final XPackage xPackage) {
    final LinkedHashMap<EObject, String> result = Maps.<EObject, String>newLinkedHashMap();
    final Resource resource = xPackage.eResource();
    EList<Resource.Diagnostic> _errors = resource.getErrors();
    for (final Resource.Diagnostic diagnostic : _errors) {
      if ((diagnostic instanceof XtextLinkingDiagnostic)) {
        final URI uri = ((XtextLinkingDiagnostic)diagnostic).getUriToProblem();
        if ((uri != null)) {
          final EObject eObject = resource.getEObject(uri.fragment());
          if ((eObject != null)) {
            result.put(eObject, ((XtextLinkingDiagnostic)diagnostic).getMessage());
          }
        }
      }
    }
    return result;
  }
  
  public XcoreAppendable createAppendable() {
    return new XcoreAppendable();
  }
  
  public String extractBody(final String body) {
    String _xblockexpression = null;
    {
      String _xifexpression = null;
      boolean _startsWith = body.startsWith("\n");
      if (_startsWith) {
        _xifexpression = body.substring(1);
      } else {
        _xifexpression = body;
      }
      String result = _xifexpression;
      String _xifexpression_1 = null;
      boolean _startsWith_1 = result.startsWith("{\n");
      if (_startsWith_1) {
        String _xblockexpression_1 = null;
        {
          result = result.replace("\n\t", "\n");
          int _length = result.length();
          int _minus = (_length - 2);
          _xblockexpression_1 = result.substring(1, _minus);
        }
        _xifexpression_1 = _xblockexpression_1;
      } else {
        _xifexpression_1 = result;
      }
      _xblockexpression = _xifexpression_1;
    }
    return _xblockexpression;
  }
  
  public Diagnostic generateGenModel(final GenModel genModel, final IFileSystemAccess fsa) {
    Diagnostic _xifexpression = null;
    String _modelDirectory = genModel.getModelDirectory();
    boolean _tripleNotEquals = (_modelDirectory != null);
    if (_tripleNotEquals) {
      Diagnostic _xblockexpression = null;
      {
        genModel.setCanGenerate(true);
        final XcoreGeneratorImpl generator = this.xcoreGeneratorImplProvider.get();
        generator.setInput(genModel);
        generator.setFileSystemAccess(fsa);
        generator.setModelDirectory(genModel.getModelDirectory());
        BasicMonitor _basicMonitor = new BasicMonitor();
        generator.generate(genModel, GenBaseGeneratorAdapter.MODEL_PROJECT_TYPE, _basicMonitor);
        BasicMonitor _basicMonitor_1 = new BasicMonitor();
        generator.generate(genModel, GenBaseGeneratorAdapter.EDIT_PROJECT_TYPE, _basicMonitor_1);
        BasicMonitor _basicMonitor_2 = new BasicMonitor();
        generator.generate(genModel, GenBaseGeneratorAdapter.EDITOR_PROJECT_TYPE, _basicMonitor_2);
        BasicMonitor _basicMonitor_3 = new BasicMonitor();
        _xblockexpression = generator.generate(genModel, GenBaseGeneratorAdapter.TESTS_PROJECT_TYPE, _basicMonitor_3);
      }
      _xifexpression = _xblockexpression;
    }
    return _xifexpression;
  }
}
