/**
 * 
 */
package org.objectstyle.wolips.wodclipse.core.refactoring;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.jdt.core.Flags;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IField;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.NamingConventions;
import org.eclipse.jdt.core.Signature;
import org.eclipse.jdt.core.formatter.CodeFormatter;
import org.eclipse.jdt.internal.core.CreateFieldOperation;
import org.eclipse.jdt.internal.core.CreateImportOperation;
import org.eclipse.jdt.internal.core.CreateMethodOperation;
import org.eclipse.jdt.internal.core.JavaModelOperation;
import org.eclipse.jdt.internal.corext.codemanipulation.GetterSetterUtil;
import org.eclipse.jdt.internal.corext.util.CodeFormatterUtil;
import org.eclipse.jdt.internal.corext.util.JavaModelUtil;
import org.eclipse.jdt.ui.JavaUI;
import org.eclipse.jface.text.IRewriteTarget;
import org.eclipse.ui.IEditorPart;
import org.objectstyle.wolips.bindings.utils.BindingReflectionUtils;

public class AddKeyOperation extends JavaModelOperation {
  public static void addKey(AddKeyInfo info) throws CoreException {
    IEditorPart editorPart = JavaUI.openInEditor(info.getComponentType().getCompilationUnit());
    if (editorPart != null) {
      IRewriteTarget target = (IRewriteTarget) editorPart.getAdapter(IRewriteTarget.class);
      if (target != null) {
        target.beginCompoundChange();
      }
      try {
        new AddKeyOperation(info).run(null);
      } finally {
        if (target != null) {
          target.endCompoundChange();
        }
      }
    }
  }

  public static void replaceField(AddKeyInfo info, String originalName) throws CoreException {
    IEditorPart editorPart = JavaUI.openInEditor(info.getComponentType().getCompilationUnit());
    if (editorPart != null) {
      IRewriteTarget target = (IRewriteTarget) editorPart.getAdapter(IRewriteTarget.class);
      if (target != null) {
        target.beginCompoundChange();
      }
      try {
        String newName = info.getName();

        info.setName(originalName);
        String originalFieldName = info.getFieldName();
        info.setName(newName);

        // This is less than ideal, but will do for now. At least this way the field order
        // should remain the same.
        IField originalField = info.getComponentType().getField(originalFieldName);
        if (info.isCreateField() && originalField.exists()) {
          AddKeyOperation op = new AddKeyOperation(info);
          op._sibling = originalField;
          op._force = true;
          op.run(null);
          originalField.delete(false, null);
        }
      } finally {
        if (target != null) {
          target.endCompoundChange();
        }
      }
    }
  }

  private boolean _force = false;

  private AddKeyInfo _info;

  private IJavaElement _sibling;

  public AddKeyOperation(AddKeyInfo info) {
    super(new IJavaElement[] { info.getComponentType() });
    _info = info;
  }

  private void createAccessor(String simpleTypeName, String fieldName) throws JavaModelException {
    boolean isBoolean = BindingReflectionUtils.isBoolean(_info.getJavaTypeName());
    IType componentType = getComponentType();

    String accessorMethodName;
    if (_info.isPrependGetToAccessorMethod()) {
      accessorMethodName = NamingConventions.suggestGetterName(componentType.getJavaProject(), fieldName, Flags.AccPublic, isBoolean, null);
    } else {
      accessorMethodName = _info.getName();
    }

    String source = null;
    if (_info.isCreateField()) {
      try {
        source = GetterSetterUtil.getGetterStub(componentType.getField(fieldName), accessorMethodName, true, Flags.AccPublic);
      } catch (CoreException e) {
        throw new JavaModelException(e);
      }
    } else {
      source = "public " + simpleTypeName + " " + accessorMethodName + "() {\n" + " // TODO\n" + "return null;\n" + "}";
    }
    if (source != null) {
      String lineDelim = "\n";// TextUtilities.getDefaultLineDelimiter(document);
      source = CodeFormatterUtil.format(CodeFormatter.K_CLASS_BODY_DECLARATIONS, source, 1, null, componentType.getJavaProject());
      new CreateMethodOperation(componentType, source, false).runOperation(progressMonitor);
    }
  }

