/**
 * Copyright (c) 2006-2010 IBM Corporation 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
 * 
 * Contributors: 
 *   IBM - Initial API and implementation
 */
package org.eclipse.emf.codegen.ecore.genmodel.generator;

import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.eclipse.emf.codegen.ecore.CodeGenEcorePlugin;
import org.eclipse.emf.codegen.ecore.Generator;
import org.eclipse.emf.codegen.ecore.generator.AbstractGeneratorAdapter;
import org.eclipse.emf.codegen.ecore.generator.GeneratorAdapterFactory;
import org.eclipse.emf.codegen.ecore.genmodel.GenBase;
import org.eclipse.emf.codegen.ecore.genmodel.GenModel;
import org.eclipse.emf.codegen.ecore.genmodel.GenPackage;
import org.eclipse.emf.codegen.ecore.genmodel.GenRuntimePlatform;
import org.eclipse.emf.codegen.jet.JETEmitter;
import org.eclipse.emf.codegen.jet.JETException;
import org.eclipse.emf.common.EMFPlugin;
import org.eclipse.emf.common.util.Diagnostic;
import org.eclipse.emf.common.util.Monitor;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.common.util.UniqueEList;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;

/**
 * A base generator adapter implementation for GenModel elements.
 * 
 * This base defines four project types for code generation and provides implementations of
 * {@link #canGenerate(Object, Object)}, {@link #getGenerateChildren(Object, Object)}, and
 * {@link #doGenerate(Object, Object, Monitor)} that dispatch to project-type-specific methods, which
 * can be overridden in subclasses.
 * 
 * @since 2.2.0
 */
public class GenBaseGeneratorAdapter extends AbstractGeneratorAdapter
{
  /**
   * The project type constant representing a model project.
   * This is the string "org.eclipse.emf.codegen.ecore.genmodel.generator.ModelProject".
   */
  public static final String MODEL_PROJECT_TYPE = "org.eclipse.emf.codegen.ecore.genmodel.generator.ModelProject";

  /**
   * The project type constant representing an edit project.
   * This is the string "org.eclipse.emf.codegen.ecore.genmodel.generator.EditProject".
   */
  public static final String EDIT_PROJECT_TYPE = "org.eclipse.emf.codegen.ecore.genmodel.generator.EditProject";

  /**
   * The project type constant representing an editor project.
   * This is the string "org.eclipse.emf.codegen.ecore.genmodel.generator.EditorProject".
   */
  public static final String EDITOR_PROJECT_TYPE = "org.eclipse.emf.codegen.ecore.genmodel.generator.EditorProject";

  /**
   * The project type constant representing a tests project.
   * This is the string "org.eclipse.emf.codegen.ecore.genmodel.generator.TestsProject".
   */
  public static final String TESTS_PROJECT_TYPE = "org.eclipse.emf.codegen.ecore.genmodel.generator.TestsProject";

  public GenBaseGeneratorAdapter()
  {
    super();
  }

  public GenBaseGeneratorAdapter(GeneratorAdapterFactory generatorAdapterFactory)
  {
    super(generatorAdapterFactory);
  }

  /**
   * Based on the given project type, dispatches to one of {@link #canGenerateModel(Object)},
   * {@link #canGenerateEdit(Object)}, {@link #canGenerateEditor(Object)}, or {@link #canGenerateTests(Object)}.
   */
  @Override
  public boolean canGenerate(Object object, Object projectType)
  {
    if (MODEL_PROJECT_TYPE.equals(projectType))
    {
      return canGenerateModel(object);
    }
    else if (EDIT_PROJECT_TYPE.equals(projectType))
    {
      return canGenerateEdit(object);
    }
    else if (EDITOR_PROJECT_TYPE.equals(projectType))
    {
      return canGenerateEditor(object);
    }
    else if (TESTS_PROJECT_TYPE.equals(projectType))
    {
      return canGenerateTests(object);
    }
    return false;
  }

  /**
   * Delegates to the GenModel element's {@link GenBase#canGenerate() canGenerate()} method.
   */
  public boolean canGenerateModel(Object object)
  {
    return ((GenBase)object).canGenerate();
  }

  /**
   * Delegates to the GenModel element's {@link GenBase#canGenerateEdit() canGenerateEdit()} method.
   */
  public boolean canGenerateEdit(Object object)
  {
    return ((GenBase)object).canGenerateEdit();
  }

