/*******************************************************************************
 * Copyright (c) 2005 IBM Corporation 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:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/

package org.eclipse.wtp.releng.tools.component.ui;

import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IResourceChangeListener;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.IResourceDeltaVisitor;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.Platform;
import org.eclipse.wtp.releng.tools.component.ILocation;
import org.eclipse.wtp.releng.tools.component.IPluginXML;
import org.eclipse.wtp.releng.tools.component.api.violation.ComponentViolationEmitter;
import org.eclipse.wtp.releng.tools.component.internal.ComponentXML;
import org.eclipse.wtp.releng.tools.component.internal.Plugin;
import org.eclipse.wtp.releng.tools.component.ui.internal.BundleAdapter;
import org.eclipse.wtp.releng.tools.component.ui.internal.ScannableComponent;
import org.eclipse.wtp.releng.tools.component.ui.internal.WorkspaceFileLocation;
import org.eclipse.wtp.releng.tools.component.ui.internal.WorkspacePluginXML;
import org.eclipse.wtp.releng.tools.component.ui.internal.job.AddComponent;
import org.eclipse.wtp.releng.tools.component.ui.internal.job.InitComponentManager;
import org.eclipse.wtp.releng.tools.component.ui.internal.job.RemoveComponent;
import org.osgi.framework.Bundle;

public class ComponentManager implements IResourceChangeListener, IResourceDeltaVisitor
{
  public static final String MARKER_COMPONENT_VIOLATION = "marker-comp-vio";
  private static ComponentManager manager;
  private Map scannableComps;
  private Map compRefs;
  private ResourceBundle bundle;

  public static ComponentManager getManager()
  {
    if (manager == null)
      manager = new ComponentManager();
    return manager;
  }

  private ComponentManager()
  {
  }

  public void init()
  {
    InitComponentManager job = new InitComponentManager();
    job.schedule();
    boolean interrupted = true;
    while (interrupted)
    {
      try
      {
        job.join();
        interrupted = false;
      }
      catch (InterruptedException e)
      {
        interrupted = true;
      }
    }
  }

  public void resourceChanged(IResourceChangeEvent event)
  {
    IResourceDelta delta = event.getDelta();
    if (delta != null)
    {
      try
      {
        delta.accept(this);
      }
      catch (CoreException e)
      {
        e.printStackTrace();
      }
    }
  }

  public boolean visit(IResourceDelta delta) throws CoreException
  {
    IResource res = delta.getResource();
    int type = res.getType();
    int kind = delta.getKind();
    if (type == IResource.FILE)
      handleFileDelta((IFile)res, kind);
    else if (type == IResource.PROJECT)
      handleProjectDelta((IProject)res, kind);
    return true;
  }

  private boolean handleFileDelta(IFile file, int kind)
  {
    if (file.getName().equals(ComponentXML.CONST_COMPONENT_XML))
    {
      if (kind == IResourceDelta.ADDED)
        new AddComponent(file).schedule();
      else if (kind == IResourceDelta.REMOVED)
        new RemoveComponent(file).schedule();
      else if (kind == IResourceDelta.CHANGED || kind == IResourceDelta.CONTENT)
      {
        new RemoveComponent(file).schedule();
        new AddComponent(file).schedule();
      }
    }
    /*
     * TODO: Incremental scan
     * 
    else if (file.getFileExtension() != null && file.getFileExtension().equals(ILibrary.EXT_CLASS))
    {
      if (kind == IResourceDelta.CHANGED || kind == IResourceDelta.CONTENT || kind == IResourceDelta.ADDED)
      {
        if (scannableComps != null)
        {
          IProject project = file.getProject();
          for (Iterator it = scannableComps.values().iterator(); it.hasNext();)
          {
            ScannableComponent scannableComp = (ScannableComponent)it.next();
            if (scannableComp.isScanningProject(project))
            {
              new ScanComponent(scannableComp, false).schedule();
            }
          }
        }
      }
    }
    */
    return true;
  }

  private boolean handleProjectDelta(IProject project, int kind)
  {
    if (kind == IResourceDelta.REMOVED)
      handleProjectDeltaRemoved(project, kind);
    else if (kind == IResourceDelta.ADDED)
      handleProjectDeltaAdded(project, kind);
    return true;
  }

  private boolean handleProjectDeltaAdded(IProject project, int kind)
  {
    if (project.findMember(IPluginXML.CONST_PLUGIN_XML) != null)
    {
      String pluginId = project.getName();
      if (scannableComps != null)
      {
        Object[] keys = scannableComps.keySet().toArray();
        for (int i = 0; i < keys.length; i++)
        {
          ScannableComponent scannableComp = (ScannableComponent)scannableComps.get(keys[i]);
          ComponentXML compXML = scannableComp.getCompXML();
          if (containsPlugin(compXML, pluginId))
          {
            scannableComp.addProject(project);
            return false;
          }
        }
      }
      if (compRefs != null)
      {
        Object[] keys = compRefs.keySet().toArray();
        for (int i = 0; i < keys.length; i++)
        {
          ComponentXML compXML = (ComponentXML)compRefs.get(keys[i]);
          if (containsPlugin(compXML, pluginId))
          {
            addScannableComponent(compXML);
            return false;
          }
        }
      }
    }
    return false;
  }

  private boolean containsPlugin(ComponentXML compXML, String pluginId)
  {
    for (Iterator it = compXML.getPlugins().iterator(); it.hasNext();)
      if (pluginId.equals(((Plugin)it.next()).getId()))
        return true;
    return false;
  }

  private boolean handleProjectDeltaRemoved(IProject project, int kind)
  {
    if (scannableComps != null)
    {
      ScannableComponent scannableComp = null;
      for (Iterator it = scannableComps.values().iterator(); it.hasNext();)
      {
        ScannableComponent comp = (ScannableComponent)it.next();
        if (comp.isScanningProject(project))
        {
          scannableComp = comp;
          break;
        }
      }
      if (scannableComp != null)
      {
        scannableComp.removeProject(project.getName());
        if (scannableComp.getProjects().size() <= 0)
          scannableComps.remove(scannableComp);
      }
    }
    return false;
  }

  public ComponentXML getComponentXML(IFile file)
  {
    String key = new WorkspaceFileLocation(file).getAbsolutePath();
    ScannableComponent scannableComponent = (ScannableComponent)getScannableComponents().get(key);
    if (scannableComponent != null)
      return scannableComponent.getCompXML();
    return (ComponentXML)getCompRefs().get(key);
  }

  public List getComponentXMLs()
  {
    List compNames = new ArrayList();
    if (compRefs != null)
      for (Iterator it = compRefs.values().iterator(); it.hasNext();)
        compNames.add(it.next());
    if (scannableComps != null)
      for (Iterator it = scannableComps.values().iterator(); it.hasNext();)
        compNames.add(((ScannableComponent)it.next()).getCompXML());
    return compNames;
  }

  public boolean isWorkspacePlugin(String id)
  {
    IProject[] projects = ResourcesPlugin.getWorkspace().getRoot().getProjects();
    for (int i = 0; i < projects.length; i++)
      if (projects[i].getName().equals(id))
        if (projects[i].findMember(IPluginXML.CONST_PLUGIN_XML) != null)
          return true;
    return false;
  }

  public Map getScannableComponents()
  {
    if (scannableComps == null)
      return new HashMap(0);
    else
      return new HashMap(scannableComps);
  }

  public void addScannableComponent(ComponentXML compXML)
  {
    Map compXMLs = new HashMap(1);
    compXMLs.put(compXML.getLocation().getAbsolutePath(), compXML);
    Collection plugins = compXML.getPlugins();
    Map pluginId2Plugins = new HashMap(plugins.size());
    List projects = new ArrayList(1);
    for (Iterator it = plugins.iterator(); it.hasNext();)
    {
      String pluginId = ((Plugin)it.next()).getId();
      IPluginXML pluginXML = getPlugin(pluginId);
      if (pluginXML instanceof WorkspacePluginXML)
      {
        IProject project = ((WorkspacePluginXML)pluginXML).getProject();
        projects.add(project);
      }
      pluginId2Plugins.put(pluginXML.getName(), pluginXML);
    }
    ComponentViolationEmitter emitter = new ComponentViolationEmitter(null);
    emitter.setDebug(true);
    List excludes = new ArrayList();
    excludes.add("java.");
    excludes.add("javax.");
    excludes.add("org.w3c.");
    excludes.add("org.xml.");
    excludes.add("org.apache.");
    excludes.add("sun.");
    emitter.setClassUseExcludes(excludes);
    emitter.init(compXMLs, getCompRefs(), pluginId2Plugins, new HashMap(0));
    ScannableComponent scannableComp = new ScannableComponent(compXML, emitter, projects);
    if (scannableComps == null)
      scannableComps = new HashMap(1);
    scannableComps.put(compXML.getLocation().getAbsolutePath(), scannableComp);
    /*
     * TODO: Initial scan
     */
    //ScanComponent job = new ScanComponent(scannableComp, true);
    //job.schedule();
  }

  public ScannableComponent removeScannableComponent(ILocation location)
  {
    if (scannableComps != null)
      return (ScannableComponent)scannableComps.remove(location.getAbsolutePath());
    else
      return null;
  }

  public IPluginXML getPlugin(String id)
  {
    IProject[] projects = ResourcesPlugin.getWorkspace().getRoot().getProjects();
    for (int i = 0; i < projects.length; i++)
    {
      if (projects[i].getName().equals(id))
      {
        IResource res = projects[i].findMember(IPluginXML.CONST_PLUGIN_XML);
        if (res != null && res.getType() == IResource.FILE)
        {
          return new WorkspacePluginXML((IFile)res);
        }
      }
    }
    Bundle bundle = Platform.getBundle(id);
    if (bundle != null)
    {
      return new BundleAdapter(bundle);
    }
    return null;
  }

  public Map getCompRefs()
  {
    if (compRefs == null)
      return new HashMap(0);
    else
      return new HashMap(compRefs);
  }

  public void addCompRef(ComponentXML compRef)
  {
    if (compRefs == null)
      compRefs = new HashMap(1);
    compRefs.put(compRef.getLocation().getAbsolutePath(), compRef);
    if (scannableComps != null)
      for (Iterator it = scannableComps.values().iterator(); it.hasNext();)
        ((ScannableComponent)it.next()).addCompRef(compRef);
  }

  public void removeCompRef(ILocation location)
  {
    if (compRefs != null)
      compRefs.remove(location.getAbsolutePath());
    if (scannableComps != null)
      for (Iterator it = scannableComps.values().iterator(); it.hasNext();)
        ((ScannableComponent)it.next()).removeCompRef(location);
  }

  public String getMessage(String key)
  {
    if (bundle == null)
    {
      try
      {
        bundle = ResourceBundle.getBundle("org.eclipse.wtp.releng.tools.component.ui.component");
      }
      catch (MissingResourceException e)
      {
        return key;
      }
    }
    return bundle.getString(key);
  }

  public String getMessage(String key, String[] subsitutes)
  {
    return MessageFormat.format(getMessage(key), subsitutes);
  }
}