/*
 * Copyright 2014 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.furnace.impl.graph;

import java.util.Set;

import org.jboss.forge.furnace.addons.Addon;
import org.jboss.forge.furnace.addons.AddonId;
import org.jboss.forge.furnace.addons.AddonView;
import org.jboss.forge.furnace.impl.addons.AddonLifecycleManager;
import org.jgrapht.event.TraversalListenerAdapter;
import org.jgrapht.event.VertexTraversalEvent;
import org.jgrapht.traverse.BreadthFirstIterator;
import org.jgrapht.traverse.DepthFirstIterator;

public class MasterGraphChangeHandler
{
   private AddonLifecycleManager lifecycleManager;
   private MasterGraph lastMasterGraph;
   private MasterGraph graph;

   public MasterGraphChangeHandler(AddonLifecycleManager lifefycleManager,
            MasterGraph currentGraph, MasterGraph graph)
   {
      this.lifecycleManager = lifefycleManager;
      this.lastMasterGraph = currentGraph;
      this.graph = graph;
   }

   public void hotSwapChanges()
   {
      initGraph();
      markDirty();
      markRemovedDirty();
      stopDirty();
      stopRemoved();
      loadAddons();
      startupIncremental();
      clearDirtyStatus();
   }

   private void initGraph()
   {
      if (lastMasterGraph != null)
      {
         /*
          * Propagate forward any addons that were removed, but still need to be shut down. This prevents duplicate
          * Addon objects from being registered in the lifecycle manager.
          */
         for (AddonVertex last : lastMasterGraph.getGraph().vertexSet())
         {
            boolean found = false;
            Set<AddonVertex> vertices = graph.getGraph().vertexSet();
            for (AddonVertex vertex : vertices)
            {
               if (last.getName().equals(vertex.getName()))
               {
                  for (AddonView view : vertex.getViews())
                  {
                     if (last.getViews().contains(view))
                     {
                        found = true;
                        break;
                     }
                  }
               }

               if (found)
                  break;
            }

            if (!found && last.getAddon().getStatus().isLoaded())
            {
               graph.getGraph().addVertex(last);
               last.setDirty(true);
            }
         }
      }

      DepthFirstIterator<AddonVertex, AddonDependencyEdge> iterator = new DepthFirstIterator<AddonVertex, AddonDependencyEdge>(
               graph.getGraph());
      iterator.addTraversalListener(new TraversalListenerAdapter<AddonVertex, AddonDependencyEdge>()
      {
         @Override
         public void vertexFinished(VertexTraversalEvent<AddonVertex> event)
         {
            AddonVertex vertex = event.getVertex();
            if (vertex.getAddon() == null)
            {
               AddonView view = vertex.getViews().iterator().next();
               AddonId addonId = vertex.getAddonId();

               Addon addon = null;
               if (lastMasterGraph != null)
               {
                  for (AddonVertex last : lastMasterGraph.getGraph().vertexSet())
                  {
                     if (last.getAddon().getId().equals(addonId) && last.getViews().contains(view))
                     {
                        addon = last.getAddon();
                        break;
                     }
                  }
               }

               vertex.setAddon(addon);
            }
         };
      });

      while (iterator.hasNext())
         iterator.next();

      iterator = new DepthFirstIterator<AddonVertex, AddonDependencyEdge>(graph.getGraph());
      iterator.addTraversalListener(new TraversalListenerAdapter<AddonVertex, AddonDependencyEdge>()
      {
         @Override
         public void vertexFinished(VertexTraversalEvent<AddonVertex> event)
         {
            AddonVertex vertex = event.getVertex();
            if (vertex.getAddon() == null)
            {
               AddonView view = vertex.getViews().iterator().next();
               AddonId addonId = vertex.getAddonId();

               Addon addon = null;
               Set<Addon> orphans = lifecycleManager.getOrphanAddons(addonId);
               if (!orphans.isEmpty())
                  addon = orphans.iterator().next();

               if (addon == null)
                  addon = lifecycleManager.getAddon(view, addonId);

               vertex.setAddon(addon);
            }
         };
      });

      while (iterator.hasNext())
         iterator.next();
   }

   private void markDirty()
   {
      DepthFirstIterator<AddonVertex, AddonDependencyEdge> iterator = new DepthFirstIterator<AddonVertex, AddonDependencyEdge>(
               graph.getGraph());

      iterator.addTraversalListener(new TraversalListenerAdapter<AddonVertex, AddonDependencyEdge>()
      {
         @Override
         public void vertexFinished(VertexTraversalEvent<AddonVertex> event)
         {
            // If this vertex is missing or any dependency was missing (is dirty), then this is dirty also
            AddonVertex vertex = event.getVertex();
            Addon addon = vertex.getAddon();
            if (!addon.getStatus().isLoaded() || addon.getStatus().isFailed())
            {
               vertex.setDirty(true);
            }

            for (AddonDependencyEdge edge : graph.getGraph().outgoingEdgesOf(vertex))
            {
               AddonVertex target = graph.getGraph().getEdgeTarget(edge);
               if (target.isDirty())
                  vertex.setDirty(true);
            }

            if (lastMasterGraph != null)
            {
               boolean equivalent = false;
               Set<AddonVertex> lastVertices = lastMasterGraph.getVertices(vertex.getName(), vertex.getVersion());
               for (AddonVertex lastVertex : lastVertices)
               {
                  if (graph.isSubtreeEquivalent(vertex, lastMasterGraph.getGraph(), lastVertex))
                  {
                     equivalent = true;
                     break;
                  }
               }

               if (!equivalent)
                  vertex.setDirty(true);
            }
         };
      });

      while (iterator.hasNext())
         iterator.next();
   }

   private void markRemovedDirty()
   {
      if (lastMasterGraph != null)
      {
         DepthFirstIterator<AddonVertex, AddonDependencyEdge> iterator = new DepthFirstIterator<AddonVertex, AddonDependencyEdge>(
                  lastMasterGraph.getGraph());

         iterator.addTraversalListener(new TraversalListenerAdapter<AddonVertex, AddonDependencyEdge>()
         {
            @Override
            public void vertexFinished(VertexTraversalEvent<AddonVertex> event)
            {
               AddonVertex lastVertex = event.getVertex();
               boolean exists = false;
               for (AddonVertex vertex : graph.getVertices(lastVertex.getName(), lastVertex.getVersion()))
               {
                  for (AddonView view : lastVertex.getViews())
                  {
                     if (vertex.getViews().contains(view))
                     {
                        exists = true;
                        break;
                     }
                  }
               }

               if (!exists)
               {
                  lastVertex.setDirty(true);
                  Set<AddonDependencyEdge> edges = lastMasterGraph.getGraph().incomingEdgesOf(lastVertex);
                  for (AddonDependencyEdge edge : edges)
                  {
                     AddonVertex source = lastMasterGraph.getGraph().getEdgeTarget(edge);
                     Set<AddonVertex> vertices = graph.getVertices(source.getName(), source.getVersion());
                     for (AddonVertex vertex : vertices)
                     {
                        vertex.setDirty(true);
                     }
                  }
               }
            };
         });

         while (iterator.hasNext())
            iterator.next();
      }
   }

   private void stopDirty()
   {
      BreadthFirstIterator<AddonVertex, AddonDependencyEdge> iterator = new BreadthFirstIterator<AddonVertex, AddonDependencyEdge>(
               graph.getGraph());

      iterator.addTraversalListener(new TraversalListenerAdapter<AddonVertex, AddonDependencyEdge>()
      {
         @Override
         public void vertexTraversed(VertexTraversalEvent<AddonVertex> event)
         {
            if (event.getVertex().isDirty())
               lifecycleManager.stopAddon(event.getVertex().getAddon());
         };
      });

      while (iterator.hasNext())
         iterator.next();
   }

   private void stopRemoved()
   {
      if (lastMasterGraph != null)
      {
         BreadthFirstIterator<AddonVertex, AddonDependencyEdge> iterator = new BreadthFirstIterator<AddonVertex, AddonDependencyEdge>(
                  lastMasterGraph.getGraph());

         iterator.addTraversalListener(new TraversalListenerAdapter<AddonVertex, AddonDependencyEdge>()
         {
            @Override
            public void vertexTraversed(VertexTraversalEvent<AddonVertex> event)
            {
               if (event.getVertex().isDirty())
                  lifecycleManager.stopAddon(event.getVertex().getAddon());
            };
         });

         while (iterator.hasNext())
            iterator.next();
      }
   }

   private void loadAddons()
   {
      DepthFirstIterator<AddonVertex, AddonDependencyEdge> iterator = new DepthFirstIterator<AddonVertex, AddonDependencyEdge>(
               graph.getGraph());

      iterator.addTraversalListener(new TraversalListenerAdapter<AddonVertex, AddonDependencyEdge>()
      {
         @Override
         public void vertexFinished(VertexTraversalEvent<AddonVertex> event)
         {
            Addon addon = event.getVertex().getAddon();
            if (!addon.getStatus().isLoaded())
               lifecycleManager.loadAddon(addon);
         };
      });

      while (iterator.hasNext())
         iterator.next();
   }

   private void startupIncremental()
   {
      DepthFirstIterator<AddonVertex, AddonDependencyEdge> iterator = new DepthFirstIterator<AddonVertex, AddonDependencyEdge>(
               graph.getGraph());

      iterator.addTraversalListener(new TraversalListenerAdapter<AddonVertex, AddonDependencyEdge>()
      {
         @Override
         public void vertexFinished(VertexTraversalEvent<AddonVertex> event)
         {
            Addon addon = event.getVertex().getAddon();
            if (addon.getStatus().isLoaded())
               lifecycleManager.startAddon(addon);
         };
      });

      while (iterator.hasNext())
         iterator.next();
   }

   private void clearDirtyStatus()
   {
      DepthFirstIterator<AddonVertex, AddonDependencyEdge> iterator = new DepthFirstIterator<AddonVertex, AddonDependencyEdge>(
               graph.getGraph());

      iterator.addTraversalListener(new TraversalListenerAdapter<AddonVertex, AddonDependencyEdge>()
      {
         @Override
         public void vertexFinished(VertexTraversalEvent<AddonVertex> event)
         {
            event.getVertex().setDirty(false);
         };
      });

      while (iterator.hasNext())
         iterator.next();
   }
}