  /**
   * Delegates to the GenModel element's {@link GenBase#canGenerateEditor() canGenerateEditor()} method.
   */
  public boolean canGenerateEditor(Object object)
  {
    return ((GenBase)object).canGenerateEditor();
  }

  /**
   * Delegates to the GenModel element's {@link GenBase#canGenerateTests() canGenerateTests()} method.
   */
  public boolean canGenerateTests(Object object)
  {
    return ((GenBase)object).canGenerateTests();
  }

  /**
   * Based on the given project type, dispatches to one of {@link #getGenerateModelChildren(Object)},
   * {@link #getGenerateEditChildren(Object)}, {@link #getGenerateEditorChildren(Object)}, or
   * {@link #getGenerateTestsChildren(Object)}.
   */
  @Override
  public Collection<?> getGenerateChildren(Object object, Object projectType)
  {
    if (MODEL_PROJECT_TYPE.equals(projectType))
    {
      return getGenerateModelChildren(object);
    }
    else if (EDIT_PROJECT_TYPE.equals(projectType))
    {
      return getGenerateEditChildren(object);
    }
    else if (EDITOR_PROJECT_TYPE.equals(projectType))
    {
      return getGenerateEditorChildren(object);
    }
    else if (TESTS_PROJECT_TYPE.equals(projectType))
    {
      return getGenerateTestsChildren(object);
    }
    return Collections.EMPTY_LIST;
  }

  protected Collection<?> getGenerateModelChildren(Object object)
  {
    return Collections.EMPTY_LIST;
  }

  protected Collection<?> getGenerateEditChildren(Object object)
  {
    return Collections.EMPTY_LIST;
  }

  protected Collection<?> getGenerateEditorChildren(Object object)
  {
    return Collections.EMPTY_LIST;
  }

  protected Collection<?> getGenerateTestsChildren(Object object)
  {
    return Collections.EMPTY_LIST;
  }

  /**
   * Returns the container of the given object if it is a GenModel element, and null otherwise. 
   */
  protected Object getParent(Object object)
  {
    Object result = ((GenBase)object).eContainer();
    return result instanceof GenBase ? result : null;
  }

  /**
   * Based on the given project type, dispatches to one of {@link #generateModel(Object, Monitor)},
   * {@link #generateEdit(Object, Monitor)}, {@link #generateEditor(Object, Monitor)}, or
   * {@link #generateTests(Object, Monitor)}.
   */
  @Override
  public Diagnostic doGenerate(Object object, Object projectType, Monitor monitor)
  {
    if (MODEL_PROJECT_TYPE.equals(projectType))
    {
      return generateModel(object, monitor);
    }
    else if (EDIT_PROJECT_TYPE.equals(projectType))
    {
      return generateEdit(object, monitor);
    }
    else if (EDITOR_PROJECT_TYPE.equals(projectType))
    {
      return generateEditor(object, monitor);
    }
    else if (TESTS_PROJECT_TYPE.equals(projectType))
    {
      return generateTests(object, monitor);
    }
    throw new IllegalArgumentException("Invalid projectType: " + projectType.toString());
  }

  protected Diagnostic generateModel(Object object, Monitor monitor)
  {
    return Diagnostic.OK_INSTANCE;
  }

  protected Diagnostic generateEdit(Object object, Monitor monitor)
  {
    return Diagnostic.OK_INSTANCE;
  }

  protected Diagnostic generateEditor(Object object, Monitor monitor)
  {
    return Diagnostic.OK_INSTANCE;
  }

  protected Diagnostic generateTests(Object object, Monitor monitor)
  {
    return Diagnostic.OK_INSTANCE;
  }

  /**
   * Returns the user-specified portion of the dynamic template path from the GenModel.
   * @since org.eclipse.emf.codegen.ecore 2.2.2
   */
  @Override
  protected List<String> getUserTemplatePath()
  {
    String templateLocation = ((GenBase)generatingObject).getGenModel().getTemplateDirectory();
    if (templateLocation != null && templateLocation.length() != 0)
    {
      if (templateLocation.indexOf(':') == -1)
      {
        templateLocation = URI.createPlatformResourceURI(templateLocation, true).toString();
      }
      return Collections.singletonList(templateLocation);
    }
    return Collections.emptyList();
  }

