package cutils.reflection;

/**
 * The DelegateSynthesizer uses cutils.reflection
 * to synthesizer proxy classes via semi-automatic
 * static proxy delegation
 */
public class DelegateSynthesizer {
  private String className = "";
  private String methodList = "";
  private java.util.Vector instanceList = new java.util.Vector();

  private cutils.reflection.MethodList ml = new cutils.reflection.MethodList();

  public void process() {
    for (int i = 0; i < instanceList.size(); i++)
        //processInstance(instanceList.elementAt(i));
      processTopoSort(instanceList.elementAt(i));
  }

  /**
   * Add an object to the cutils.delegate synthesizer
   * you may add as many instances as you like.
   */
  public void add(Object o) {
    instanceList.addElement(o);
  }

  /**
   *    use topological sorting to disambiguate
   */
  private void processTopoSort(Object o) {
    cutils.reflection.ReflectUtil ru = new cutils.reflection.ReflectUtil(o);
    String cn = stripPackageName(ru.getClassName());
    String instanceName = cn.toLowerCase();
    className = className +
        stripPackageName(cn);
    java.lang.reflect.Method m[] = ru.getAllMethods();
    ////-----> filter!!
    m = ml.filter(m);

    methodList = methodList
        + " " + ru.getClassName() + " " + instanceName + ";\n"
        + getMethodList(m, instanceName);
    ml.add(m);
  }

  public String getInterface() {
    StringBuffer sb = new StringBuffer("interface ");
    sb.append(getInterfaceName() + "Stub extends \n");
    sb.append("\t" + getImplementsList());
    sb.append(" {\n");
    sb.append("}");
    sb.append(getInterfaces());
    return sb.toString();
  }

  public String getInterfaces() {
    StringBuffer sb = new StringBuffer("");
    for (int i = 0; i < instanceList.size(); i++)
      sb.append(getInterface(instanceList.elementAt(i)));
    return sb.toString();
  }

  public String getInterface(Object o) {
    String s = "\n interface "
        + getStubName(o)
        + " {\n"
        + getMethodPrototypes(o)
        + " }";
    return s;
  }

  private String getImplementsList() {
    StringBuffer sb = new StringBuffer("");
    for (int i = 0; i < instanceList.size() - 1; i++)
      sb.append(getStubName(
          instanceList.elementAt(i)) + ", ");
    sb.append(getStubName(
        instanceList.elementAt(instanceList.size() - 1)));
    return sb.toString();
  }

  private String getStrippedClassName(Object o) {
    cutils.reflection.ReflectUtil ru = new cutils.reflection.ReflectUtil(o);
    return stripPackageName(ru.getClassName());
  }

  private String getStubName(Object o) {
    cutils.reflection.ReflectUtil ru = new cutils.reflection.ReflectUtil(o);
    return stripPackageName(ru.getClassName() + "Stub");
  }

  private String getInterfaceName() {
    StringBuffer sb = new StringBuffer("");
    for (int i = 0; i < instanceList.size(); i++)
      sb.append(getStrippedClassName(
          instanceList.elementAt(i)));
    return sb.toString();
  }


  public String getConstructorParameters() {
    StringBuffer sb = new StringBuffer("\n\t");
    for (int i = 0; i < instanceList.size(); i++) {
      cutils.reflection.ReflectUtil ru = new cutils.reflection.ReflectUtil(
          instanceList.elementAt(i));
      String instanceName =
          stripPackageName(ru.getClassName()).toLowerCase();
      sb.append(ru.getClassName()
                + " _"
                + instanceName
      );
      if (i < instanceList.size() - 1)
        sb.append(",\n\t");
    }
    return sb.toString();
  }

  private String getConstructorBody() {
    StringBuffer sb = new StringBuffer("\n\t");
    for (int i = 0; i < instanceList.size(); i++) {
      cutils.reflection.ReflectUtil ru = new cutils.reflection.ReflectUtil(
          instanceList.elementAt(i));
      String instanceName =
          stripPackageName(ru.getClassName()).toLowerCase();
      sb.append(
          instanceName
          + " = _"
          + instanceName
          + ";"
      );
      if (i < instanceList.size() - 1)
        sb.append("\n\t");
    }
    return sb.toString();
  }

  private String getMethodPrototypes(Object o) {
    cutils.reflection.ReflectUtil ru = new cutils.reflection.ReflectUtil(o);
    String cn = stripPackageName(ru.getClassName());
    String instanceName = cn.toLowerCase();
    java.lang.reflect.Method m[] = ru.getAllMethods();
    return getMethodPrototypes(m, instanceName);
  }

