/*
* JBoss, Home of Professional Open Source
* Copyright 2005, JBoss Inc., and individual contributors as indicated
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.aop.advice;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedHashMap;

import org.jboss.aop.AspectManager;

/**
 *
 * @author <a href="mailto:kabir.khan@jboss.org">Kabir Khan</a>
 * @version $Revision$
 */
public class PrecedenceSorter
{
   static Comparator interceptorComparator = new Comparator()
   {
      public int compare(Object objA, Object objB)
      {
         InterceptorEntry entryA = (InterceptorEntry)objA;
         InterceptorEntry entryB = (InterceptorEntry)objB;
         
         return entryA.precedenceOrder - entryB.precedenceOrder;
      }
   };
   
   static class InterceptorEntry
   {
      Interceptor interceptor;
      int originalOrder;
      int precedenceOrder = -1;
      String classname;
      String method;
      
      InterceptorEntry(Interceptor interceptor)
      {
         this.interceptor = interceptor;

         String interceptorName = null;
         if (interceptor instanceof PerInstanceInterceptor)
         {
            PerInstanceInterceptor icptr = (PerInstanceInterceptor)interceptor;
            interceptorName = icptr.getName();
         }
         else if (interceptor instanceof PerJoinpointInterceptor)
         {
            PerJoinpointInterceptor icptr = (PerJoinpointInterceptor)interceptor;
            interceptorName = icptr.getName();
         }
         else if (interceptor instanceof CFlowInterceptor)
         {
            CFlowInterceptor icptr = (CFlowInterceptor)interceptor;
            interceptorName = icptr.getName();
         }
         else
         {
            interceptorName = interceptor.getClass().getName();
         }
         
         try
         {
            boolean isAdvice = interceptorName.startsWith("org.jboss.aop.advice.");
            if (isAdvice)
            {
               String name = interceptor.getName();
               int index = name.lastIndexOf(".");
               classname = name.substring(0, index);
               method = name.substring(index + 1);
            }
            else
            {
               classname = interceptorName;
            }
         } 
         catch (RuntimeException e)
         {
            System.err.print(interceptor.getName());
            throw e;
         }
      }
      
      public String toString()
      {
         return "Entry: " + precedenceOrder + " (" + originalOrder + ")interceptorClass=" + classname + "; adviceMethod=" + method;
      }
      
   }
   
   private static boolean matches(InterceptorEntry ientry, PrecedenceDefEntry pentry)
   {
      if (ientry.classname.equals(pentry.interceptorClass))
      {
         if (ientry.method == null)
         {
            if (pentry.adviceMethod == null)
            {
               return true;
            }
         }
         else if (pentry.adviceMethod != null)
         {
            //This was:
            //return ientry.classname.equals(pentry.interceptorClass);
            return ientry.method.equals(pentry.adviceMethod);
         }
      }
      return false;
   }
   
   
   public static PrecedenceDefEntry[] createOverallPrecedence(AspectManager manager)
   {
      ArrayList overall = new ArrayList();
      
      LinkedHashMap precedenceDefs = manager.getPrecedenceDefs();
      boolean first = true;
      for (Iterator it = precedenceDefs.values().iterator() ; it.hasNext(); )
      {
         PrecedenceDef precedenceDef = (PrecedenceDef)it.next();
         PrecedenceDefEntry[] entries = precedenceDef.getEntries();

         if (first)
         {
            //Populate overall with the initial precedence
            for (int i = 0 ; i < entries.length ; i++)
            {
               overall.add(entries[i]);
            }
            first = false;
            continue;
         }
         
         overall = mergePrecedenceDef(overall, precedenceDef);
      }
      //System.out.println("OVERALL PRECEDENCE: " + overall);
      return (PrecedenceDefEntry[])overall.toArray(new PrecedenceDefEntry[overall.size()]);
   }
   