  /**
   * Adds the default EMF template location to the base portion of the dynamic template path. Subclasses may
   * override this to add to the front of the path, and then invoke this implementation.
   * @since org.eclipse.emf.codegen.ecore 2.2.2
   */
  @Override
  protected void addBaseTemplatePathEntries(List<String> templatePath)
  {
    templatePath.add(CodeGenEcorePlugin.INSTANCE.getBaseURL().toString() + "templates");
    super.addBaseTemplatePathEntries(templatePath);
  }

  /**
   * Adds the plug-ins required for GenModel/Ecore-based templates to the <code>JETEmitter</code>'s classpath.
   */
  @Override
  protected void addClasspathEntries(JETEmitter jetEmitter) throws JETException
  {
    jetEmitter.addVariable("EMF_CODEGEN", "org.eclipse.emf.codegen");
    jetEmitter.addVariable("EMF_CODEGEN_ECORE", "org.eclipse.emf.codegen.ecore");
    jetEmitter.addVariable("EMF_COMMON", "org.eclipse.emf.common");
    jetEmitter.addVariable("EMF_ECORE", "org.eclipse.emf.ecore");

    super.addClasspathEntries(jetEmitter);
  }

  /**
   * Creates the import manager and stores it on the <code>GenModel</code>, for use its in computing names.
   */
  @Override
  protected void createImportManager(String packageName, String className)
  {
    super.createImportManager(packageName, className);
    updateImportManager();
  }

  /**
   * Clears the import manager and removes it from the <code>GenModel</code>.
   */
  @Override
  protected void clearImportManager()
  {
    super.clearImportManager();
    updateImportManager();
  }

  private void updateImportManager()
  {
    if (generatingObject != null)
    {
      ((GenBase)generatingObject).getGenModel().setImportManager(importManager);
    }
  }

  @Override
  protected void setLineDelimiter(String lineDelimiter)
  {
    super.setLineDelimiter(lineDelimiter);
    if (generatingObject != null)
    {
      ((GenBase)generatingObject).getGenModel().setLineDelimiter(lineDelimiter);
    }
  }

  /**
   * Ensures that a project exists. If not, a properly configured EMF project will be created. Similarly, if the project
   * does exist and <code>force</code> is true, it will be reconfigured to match the default EMF configuration.
   */
  @Override
  protected void ensureProjectExists(String workspacePath, Object object, Object projectType, boolean force, Monitor monitor)
  {
    try
    {
      if (EMFPlugin.IS_ECLIPSE_RUNNING)
      {
        EclipseHelper.ensureProjectExists(workspacePath, object, projectType, force, monitor);
      }
    }
    finally
    {
      monitor.done();
    }
  }

