/*
 * Copyright (c) 2002 - 2006 IBM Corporation.
 * 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 com.ibm.wala.ipa.callgraph.propagation;

import java.util.Comparator;

import com.ibm.wala.classLoader.ArrayClass;
import com.ibm.wala.classLoader.IClass;
import com.ibm.wala.classLoader.IField;
import com.ibm.wala.ipa.callgraph.propagation.cfa.ExceptionReturnValueKey;
import com.ibm.wala.ipa.cha.IClassHierarchy;
import com.ibm.wala.types.TypeReference;
import com.ibm.wala.util.debug.Assertions;

public class PointerKeyComparator implements Comparator<PointerKey> {

  private final IClassHierarchy cha;

  public PointerKeyComparator(IClassHierarchy cha) {
    if (cha == null) {
      throw new IllegalArgumentException("null cha");
    }
    this.cha = cha;
  }

  protected int comparePrimitives(TypeReference r1, TypeReference r2) {
    int h1 = r1.hashCode();
    int h2 = r2.hashCode();
    if (h1 != h2)
      return h1-h2;
    else {
      assert r1 == r2;
      return 0;
    }
  }

  protected int compareConcreteTypes(IClass k1, IClass k2) {
    int n1 = cha.getNumber( k1 );
    int n2 = cha.getNumber( k2 );
    if (n1 != n2)
      return n1-n2;
    else {
      int s1 = k1.hashCode();
      int s2 = k2.hashCode();
      assert k1 == k2 || s1 != s2;
      return s1-s2;
    }
  }

  protected int compareInstanceKeys(InstanceKey k1, InstanceKey k2) {
    return compareConcreteTypes( k1.getConcreteType(), k2.getConcreteType() );
  }
    
  protected int compareFields(IField if1, IField if2) {
    int f1 = if1.hashCode();
    int f2 = if2.hashCode();
    if (f1 != f2)
      return f1-f2;
    else {
      assert if1 == if2;
      return 0;
    }
  }
    
  private static int compareLocalKey(LocalPointerKey key1, Object key2) {
    if (key2 instanceof LocalPointerKey) {
      int l1 = key1.getValueNumber();
      int l2 = ((LocalPointerKey)key2).getValueNumber();
      if (l1 != l2)
	return l1-l2;
      else {
	int n1 = key1.getNode().getGraphNodeId();
	int n2 = ((LocalPointerKey)key2).getNode().getGraphNodeId();
	if (n1 != n2)
	  return n1-n2;
	else {
	  assert key1.equals(key2);
	  return 0;
	}
      }
    }
    
    else return -1;
  }

  private static int compareReturnValueKey(ReturnValueKey key1, Object key2) {
    if (key2 instanceof ReturnValueKey) {
      int n1 = key1.getNode().getGraphNodeId();
      int n2 = ((ReturnValueKey)key2).getNode().getGraphNodeId();
      if (n1 != n2)
	return n1-n2;
      else {
	assert key1.equals(key2);
	return 0;
      }
    }

    else return -1;
  }

  private static int compareExceptionKey(ExceptionReturnValueKey key1, Object key2) {
    if (key2 instanceof ExceptionReturnValueKey) {
      int n1 = key1.getNode().getGraphNodeId();
      int n2 = ((ExceptionReturnValueKey)key2).getNode().getGraphNodeId();
      if (n1 != n2)
	return n1-n2;
      else {
	assert key1.equals(key2);
	return 0;
      }
    }

    else return -1;
  }

  private int compareFieldKey(InstanceFieldKey key1, Object key2) {
    if (key2 instanceof InstanceFieldKey) {
      int r1 = compareInstanceKeys(key1.getInstanceKey(), ((InstanceFieldKey)key2).getInstanceKey());
      if (r1 != 0)
	return r1;
      else {
	return compareFields(key1.getField(), ((InstanceFieldKey)key2).getField());
      }
    }
    
    else
      return -1;
  }

  private int compareStaticKey(StaticFieldKey key1, Object key2) {
    if (key2 instanceof StaticFieldKey) {
      int n1 = cha.getNumber( key1.getField().getDeclaringClass() );
      int n2 = cha.getNumber( ((StaticFieldKey)key2).getField().getDeclaringClass() );
      if (n1 != n2)
	return n1-n2;
      else {
	return compareFields(key1.getField(), ((StaticFieldKey)key2).getField());
      }
    }
    
    else
	return -1;
  }

  private int compareArrayKey(ArrayContentsKey key1, Object key2) {
    if (key2 instanceof ArrayContentsKey) {
      ArrayClass k1 = (ArrayClass)key1.getInstanceKey().getConcreteType();
      ArrayClass k2 = (ArrayClass)((ArrayContentsKey)key2).getInstanceKey().getConcreteType();
      int d1 = k1.getDimensionality();
      int d2 = k2.getDimensionality();
      if (d1 != d2) {
	return d1-d2;
      } else if (k1.getInnermostElementClass() == null) {
	if (k2.getInnermostElementClass() == null)
	  return 
	    comparePrimitives(
	      k1.getReference().getInnermostElementType(),
	      k2.getReference().getInnermostElementType());
	else
	  return -1;
      } else if (k2.getInnermostElementClass() == null) {
	return 1;
      } else {
        return
	  compareConcreteTypes(
	    k1.getInnermostElementClass(), 
	    k2.getInnermostElementClass());
      }
    }
    
    else
      return -1;
  }

  @Override
  public int compare(PointerKey key1, PointerKey key2) {
    if (key1 == key2) return 0;

    else if (key1 instanceof LocalPointerKey) {
      return compareLocalKey((LocalPointerKey) key1, key2);
    }

    else if (key2 instanceof LocalPointerKey) {
      return -1*compareLocalKey((LocalPointerKey) key2, key1);
    }

    // at this point, neither key is local
    else if (key1 instanceof ExceptionReturnValueKey) {
      return compareExceptionKey((ExceptionReturnValueKey)key1, key2);
    }

    else if (key2 instanceof ExceptionReturnValueKey) {
      return -1*compareExceptionKey((ExceptionReturnValueKey)key2, key1);
    }

    // at this point, neither key is local or expretval
    else if (key1 instanceof ReturnValueKey) {
      return compareReturnValueKey((ReturnValueKey)key1, key2);
    }

    else if (key2 instanceof ReturnValueKey) {
      return -1*compareReturnValueKey((ReturnValueKey)key2, key1);
    }

    // at this point, neither key is local or retval, expretval
    else if (key1 instanceof InstanceFieldKey) {
      return compareFieldKey((InstanceFieldKey)key1, key2);
    }

    else if (key2 instanceof InstanceFieldKey) {
      return -1*compareFieldKey((InstanceFieldKey)key2, key1);
    }

    // at this point, neither key is local or retval, expretval, field
    else if (key1 instanceof StaticFieldKey) {
      return compareStaticKey((StaticFieldKey)key1, key2);
    }

    else if (key2 instanceof StaticFieldKey) {
      return -1*compareStaticKey((StaticFieldKey)key2, key1);
    }

    // at this point, neither key is local or retval, expretval, field, static
    else if (key1 instanceof ArrayContentsKey) {
      return compareArrayKey((ArrayContentsKey)key1, key2);
    }

    else if (key2 instanceof ArrayContentsKey) {
      return -1*compareArrayKey((ArrayContentsKey)key2, key1);
    }

    else {
      return compareOtherKeys(key1, key2);
    }
  }

  protected int compareOtherKeys(Object key1, Object key2) {
    System.err.println("Cannot compare " + key1 + " and " + key2);
    Assertions.UNREACHABLE();
    return 0;
  }

  @Override
  public boolean equals(Object o) {
    return (o instanceof PointerKeyComparator) &&
	((PointerKeyComparator)o).cha.equals(cha);
  }

  @Override
  public int hashCode() {
    return cha.hashCode();
  }

}
