package cutils.delegate;

import javax.swing.*;
import java.awt.*;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Vector;

/**
 DelegateSynthesizer class
 @author: Job Shen
 These classes are use use reflectutil to automatically
 generate a new class file
 */

public class DelegateSynthesizer {
  private String className = "";
  private String methodList = "";
  private Vector instanceList = new Vector();
  private Vector uniqueMethodList = new Vector();
  private Vector dupMethodList = new Vector();
  private Vector doMethodList = new Vector();
  private boolean choseToplogic = false;

  public void chooseToplogic(boolean b) {
    choseToplogic = b;
  }

  // compare method with all methods in uniqueMethodList, if equal,
  // then add it to dupmethodlist, or else append at the end.
  public void addMethod(String cn, Method m) {
    int i, j;
    boolean pos_found = false;
    Method other;

    i = 0;
    j = uniqueMethodList.size();
    getException(m);
    try {
      while (i <= j && !pos_found) {
        if (i == j) {
//                  uniqueClassnameList.addElement(cn);
          uniqueMethodList.addElement(m);
          pos_found = true;
        } else {
          other = (Method) uniqueMethodList.elementAt(i);
          if (equals(other, m)) {
            // if it's toplogic, do nothing
            addNonTopologic(other);

            // add current to dup vectors
            getDupMethodList().addElement(m);
            pos_found = true;
          }
          i++;
        }
      }
    } catch (Exception e) {
    }
  }

  private void addNonTopologic(Method other) {
    if (!choseToplogic) {
      // duplicate record, copy from unique to dup vector
      // but if the same one has been copied alread, do not copy again
      if (getDupMethodList().indexOf(other) == -1)
        getDupMethodList().addElement(other);
    }
  }

  public void addDupMethod(Object o) {
    getDupMethodList().addElement(o);
  }

  public void removeDupMethod(Object o) {
    getDupMethodList().removeElement(o);
  }

  public void addDoMethod(Object o) {
    getDoMethodList().addElement(o);
  }

  public void removeDoMethod(Object o) {
    getDoMethodList().removeElement(o);
  }


  // build both unique method list and duplicate method list
  public void buildMethodList(Object o) {
    ReflectUtil ru = new ReflectUtil(o);
    String cn = stripPackageName(ru.getClassName());
    String instanceName = cn.toLowerCase();

    className = className + stripPackageName(cn);
    Method m[] = ru.getAllMethods();
    System.out.println("\nClass: " + cn);

    for (int i = 0; i < m.length; i++)
      addMethod(cn, m[i]);

  }

  public void process() {
    for (int i = 0; i < instanceList.size(); i++)
      buildMethodList(instanceList.elementAt(i));

    System.out.println("\ntotal unique method: " + uniqueMethodList.size() +
                       "\nTotal duplicated method: " + getDupMethodList().size());
  }

  public void add(Object o) {
    instanceList.addElement(o);
  }

