/*
 * Copyright 2012 Red Hat, Inc. and/or its affiliates.
 *
 * Licensed under the Eclipse Public License version 1.0, available at
 * http://www.eclipse.org/legal/epl-v10.html
 */
package org.jboss.forge.shell.project;

import java.util.ArrayList;
import java.util.List;

import javax.enterprise.event.Observes;
import javax.enterprise.inject.spi.BeanManager;
import javax.inject.Inject;

import org.jboss.forge.project.Facet;
import org.jboss.forge.project.Project;
import org.jboss.forge.project.facets.FacetActionAborted;
import org.jboss.forge.project.facets.PackagingFacet;
import org.jboss.forge.project.facets.events.FacetInstalled;
import org.jboss.forge.project.facets.events.InstallFacets;
import org.jboss.forge.project.packaging.PackagingType;
import org.jboss.forge.project.services.FacetFactory;
import org.jboss.forge.shell.Shell;
import org.jboss.forge.shell.ShellMessages;
import org.jboss.forge.shell.exceptions.Abort;
import org.jboss.forge.shell.util.ConstraintInspector;

/**
 * @author <a href="mailto:lincolnbaxter@gmail.com">Lincoln Baxter, III</a>
 */
public class FacetInstallationHandler
{
   @Inject
   private FacetFactory factory;

   @Inject
   private Shell shell;

   @Inject
   private Project project;

   @Inject
   private BeanManager manager;

   public void installRequest(@Observes final InstallFacets request)
   {
      List<Facet> installed = new ArrayList<Facet>();
      shell.printlnVerbose("Received Facet installation request " + request.getFacetTypes());
      if (!request.promptRequested()
               || shell.promptBoolean("An action has requested to install the following facets into your project "
                        + request.getFacetTypes() + " continue?", true))
      {
         for (Class<? extends Facet> type : request.getFacetTypes())
         {
            Facet facet = factory.getFacet(type);
            if (!project.hasFacet(type))
            {
               install(facet, false);
               installed.add(facet);
            }
            else
            {
               shell.printlnVerbose("Facet type already installed [" + type + "]");
            }
         }
      }
      else if (request.promptRequested())
      {
         throw new FacetActionAborted("Facet installation aborted.");
      }

      for (Facet facet : installed)
      {
         manager.fireEvent(new FacetInstalled(facet));
      }
   }

   private void install(final Facet facet, final boolean prompt)
   {
      if (!performInstallation(facet, prompt))
      {
         ShellMessages.error(shell, "Failed to install [" + ConstraintInspector.getName(facet.getClass())
                  + "]; there may be a mess!");
      }
   }

   private boolean performInstallation(final Facet facet, final boolean prompt)
   {
      if (project.hasFacet(facet.getClass()))
      {
         return true;
      }
      else
      {
         facet.setProject(project);

         try
         {
            installDependencies(facet, prompt);
            PackagingType type = updatePackaging(facet);

            if (!project.hasFacet(facet.getClass()))
            {
               project.installFacet(facet);
            }

            if (type != null)
            {
               project.getFacet(PackagingFacet.class).setPackagingType(type);
            }

            if (facet.isInstalled())
            {
               ShellMessages.success(shell, "Installed [" + ConstraintInspector.getName(facet.getClass())
                        + "] successfully.");
               return true;
            }
         }
         catch (Abort e)
         {
            abort();
         }
      }
      return false;
   }

   private void installDependencies(final Facet facet, final boolean prompt) throws Abort
   {
      List<Class<? extends Facet>> deps = ConstraintInspector.getFacetDependencies(facet.getClass());

      if (!project.hasAllFacets(deps))
      {
         List<Class<? extends Facet>> missingDeps = new ArrayList<Class<? extends Facet>>();
         List<String> facetNames = new ArrayList<String>();
         for (Class<? extends Facet> f : deps)
         {
            if (!project.hasFacet(f))
            {
               facetNames.add(ConstraintInspector.getName(f));
               missingDeps.add(f);
            }
         }

         if (prompt && !shell.promptBoolean("The ["
                  + ConstraintInspector.getName(facet.getClass())
                  + "] facet depends on the following missing facet(s): "
                  + facetNames
                  + ". Install as well?"))
         {
            throw new Abort();
         }
         else
         {
            List<Facet> installed = new ArrayList<Facet>();
            for (Class<? extends Facet> d : missingDeps)
            {
               Facet instance = factory.getFacet(d);
               if (performInstallation(instance, prompt))
               {
                  installed.add(instance);
               }
               else
               {
                  // attempt to undo everything we've done so far
                  for (Facet f : installed)
                  {
                     if (!f.uninstall())
                     {
                        ShellMessages.info(shell,
                                 "Could not uninstall [" + ConstraintInspector.getName(f.getClass())
                                          + "]. Must be cleaned up manually.");
                     }
                     else
                     {
                        ShellMessages.info(shell,
                                 "Uninstalled facet [" + ConstraintInspector.getName(f.getClass())
                                          + "].");
                     }
                  }
                  throw new Abort();
               }
            }
         }
      }
   }

   private PackagingType updatePackaging(final Facet facet) throws Abort
   {
      List<PackagingType> types = ConstraintInspector.getCompatiblePackagingTypes(facet.getClass());
      String facetName = ConstraintInspector.getName(facet.getClass());

      PackagingType packaging = project.getFacet(PackagingFacet.class).getPackagingType();
      if (types.isEmpty() || types.contains(packaging))
      {
         return null;
      }
      else if (shell.promptBoolean("Facet ["
               + facetName + "] requires packaging type(s) " + types
               + ", but is currently [" + packaging
               + "]. Update packaging? (Note: this could deactivate other plugins in your project.)"))
      {
         if (types.size() == 1)
         {
            return types.get(0);
         }
         else
         {
            return shell.promptChoiceTyped("Select a new packaging type:", types);
         }
      }
      else
      {
         throw new Abort();
      }
   }

   private void abort()
   {
      ShellMessages.info(shell, "Installation cancelled!");
   }
}