  /*
   * All Eclipse-dependent operations are delegated to this class. This pattern avoids any runtime failure due to
   * missing dependencies in the stand-alone case.
   */
  private static class EclipseHelper
  {
    public static boolean ensureProjectExists(String workspacePath, Object object, Object projectType, boolean force, Monitor monitor)
    {
      try
      {
        IPath path = new Path(workspacePath);

        if (path.isAbsolute())
        {
          GenModel genModel = ((GenBase)object).getGenModel();
          IWorkspace workspace = ResourcesPlugin.getWorkspace();
          IProject project = workspace.getRoot().getProject(path.segment(0));

          if (!project.exists() || force)
          {
            URI projectLocation = null;
            List<IProject> referencedProjects = new UniqueEList<IProject>();

            if (project.exists())
            {
              referencedProjects.addAll(Arrays.asList(project.getDescription().getReferencedProjects()));
              projectLocation = getLocationURI(project);
            }
            else
            {
              Resource genModelResource = genModel.eResource();
              URI genModelURI = genModelResource.getURI();
              ResourceSet resourceSet = genModelResource.getResourceSet();
              if (resourceSet != null)
              {
                genModelURI = resourceSet.getURIConverter().normalize(genModelURI);
              }
              if (genModelURI.isPlatformResource())
              {
                IProject genModelProject = workspace.getRoot().getProject(URI.decode(genModelURI.segments()[1]));
                if (genModelProject.exists())
                {
                  projectLocation = getLocationURI(genModelProject);
                }
              }
            }

            IProject modelProject = null;
            IPath javaSource = null;
            String modelProjectDirectory = genModel.getModelProjectDirectory();
            if (modelProjectDirectory != null && !"".equals(modelProjectDirectory))
            {
              modelProject = workspace.getRoot().getProject(modelProjectDirectory);
              javaSource = new Path(genModel.getModelDirectory());
            }

            //DMS factor this into a method? Use a non-static subclass? 
            int style = 0;
            if (MODEL_PROJECT_TYPE.equals(projectType))  style = Generator.EMF_MODEL_PROJECT_STYLE;
            if (EDIT_PROJECT_TYPE.equals(projectType))   style = Generator.EMF_EDIT_PROJECT_STYLE;
            if (EDITOR_PROJECT_TYPE.equals(projectType)) style = Generator.EMF_EDITOR_PROJECT_STYLE;
            if (TESTS_PROJECT_TYPE.equals(projectType))  style = Generator.EMF_TESTS_PROJECT_STYLE;

            if (genModel.getRuntimePlatform() == GenRuntimePlatform.GWT)
            {
              style |= Generator.EMF_GWT_PROJECT_STYLE; 
            }

            if ((style & Generator.EMF_TESTS_PROJECT_STYLE) != 0)
            {
              IProject testsProject = workspace.getRoot().getProject(genModel.getTestsProjectDirectory());

              javaSource = new Path(genModel.getTestsDirectory());
              if (!genModel.sameModelTestsProject()) 
              {
                if (testsProject.exists())
                {
                  projectLocation = getLocationURI(testsProject);
                }

                if (modelProject != null)
                {
                  referencedProjects.add(modelProject);
                  referencedProjects.addAll(Arrays.asList(modelProject.getDescription().getReferencedProjects()));
                }
              }
            }
            else if ((style & Generator.EMF_MODEL_PROJECT_STYLE) == 0 && genModel.hasEditSupport())
            {
              IProject editProject = workspace.getRoot().getProject(genModel.getEditProjectDirectory());

              javaSource = new Path(genModel.getEditDirectory());
              if (!genModel.sameModelEditProject())
              {
                if (editProject.exists())
                {
                  projectLocation = getLocationURI(editProject);
                }

                if (modelProject != null)
                {
                  referencedProjects.add(modelProject);
                }
              }
  
              for (GenPackage genPackage : genModel.getUsedGenPackages())
              {
                GenModel otherGenModel = genPackage.getGenModel();
                if (otherGenModel.hasEditSupport())
                {
                  IProject otherEditProject = workspace.getRoot().getProject(otherGenModel.getEditProjectDirectory());
                  if (otherEditProject.exists())
                  {
                    referencedProjects.add(otherEditProject);
                    referencedProjects.addAll(Arrays.asList(otherEditProject.getDescription().getReferencedProjects()));
                  }
                }
              }

              if ((style & Generator.EMF_EDIT_PROJECT_STYLE) == 0 && genModel.hasEditorSupport())
              {
                javaSource = new Path(genModel.getEditorDirectory());
                if (!genModel.sameEditEditorProject())
                {
                  referencedProjects.add(editProject);
                  referencedProjects.addAll(Arrays.asList(editProject.getDescription().getReferencedProjects()));
                }
              }
            }

            //  Remove any non-Java dependencies from being added.
            //
            for (Iterator<IProject> i = referencedProjects.iterator(); i.hasNext(); )
            {
              IProject referencedProject = i.next();
              IJavaProject referencedJavaProject = JavaCore.create(referencedProject);
              if (!referencedJavaProject.exists())
              {
                i.remove();
              }
            }

            if (projectLocation != null && javaSource != null)
            {
              projectLocation = projectLocation.trimSegments(1).appendSegment(javaSource.segment(0));
            }

            if (genModel.hasXMLDependency())
            {
              style |= Generator.EMF_XML_PROJECT_STYLE;
            }

            if ((style & Generator.EMF_MODEL_PROJECT_STYLE) == 0 || genModel.hasPluginSupport())
            {
              style |= Generator.EMF_PLUGIN_PROJECT_STYLE;
            }

            if (javaSource == null)
            {
              javaSource = new Path(genModel.getEditorDirectory());
            }

            Generator.createEMFProject
              (javaSource,
               projectLocation, 
               referencedProjects, 
               monitor, 
               style,
               genModel.getEffectiveModelPluginVariables());
          }
          return workspace.getRoot().getProject(path.segment(0)).exists();
        }
      }
      catch (Exception exception)
      { 
        //DMS should we let this exception out?
        CodeGenEcorePlugin.INSTANCE.log(exception);
      }
      return false;
    }
    
    protected static URI getLocationURI(IProject project) throws CoreException
    {
      java.net.URI locationURI = project.getDescription().getLocationURI();
      return locationURI == null ? null : URI.createURI(locationURI.toString());
    }
  }
}
