/*******************************************************************************
 * Copyright (c) 2016 Avaloq Evolution AG and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Avaloq Evolution AG - initial API and implementation
 *******************************************************************************/
package com.avaloq.tools.ddk.check.runtime.ui.editor;

import java.io.InputStream;
import java.util.NoSuchElementException;

import org.apache.log4j.Logger;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IStorage;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.common.util.WrappedException;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.ide.IDE;
import org.eclipse.xtext.Constants;
import org.eclipse.xtext.ui.editor.LanguageSpecificURIEditorOpener;
import org.eclipse.xtext.ui.editor.XtextReadonlyEditorInput;
import org.eclipse.xtext.ui.editor.utils.EditorUtils;

import com.avaloq.tools.ddk.check.runtime.configuration.IModelLocation;
import com.avaloq.tools.ddk.check.runtime.registry.ICheckCatalogRegistry;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.inject.Inject;
import com.google.inject.name.Named;


/**
 * {@link org.eclipse.xtext.ui.editor.IURIEditorOpener URI editor opener} capable of dealing
 * with <em>platform plugin</em> URIs. Opens content with platform plugin URIs in read-only
 * editors.
 */
public class PlatformPluginAwareEditorOpener extends LanguageSpecificURIEditorOpener {

  /** Bundle resource URL protocol. */
  public static final String OSGI_RESOURCE_URL_PROTOCOL = "bundleresource"; //$NON-NLS-1$

  private static final Logger LOG = Logger.getLogger(PlatformPluginAwareEditorOpener.class);

  @Inject
  @Named(Constants.LANGUAGE_NAME)
  private String editorID;

  @Inject(optional = true)
  private IWorkbench workbench;

  /**
   * If a platform plugin URI is given, a read-only Xtext editor is opened and returned. {@inheritDoc}
   *
   * @see {@link org.eclipse.emf.common.util.URI#isPlatformPlugin()}
   */
  @Override
  public IEditorPart open(final URI uri, final EReference crossReference, final int indexInList, final boolean select) {
    IEditorPart result = super.open(uri, crossReference, indexInList, select);
    if (result == null && (uri.isPlatformPlugin() || OSGI_RESOURCE_URL_PROTOCOL.equals(uri.scheme()))) {
      final IModelLocation modelLocation = getModelLocation(uri.trimFragment());
      if (modelLocation != null) {
        PlatformPluginStorage storage = new PlatformPluginStorage(modelLocation);
        IEditorInput editorInput = new XtextReadonlyEditorInput(storage);
        IWorkbenchPage activePage = workbench.getActiveWorkbenchWindow().getActivePage();
        try {
          IEditorPart editor = IDE.openEditor(activePage, editorInput, editorID);
          selectAndReveal(editor, uri, crossReference, indexInList, select);
          return EditorUtils.getXtextEditor(editor);
        } catch (WrappedException e) {
          LOG.error("Error while opening editor part for EMF URI '" + uri + "'", e.getCause()); //$NON-NLS-1$ //$NON-NLS-2$
        } catch (PartInitException partInitException) {
          LOG.error("Error while opening editor part for EMF URI '" + uri + "'", partInitException); //$NON-NLS-1$ //$NON-NLS-2$
        }
      }
    }
    return result;
  }

  /**
   * Gets the model location for a given org.eclipse.emf.common.util.URI or {@code null} if none could be determined,
   * meaning that no registered model location with a catalog java.net.URI equal to given org.eclipse.emf.common.util.URI
   * was found in the catalog registry.
   *
   * @param uri
   *          the org.eclipse.emf.common.util.URI
   * @return the model location
   */
  private IModelLocation getModelLocation(final URI uri) {
    try {
      return Iterables.find(ICheckCatalogRegistry.INSTANCE.getAllCheckModelLocations(), new Predicate<IModelLocation>() {
        @Override
        public boolean apply(final IModelLocation input) {
          return input.getCatalogUri().toString().equals(uri.toString());
        }
      });
    } catch (NoSuchElementException e) {
      LOG.error("Could not find a model location for URI " + uri); //$NON-NLS-1$
      return null;
    }
  }

  /**
   * Defines an editor storage providing read-only access in an Xtext editor.
   */
  public static class PlatformPluginStorage implements IStorage {

    @Inject(optional = true)
    private IWorkbench workbench;
    private final IModelLocation location;

    public PlatformPluginStorage(final IModelLocation location) {
      this.location = location;
    }

    /** {@inheritDoc} */
    @Override
    public <T> T getAdapter(final Class<T> adapter) {
      return null;
    }

    /**
     * Gets the name of the project in which the currently active editor's resource is contained.
     *
     * @return the project name
     */
    private String getProjectName() {
      try {
        final IWorkbenchWindow activeWorkbenchWindow = workbench.getActiveWorkbenchWindow();
        if (activeWorkbenchWindow != null) {
          IEditorPart editorPart = activeWorkbenchWindow.getActivePage().getActiveEditor();
          if (editorPart != null) {
            IFileEditorInput input = (IFileEditorInput) editorPart.getEditorInput();
            IProject activeProject = input.getFile().getProject();
            return activeProject.getName();
          }
        }
        // CHECKSTYLE:OFF
      } catch (Exception e) {
        // CHECKSTYLE:ON
      }
      return "<unknown plugin>"; //$NON-NLS-1$
    }

    /** {@inheritDoc} */
    @Override
    public InputStream getContents() throws CoreException {
      try {
        return location.getCatalogStream();
        // CHECKSTYLE:OFF
      } catch (Exception e) {
        throw new CoreException(new Status(IStatus.ERROR, getProjectName(), e.getMessage())); // NOPMD
        // CHECKSTYLE:ON
      }
    }

    @Override
    public IPath getFullPath() {
      return new Path(location.getCatalogPath());
    }

    @Override
    public String getName() {
      return URI.createURI(location.getCatalogUri().toString(), true).trimFragment().lastSegment();
    }

    @Override
    public boolean isReadOnly() {
      return true;
    }

    @Override
    public int hashCode() {
      final int prime = 31; // NOPMD
      int result = 1;
      result = prime * result + ((location.getCatalogUri() == null) ? 0 : location.getCatalogUri().hashCode());
      return result;
    }

    @Override
    public boolean equals(final Object obj) {
      if (this == obj) {
        return true;
      }
      if (obj == null) {
        return false;
      }
      if (getClass() != obj.getClass()) {
        return false;
      }
      PlatformPluginStorage other = (PlatformPluginStorage) obj;
      if (location == null) {
        if (other.location != null) {
          return false;
        }
      } else if (!location.getCatalogUri().equals(other.location.getCatalogUri())) {
        return false;
      }
      return true;
    }
  }

}