   public static ArrayList mergePrecedenceDef(ArrayList overall, PrecedenceDef precedenceDef)
   {
      //TODO This can be improved. If you have the precedences
      //		1) A, D
      //    2) C, E
      //    3) C, D
      //After adding 2) to 1) since there is no relationship defined you get:
      //    i) A, D, C, E
      //After adding 3) to i) you end up with an overall precedence of
      //    ii) A, C, D, C, E,
      //In practice this should be fine, since the applyPrecedence() looks for the 
      //first matching entry, so the second (duplicate) occurrence of C is ignored.
      PrecedenceDefEntry[] entries = precedenceDef.getEntries();
      int start = 0, end = 0;
      int size = overall.size(); 
      for (int i = 0 ; i < size ; i++)
      {
         PrecedenceDefEntry global = (PrecedenceDefEntry)overall.get(i);
         boolean found = false;

         //find current overall precedence entry in the new set of defs
         for (int j = start ; j < entries.length ; j++)
         {
            PrecedenceDefEntry cur = entries[j];
            
            if (cur.equals(global))
            {
               found = true;
               end = j;
               break;
            }
         }
         
         //We found it. Now insert everything until this into global and 
         //reset the counters
         if (found)
         {
            int insert = i;
            for (int j = start ; j < end ; j++)
            {
               overall.add(insert++, entries[j]);
            }
            end++;
            start = end;
         }
      }

      for (int j = start ; j < entries.length ; j++)
      {
         overall.add(entries[j]);
      }
      
      return overall;
   }
   
   public static Interceptor[] applyPrecedence(Interceptor[] interceptors, AspectManager manager)
   {
      ArrayList all = new ArrayList(interceptors.length);
      ArrayList precedence = new ArrayList(interceptors.length);
      PrecedenceDefEntry[] precedenceEntries = manager.getSortedPrecedenceDefEntries();
      
      //Figure out what interceptors have precedence
      for (int i = 0 ; i < interceptors.length ; i++)
      {
         InterceptorEntry interceptorEntry = new InterceptorEntry(interceptors[i]);
         all.add(interceptorEntry);
         for (int j = 0 ; j < precedenceEntries.length ; j++)
         {
            if (matches(interceptorEntry, precedenceEntries[j]))
            {
               //This interceptor is defined in the precedence
               interceptorEntry.originalOrder = i;
               interceptorEntry.precedenceOrder = j;
               precedence.add(interceptorEntry);
               break;
            }
         }
      }
      
      //Sort the interceptors having precedence
      Collections.sort(precedence, interceptorComparator);
      Interceptor[] sortedInterceptors = new Interceptor[interceptors.length];
      
      //Build up new array of interceptors depending on their precedence 
      int prec = 0;
      int allSize = all.size();
      int precedenceSize = precedence.size();
      
      for (int i = 0 ; i < allSize ; i++)
      {
         InterceptorEntry entry = (InterceptorEntry)all.get(i);
         
         if (entry.precedenceOrder >= 0 && prec < precedenceSize)
         {
            entry = (InterceptorEntry)precedence.get(prec++);
         }
         sortedInterceptors[i] = entry.interceptor;
      }
      
      return sortedInterceptors;
   }
   
/*   public static void main(String[] args)
   {
      System.out.println("Hello");
      AspectManager manager = new AspectManager();
      PrecedenceDef def = new PrecedenceDef("3",new PrecedenceDefEntry[]{
            new PrecedenceDefEntry("A", null),
         	new PrecedenceDefEntry("D", null)});
      manager.addPrecedence(def);
      outputOverAll(manager);

      def = new PrecedenceDef("4",new PrecedenceDefEntry[]{
            new PrecedenceDefEntry("C", null),
            new PrecedenceDefEntry("E", null)});
      manager.addPrecedence(def);
      outputOverAll(manager);

      def = new PrecedenceDef("5",new PrecedenceDefEntry[]{
            new PrecedenceDefEntry("C", null),
            new PrecedenceDefEntry("D", null)});
      manager.addPrecedence(def);
      outputOverAll(manager);

   }
   
   private static void outputOverAll(AspectManager manager)
   {
      PrecedenceDefEntry[] entries = manager.getSortedPrecedenceDefEntries();
      for (int i = 0 ; i < entries.length ; i++)
      {
         System.out.println("\t" + entries[i]);
      }
      System.out.println("==================================");
   }
*/   
}
