// Copyright 2014 Connectifier, Inc. All Rights Reserved.

package com.connectifier.xeroclient.jaxb;

import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;

import com.sun.codemodel.JClass;
import com.sun.codemodel.JCodeModel;
import com.sun.codemodel.JDefinedClass;
import com.sun.codemodel.JExpr;
import com.sun.codemodel.JFieldVar;
import com.sun.codemodel.JFormatter;
import com.sun.codemodel.JMethod;
import com.sun.codemodel.JOp;
import com.sun.codemodel.JType;
import com.sun.tools.xjc.Options;
import com.sun.tools.xjc.Plugin;
import com.sun.tools.xjc.outline.ClassOutline;
import com.sun.tools.xjc.outline.Outline;

public class PluginImpl extends Plugin {

  @Override
  public String getOptionName() {
    return "Xcustom";
  }

  @Override
  public String getUsage() {
    return "  -Xcustom alter generated code with client library improvements";
  }

  @Override
  public boolean run(Outline outline, Options opt, ErrorHandler errorHandler) throws SAXException {
    JCodeModel model = outline.getModel().codeModel;
    for (ClassOutline co : outline.getClasses()) {
      updateArrayOfGetters(co, model);
      updateArrayOfSetters(co, model);
    }

    return true;
  }

  /**
   * Update getters to use Java List. For example:
   * ArrayOfInvoices getInvoices() -> List<Invoice> getInvoices()
   */
  private void updateArrayOfGetters(ClassOutline co, JCodeModel model) {
    JDefinedClass implClass = co.implClass;

    List<JMethod> removedMethods = new ArrayList<>();
    Iterator<JMethod> iter = implClass.methods().iterator();
    while (iter.hasNext()) {
      JMethod method = iter.next();
      if (method.type().name().startsWith("ArrayOf")) {
        removedMethods.add(method);
        iter.remove();
      }
    }

    for (JMethod removed : removedMethods) {
      // Parse the old code to get the variable name
      StringWriter oldWriter = new StringWriter();
      removed.body().state(new JFormatter(oldWriter));
      String oldBody = oldWriter.toString();
      String varName = oldBody.substring(oldBody.indexOf("return ") + "return ".length(), oldBody.indexOf(";"));

      // Build the new method
      JClass newReturnType = (JClass) ((JDefinedClass) removed.type()).fields().values().iterator().next().type();
      JMethod newMethod = implClass.method(removed.mods().getValue(), newReturnType, removed.name());
      JFieldVar field = implClass.fields().get(varName);          
      JClass typeParameter = newReturnType.getTypeParameters().get(0);
      String fieldName = model._getClass(field.type().fullName()).fields().keySet().iterator().next();

      newMethod.body()._return(
          JOp.cond(field.eq(JExpr._null()),
              JExpr._new(model.ref("java.util.ArrayList").narrow(typeParameter)),
              field.invoke("get" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1))));
    }    
  }

  /**
   * Update setters to use Java List. For example:
   * setLineItems(ArrayOfLineItem value) -> setLineItems(List<LineItem> value)
   */
  private void updateArrayOfSetters(ClassOutline co, JCodeModel model) {
    JDefinedClass implClass = co.implClass;

    List<JMethod> removedMethods = new ArrayList<>();
    Iterator<JMethod> iter = implClass.methods().iterator();
    while (iter.hasNext()) {
      JMethod method = iter.next();
      if (method.params().size() == 1 && method.params().get(0).type().name().startsWith("ArrayOf")) {
        removedMethods.add(method);
        iter.remove();
      }
    }

    for (JMethod removed : removedMethods) {
      // Parse the old code to get the variable name
      StringWriter oldWriter = new StringWriter();
      removed.body().state(new JFormatter(oldWriter));
      String oldBody = oldWriter.toString();
      String varName = oldBody.substring(oldBody.indexOf("this.") + "this.".length(), oldBody.indexOf(" = "));

      // Build the new method
      JType arrType = removed.params().get(0).type();
      String type = arrType.name().substring("ArrayOf".length());
      JFieldVar field = implClass.fields().get(varName);  
      String fieldName = model._getClass(field.type().fullName()).fields().keySet().iterator().next();

      JMethod newMethod = implClass.method(removed.mods().getValue(), Void.TYPE, removed.name());
      newMethod.param(model.ref("java.util.List").narrow(model.ref(type)), "value");
      newMethod.body().decl(arrType, "arr", JExpr._new(arrType));
      newMethod.body().directStatement("arr.get" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1) + "().addAll(value);");
      newMethod.body().directStatement("this." + varName + " = arr;");
    }
  }

}