  private String getException(Method m) {
    Class c[];
    String s = new String();
    c = m.getExceptionTypes();
    if (c.length > 0) {
      s = "throws ";
      s = s + c[0].getName();
    }
    // in case have more than one exception, this will work
    for (int i = 1; i < c.length; i++)
      s = s + "," + c[i].getName();

    return s;
  }


  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) {
    ReflectUtil ru = new ReflectUtil(o);
    return stripPackageName(ru.getClassName());
  }

  private String getStubName(Object o) {
    ReflectUtil ru = new 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++)
      buildConstructorParameter(i, sb);
    return sb.toString();
  }

  private void buildConstructorParameter(int i, StringBuffer sb) {
    ReflectUtil ru = new ReflectUtil(
        instanceList.elementAt(i));
    String instanceName =
        stripPackageName(ru.getClassName()).toLowerCase();
    sb.append(ru.getClassName()
              + " _"
              + instanceName
    );
    if (i < instanceList.size() - 1)
      sb.append(",\n\t");
  }

  private String getConstructorBody() {
    StringBuffer sb = new StringBuffer("\n\t");
    for (int i = 0; i < instanceList.size(); i++)
      buildConstructor(i, sb);
    return sb.toString();
  }

  private void buildConstructor(int i, StringBuffer sb) {
    ReflectUtil ru = new ReflectUtil(
        instanceList.elementAt(i));
    String instanceName =
        stripPackageName(ru.getClassName()).toLowerCase();
    sb.append(
        instanceName
        + " = _"
        + instanceName
        + ";"
    );
    if (i < instanceList.size() - 1)
      sb.append("\n\t");
  }

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

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

  public String getMethodList(Method m[], String instanceName) {
    String s = "";
    for (int i = 0; i < m.length; i++)
      if (getDupMethodList().indexOf(m[i]) == -1)
        s = s + getMethodDeclaration(m[i], instanceName) + "\n";
    return s;
  }

  public String getMethodPrototypes(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(Method m, String instanceName) {
    if (isPublic(m))
      return "\t"
          + "public" // strip out other modifiers.
          + " "
          + getReturnType(m)
          + " "
          + m.getName()
          + "("
          + getParameters(m)
          + ") "
          + getException(m)
          + " {\n\t"
          + getInvocation(m, instanceName)
          + "\t}";
    return "";
  }

  public String getMethodPrototype(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(Method m) {
    return getTypeName(m.getReturnType());
  }

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

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

  private String getOptionalReturn(Method m) {
    if (isReturningVoid(m)) return "";
    return "return ";
  }

  private String getInvocation(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(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(Method m) {
    return
        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);
  }

  /**
   * Compares this <code>Method</code> against the specified object.  Returns
   * true if the objects are the same.  Two <code>Methods</code> are the same if
   * they were declared by the same class and have the same name
   * and formal parameter types.
   */
  public boolean equals(Method obj1, Method obj2) {
    if (obj1 != null && obj2 != null) {
      if ((obj1.getName().equals(obj2.getName()))) {
        /* Avoid unnecessary cloning */
        Class[] params1 = obj1.getParameterTypes();
        Class[] params2 = obj2.getParameterTypes();
        if (params1.length == params2.length) {
          for (int i = 0; i < params1.length; i++) {
            if (params1[i] != params2[i])
              return false;
          }
          return true;
        }
      }
    }
    return false;
  }

  public String getSaveFileName(String prompt) {
    FileDialog fd = new FileDialog(new Frame(),
                                   prompt,
                                   FileDialog.SAVE);
    fd.setFile(className + ".java");
    fd.setVisible(true);
    fd.setVisible(false);
    String fn = fd.getDirectory() + fd.getFile();
    if (fd.getFile() == null) return null;
    return fn;
  }


  public void createFile() {
    String fn = getSaveFileName("save file as");
    if (fn == null) {
      JOptionPane.showMessageDialog(null, "No filename is specified! operation aborted.", "Wait a second",
                                    JOptionPane.INFORMATION_MESSAGE);
      return;
    }

    File f = new File(fn);
    BufferedWriter bw = null;
    Object[] savedDupMethodList = new Object[getDupMethodList().size()];
    Object[] savedDoMethodList = new Object[getDoMethodList().size()];
    // open file
    try {
      bw = new BufferedWriter(new FileWriter(f));
    } catch (Exception e) {
    }

    // default is user chosen solving ambg, now we need to rebuild
    // the dupMethodList according to toplogic rule
    if (choseToplogic == true)
      topoSave(savedDupMethodList, savedDoMethodList);

    className = "";
    methodList = "";
    for (int i = 0; i < instanceList.size(); i++)
      processInstance(instanceList.elementAt(i));

    try {
      bw.write("package finalexam;\n" + getClassString() + getInterface());
    } catch (Exception e) {
    }

    try {
      bw.close();
    } catch (Exception e) {
    }

    if (choseToplogic == true)
      sortTopologic(savedDupMethodList, savedDoMethodList);

  }

  private void sortTopologic(Object[] savedDupMethodList, Object[] savedDoMethodList) {
    //put data back to dup list
    getDupMethodList().removeAllElements();
    getDoMethodList().removeAllElements();
    for (int i = 0; i < savedDupMethodList.length; i++)
      getDupMethodList().addElement(savedDupMethodList[i]);
    for (int i = 0; i < savedDoMethodList.length; i++)
      getDoMethodList().addElement(savedDoMethodList[i]);
  }

  private void topoSave(Object[] savedDupMethodList, Object[] savedDoMethodList) {
    // reset the dup method list and unique method list
    getDupMethodList().copyInto(savedDupMethodList);
    getDoMethodList().copyInto(savedDoMethodList);
    getDupMethodList().removeAllElements();
    getDoMethodList().removeAllElements();
    uniqueMethodList.removeAllElements();
    for (int i = 0; i < instanceList.size(); i++)
      buildMethodList(instanceList.elementAt(i));
  }


  // write the specified string to file
  public void writeSaveFile(BufferedWriter bw, String s) {
    try {
      bw.write(s);
    } catch (Exception e) {
    }

  }

  public static void main(String args[]) {
    DelegateSynthesizer ds = new DelegateSynthesizer();
    ReflectUtil ru = new ReflectUtil(ds);
//      ds.add(new ChineseExample());
//      ds.add(new AmericanExample());
//      ds.add(new Date());
    ds.add(new String());
    ds.process();
    System.out.println(
        ds.getClassString() +
        ds.getInterface());
  }

  public Vector getDoMethodList() {
    return doMethodList;
  }

  public void setDoMethodList(Vector doMethodList) {
    this.doMethodList = doMethodList;
  }

  public Vector getDupMethodList() {
    return dupMethodList;
  }

  public void setDupMethodList(Vector dupMethodList) {
    this.dupMethodList = dupMethodList;
  }
}