  private void createField(String simpleTypeName, String fieldName) throws JavaModelException {
    IType componentType = getComponentType();

    String accessModifier = "private ";
    if (!(_info.isCreateAccessorMethod() || _info.isCreateMutatorMethod())) {
      accessModifier = "public ";
    }
    String keyField = accessModifier + simpleTypeName + " " + fieldName + ";";
    CreateFieldOperation op = new CreateFieldOperation(componentType, keyField, _force);
    if (_sibling != null) {
      op.createAfter(_sibling);
    }
    op.runOperation(progressMonitor);
  }

  private void createImports(String keyType, String keyParameterType) throws JavaModelException {
    if (BindingReflectionUtils.isImportRequired(keyType)) {
      new CreateImportOperation(keyType, getCompilationUnit(), Flags.AccDefault).runOperation(progressMonitor);
    }

    if (BindingReflectionUtils.isImportRequired(keyParameterType)) {
      new CreateImportOperation(keyParameterType, getCompilationUnit(), Flags.AccDefault).runOperation(progressMonitor);
    }
  }

  private void createMutator(String simpleTypeName, String fieldName) throws JavaModelException {
    boolean isBoolean = BindingReflectionUtils.isBoolean(_info.getJavaTypeName());
    IType componentType = getComponentType();

    String mutatorMethodName = NamingConventions.suggestSetterName(getJavaProject(), fieldName, Flags.AccPublic, isBoolean, null);

    String source = null;
    if (_info.isCreateField()) {
      try {
        source = GetterSetterUtil.getSetterStub(componentType.getField(fieldName), mutatorMethodName, true, Flags.AccPublic);
      } catch (CoreException e) {
        throw new JavaModelException(e);
      }
    } else {
      source = "public void " + mutatorMethodName + "(" + simpleTypeName + " " + _info.getName() + ") {\n" + " // TODO\n" + "}";
    }
    if (source != null) {
      String lineDelim = "\n";// TextUtilities.getDefaultLineDelimiter(document);
      source = CodeFormatterUtil.format(CodeFormatter.K_CLASS_BODY_DECLARATIONS, source, 1, null, getJavaProject());
      new CreateMethodOperation(componentType, source, false).runOperation(progressMonitor);
    }
  }

  @Override
  protected void executeOperation() throws JavaModelException {
    String keyType = _info.getJavaTypeName();
    String keyParameterType = _info.getJavaParameterTypeName();

    String simpleTypeName = simpleTypeName(keyType, keyParameterType);
    String fieldName = _info.getFieldName();

    createImports(keyType, keyParameterType);

    if (_info.isCreateField()) {
      createField(simpleTypeName, fieldName);
    }

    if (_info.isCreateAccessorMethod()) {
      createAccessor(simpleTypeName, fieldName);
    }

    if (_info.isCreateMutatorMethod()) {
      createMutator(simpleTypeName, fieldName);
    }
  }

  private ICompilationUnit getCompilationUnit() {
    return _info.getComponentType().getCompilationUnit();
  }

  private IType getComponentType() {
    return _info.getComponentType();
  }

  private IJavaProject getJavaProject() {
    return _info.getComponentType().getJavaProject();
  }

  private String simpleTypeName(String keyType, String keyParameterType) {
    boolean useGenerics = JavaModelUtil.is50OrHigher(getJavaProject());
    String simpleTypeName;
    if (keyParameterType != null && useGenerics) {
      simpleTypeName = Signature.getSimpleName(keyType) + "<" + Signature.getSimpleName(keyParameterType) + ">";
    } else {
      simpleTypeName = Signature.getSimpleName(keyType);
    }
    return simpleTypeName;
  }
}