/*
* JBoss, Home of Professional Open Source.
* Copyright 2006, Red Hat Middleware LLC, and individual contributors
* as indicated by the @author tags. See the copyright.txt file 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.util;

import java.lang.reflect.Constructor;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.jboss.aop.Advisor;
import org.jboss.aop.ConstructorInfo;
import org.jboss.aop.JoinPointInfo;
import org.jboss.aop.MethodInfo;
import org.jboss.aop.advice.Interceptor;

/**
 * 
 * @author <a href="kabir.khan@jboss.com">Kabir Khan</a>
 * @version $Revision: 1.1 $
 */
public class JoinPointComparator
{
   public static long[] mergeMethodInfoKeys(long[] myKeys, long[] otherKeys)
   {
      long[] allKeys = null;
      
      if (myKeys == null || myKeys.length == 0)
      {
         allKeys = otherKeys;
         Arrays.sort(allKeys);
      }
      else if (otherKeys == null || otherKeys.length == 0)
      {
         allKeys = myKeys;
         Arrays.sort(allKeys);
      }
      else
      {
         allKeys = new long[myKeys.length + otherKeys.length];
         System.arraycopy(myKeys, 0, allKeys, 0, myKeys.length);
         System.arraycopy(otherKeys, 0, allKeys, myKeys.length, otherKeys.length);
         Arrays.sort(allKeys);
         
         boolean haveDups = false;
         int i = 0;
         int peek = 1;
         int dup = allKeys.length;
         for ( ; peek < allKeys.length; i++, peek++)
         {
            if (allKeys[i] == allKeys[peek])
            {
               if (!haveDups)
               {
                  dup = peek;
                  haveDups = true;
               }  
            }
            else if (haveDups)
            {
                  allKeys[dup++] = allKeys[peek];
            }
         }
         
         if (dup != allKeys.length)
         {
            long[] deduped = new long[dup];
            System.arraycopy(allKeys, 0, deduped, 0, dup);
            allKeys = deduped;
         }
      }
      
      return allKeys;
   }   
   
   public static int hasSameInterceptorLengths(JoinPointInfo myInfo, JoinPointInfo otherInfo)
   {
      if (myInfo != null && otherInfo != null)
      {
         if (myInfo.getInterceptors() != null && otherInfo.getInterceptors() != null)
         {
            int i = compareLengths(getLengthNullAsZero(myInfo), getLengthNullAsZero(otherInfo));
            if (i != 0)
            {
               return i;
            }
         }
      }
      else if (myInfo == null && otherInfo == null)
      {
         //same
      }
      else
      {
         if (myInfo == null)
         {
            if (getLengthNullAsZero(otherInfo) > 0)
            {
               return 1;
            }
         }
         else
         {
            if (getLengthNullAsZero(myInfo) > 0)
            {
               return -1;
            }
         }
      }
      return 0;
   }
   
   public static boolean hasSameMethodAspectLength(long[] myKeys, long[] otherKeys, Advisor myAdvisor, Advisor otherAdvisor)
   {
      long[] keys = JoinPointComparator.mergeMethodInfoKeys(myKeys, otherKeys);
      
      for (int i = 0 ; i < keys.length ; i++)
      {
         MethodInfo myInfo = myAdvisor.getMethodInfo(keys[i]);
         MethodInfo otherInfo = otherAdvisor.getMethodInfo(keys[i]);
         
         if (JoinPointComparator.hasSameInterceptorLengths(myInfo, otherInfo) != 0)
         {
            return false;
         }
         
      }
      return true;

   }
   
   
   public static boolean hasSameConstructorAspectLength(ConstructorInfo[] myInfos, ConstructorInfo[] otherInfos)
   {
      //TODO: GeneratedAdvisor weaving might only be including infos for the woven constructors? 
      Map<Constructor<?>, ConstructorInfo> myMap = createConstructorInfoMap(myInfos);
      Map<Constructor<?>, ConstructorInfo> otherMap = createConstructorInfoMap(otherInfos);
      
      if (myMap.size() == 0 && otherMap.size() == 0)
      {
         return true;
      }
      else if (myMap.size() == 0 && otherMap.size() > 0)
      {
         if (hasNonNullInfos(otherInfos))
         {
            return false;
         }
      }
      else if (myMap.size() > 0 && otherMap.size() == 0)
      {
         if (hasNonNullInfos(myInfos))
         {
            return false;
         }
      }
      else
      {
         Set<ConstructorInfo> nonMatches = new HashSet<ConstructorInfo>();
         for (Constructor<?> ctor : myMap.keySet())
         {
            ConstructorInfo myInfo = myMap.get(ctor);
            ConstructorInfo otherInfo = otherMap.remove(ctor);
            if (otherInfo != null)
            {
               int i = compareLengths(getLengthNullAsZero(myInfo), getLengthNullAsZero(otherInfo));
               if (i != 0)
               {
                  return false;
               }  
            }
            else
            {
               nonMatches.add(myInfo);
            }
         }
         if (otherMap.size() > 0 && hasNonNullInfos(otherMap.values()))
         {
            return false;
         }
         if (nonMatches.size() > 0 && hasNonNullInfos(nonMatches))
         {
            return false;
         }
      }

      return true;
   }
   
   private static Map<Constructor<?>, ConstructorInfo> createConstructorInfoMap(ConstructorInfo[] infos)
   {
      if (infos == null || infos.length == 0)
      {
         return UnmodifiableEmptyCollections.EMPTY_HASHMAP;
      }
      Map<Constructor<?>, ConstructorInfo> map = new HashMap<Constructor<?>, ConstructorInfo>(infos.length);
      for (ConstructorInfo info : infos)
      {
         map.put(info.getConstructor(), info);
      }
      return map;
   }
   
   private static boolean hasNonNullInfos(JoinPointInfo[] infos)
   {
      if (infos.length > 0)
      {
         for (JoinPointInfo info : infos)
         {
            if (getLengthNullAsZero(info) > 0)
            {
               return true;
            }
               
         }
      }
      return false;
   }
   
   private static boolean hasNonNullInfos(Collection<ConstructorInfo> infos)
   {
      if (infos.size() > 0)
      {
         for (JoinPointInfo info : infos)
         {
            if (getLengthNullAsZero(info) > 0)
            {
               return true;
            }
               
         }
      }
      return false;
   }
   
   private static int compareLengths(int mine, int other)
   {
      if (mine > other) return -1;
      if (mine == other) return 0;
      return 1;
   }
   
   private static int getLengthNullAsZero(JoinPointInfo info)
   {
      Interceptor[] icptrs = info.getInterceptors(); 
      if (icptrs == null)
      {
         return 0;
      }
      return icptrs.length;
   }
}