  private void processInstance(Object o) {
    cutils.reflection.ReflectUtil ru = new cutils.reflection.ReflectUtil(o);
    String cn = stripPackageName(ru.getClassName());
    String instanceName = cn.toLowerCase();
    className = className +
        stripPackageName(cn);
    java.lang.reflect.Method m[] = ru.getAllMethods();
    methodList = methodList
        + " " + ru.getClassName() + " " + instanceName + ";\n"
        + getMethodList(m, instanceName);
  }

  public String getMethodList(java.lang.reflect.Method m[], String instanceName) {
    String s = "";
    for (int i = 0; i < m.length; i++)
      s = s + getMethodDeclaration(m[i], instanceName) + "\n";
    return s;
  }

  public String getMethodPrototypes(java.lang.reflect.Method m[], String instanceName) {
    String s = "";
    for (int i = 0; i < m.length; i++)
      s = s + getMethodPrototype(m[i], instanceName) + "\n";
    return s;
  }

  public String getMethodDeclaration(java.lang.reflect.Method m, String instanceName) {
    if (isPublic(m))
      return "\t"
          + "public" // strip out other modifiers.
          + " "
          + getReturnType(m)
          + " "
          + m.getName()
          + "("
          + getParameters(m)
          + "){\n\t"
          + getInvocation(m, instanceName)
          + "\t}";
    return "";
  }

  public String getMethodPrototype(java.lang.reflect.Method m, String instanceName) {
    if (isPublic(m))
      return "\t"
          + "public" // strip out other modifiers.
          + " "
          + getReturnType(m)
          + " "
          + m.getName()
          + "("
          + getParameters(m)
          + ");";
    return "";
  }

  public static String getReturnType(java.lang.reflect.Method m) {
    return getTypeName(m.getReturnType());
  }

  public static boolean isReturningVoid(java.lang.reflect.Method m) {
    return getReturnType(m).startsWith("void");
  }

  public static String getModifiers(java.lang.reflect.Method m) {
    return java.lang.reflect.Modifier.toString(m.getModifiers());
  }

  private String getOptionalReturn(java.lang.reflect.Method m) {
    if (isReturningVoid(m)) return "";
    return "return ";
  }

  private String getInvocation(java.lang.reflect.Method m, String instanceName) {
    StringBuffer sb = new StringBuffer(
        "\t"
        + getOptionalReturn(m)
        + instanceName
        + "."
        + m.getName()
        + "("
    );
    Class[] params = m.getParameterTypes();

    for (int j = 0; j < params.length; j++) {
      sb.append("v" + j);
      if (j < (params.length - 1))
        sb.append(",");
    }
    sb.append(");\n");
    return sb.toString();
  }

  public String getParameters(java.lang.reflect.Method m) {
    StringBuffer sb = new StringBuffer("");
    Class[] params = m.getParameterTypes(); // avoid clone
    for (int j = 0; j < params.length; j++) {
      sb.append(
          getTypeName(params[j]) + " v" + j);
      if (j < (params.length - 1))
        sb.append(",");
    }
    return sb.toString();
  }

  public static String getTypeName(Class type) {

    if (!type.isArray())
      return type.getName();

    Class cl = type;
    int dimensions = 0;
    while (cl.isArray()) {
      dimensions++;
      cl = cl.getComponentType();
    }
    StringBuffer sb = new StringBuffer();
    sb.append(cl.getName());

    for (int i = 0; i < dimensions; i++)
      sb.append("[]");

    return sb.toString();
  }


  public static boolean isPublic(java.lang.reflect.Method m) {
    return
        java.lang.reflect.Modifier.toString(m.getModifiers()).startsWith("public");
  }

  public static String stripPackageName(String s) {
    int index = s.lastIndexOf('.');
    if (index == -1) return s;
    index++;
    return s.substring(index);
  }

  private String getConstructor() {
    // public className(class1 _class1Instance, class2 _class2Instance...) {
    //  class1Instance = _class1Instance;
    //  class2Instance = _class2Instance;
    //}
    return "\n// constructor: \npublic "
        + className
        + "("
        + getConstructorParameters()
        + "){"
        + getConstructorBody()
        + "\n}\n\n";
  }

  public String getClassString() {
    return
        "// automatically generated by the DelegateSynthesizer"
        + "\npublic class "
        + className
        + " {\n"
        + getConstructor()
        + methodList
        + "}\n";
  }

  public void print() {
    print(getClassString());
  }

  private void print(Object o) {
    System.out.println(o);
  }


  public static void main(String args[]) {
    DelegateSynthesizer ds = new DelegateSynthesizer();
    cutils.reflection.ReflectUtil ru = new cutils.reflection.ReflectUtil(ds);
    ds.add(ds);
    ds.add(ru);
    ds.process();
    System.out.println(
        ds.getClassString());
  }
}

//ds.getInterface());
// Generated code:

class Movable {
  public void move(int x, int y) {
  };
}