package org.testng.eclipse.ui.conversion;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
import org.eclipse.jdt.internal.corext.codemanipulation.StubUtility;
import org.eclipse.jdt.ui.text.java.IJavaCompletionProposal;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.contentassist.IContextInformation;
import org.eclipse.ltk.core.refactoring.DocumentChange;
import org.eclipse.ltk.core.refactoring.TextChange;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.text.edits.CopyTargetEdit;
import org.eclipse.text.edits.DeleteEdit;
import org.eclipse.text.edits.InsertEdit;
import org.eclipse.text.edits.MalformedTreeException;
import org.eclipse.text.edits.MoveSourceEdit;
import org.eclipse.text.edits.MoveTargetEdit;
import org.eclipse.text.edits.MultiTextEdit;
import org.eclipse.text.edits.ReplaceEdit;
import org.eclipse.text.edits.TextEdit;
import org.eclipse.text.edits.TextEditVisitor;
import org.eclipse.text.edits.UndoEdit;
import org.testng.eclipse.TestNGPlugin;
import org.testng.eclipse.ui.Images;

/**
 * This class implements the proposal to convert a JUnit class into a TestNG one.
 * 
 * Created on Aug 8, 2005
 * @author cbeust
 */
public class JUnitRewriteCorrectionProposal implements IJavaCompletionProposal 
{
  private ASTRewrite m_rewriter;
  private ICompilationUnit m_cu;
  private String m_name;

  public JUnitRewriteCorrectionProposal(String name, ICompilationUnit cu,
      ASTRewrite rewriter, int i) {
    m_name = name;
    m_rewriter = rewriter;
    m_cu = cu;
  }

  public void apply(IDocument document) {
    TextEdit edits = m_rewriter.rewriteAST(document, null);
    UndoEdit undo = null;
    try {
        undo = edits.apply(document);
    } catch(MalformedTreeException e) {
        e.printStackTrace();
    } catch(BadLocationException e) {
        e.printStackTrace();
    }

  }

  public Point getSelection(IDocument document) {
    // TODO Auto-generated method stub
    return null;
  }

  public String getAdditionalProposalInfo() {
    return getHtml();
  }

  protected TextChange createTextChange() throws CoreException {
    ICompilationUnit cu = m_cu;
    String name = "TestNG";
    TextChange change = null;
//    if (!cu.getResource().exists()) {
//      String source;
//      try {
//        source= cu.getSource();
//      } catch (JavaModelException e) {
//        JavaPlugin.log(e);
//        source= new String(); // empty
//      }
//    }
    Document document= new Document(cu.getSource());
    document.setInitialLineDelimiter(StubUtility.getLineDelimiterUsed(cu));
    change= new DocumentChange(name, document);
//    } else {
//      Document doc = new Document(m_cu.getSource());
//      CompilationUnitChange cuChange = new CompilationUnitChange(name, cu);
//      cuChange.setSaveMode(TextFileChange.LEAVE_DIRTY);
//      change= cuChange;
//    }
    TextEdit rootEdit= new MultiTextEdit();
    change.setEdit(rootEdit);

    // initialize text change
//    IDocument document= change.getCurrentDocument(new NullProgressMonitor());
    addEdits(change.getCurrentDocument(new NullProgressMonitor()), rootEdit);
    return change;
  }

  protected void addEdits(IDocument document, TextEdit editRoot) throws CoreException {
//    super.addEdits(document, editRoot);
    ASTRewrite rewrite = m_rewriter;
    if (rewrite != null) {
      try {
        TextEdit edit= rewrite.rewriteAST();
        editRoot.addChild(edit);
      } catch (IllegalArgumentException e) {
        throw new CoreException(TestNGPlugin.createError(e));
      }
    }
//    if (fImportRewrite != null) {
//      editRoot.addChild(fImportRewrite.rewriteImports(new NullProgressMonitor()));
//    }
  }

  private TextChange getTextChange() {
    try {
      return createTextChange();
    } catch (CoreException e) {
      e.printStackTrace();
    }
    return null;
  }

  /**
   * @return an HTML version of the change that's about to be made, with changes
   * highlighted in bold.
   */
  private String getHtml() {
    final StringBuffer buf= new StringBuffer();

    try {
      final TextChange change= getTextChange();

      change.setKeepPreviewEdits(true);
      final IDocument previewContent= change.getPreviewDocument(new NullProgressMonitor());
      final TextEdit rootEdit= change.getPreviewEdit(change.getEdit());

      class EditAnnotator extends TextEditVisitor {
        private int m_writtenToPos = 0;

        public void unchangedUntil(int pos) {
          if (pos > m_writtenToPos) {
            appendContent(previewContent, m_writtenToPos, pos, buf, true);
            m_writtenToPos = pos;
          }
        }

        public boolean visit(MoveTargetEdit edit) {
          return true; //rangeAdded(edit);
        }

        public boolean visit(CopyTargetEdit edit) {
          return true; //return rangeAdded(edit);
        }

        public boolean visit(InsertEdit edit) {
          return rangeAdded(edit);
        }

        public boolean visit(ReplaceEdit edit) {
          if (edit.getLength() > 0)
            return rangeAdded(edit);
          return rangeRemoved(edit);
        }

        public boolean visit(MoveSourceEdit edit) {
          return rangeRemoved(edit);
        }

        public boolean visit(DeleteEdit edit) {
          return rangeRemoved(edit);
        }

        private boolean rangeRemoved(TextEdit edit) {
          unchangedUntil(edit.getOffset());
          return false;
        }

        private boolean rangeAdded(TextEdit edit) {
          unchangedUntil(edit.getOffset());
          buf.append("<b>"); //$NON-NLS-1$
          appendContent(previewContent, edit.getOffset(), edit.getExclusiveEnd(), buf, false);
          buf.append("</b>"); //$NON-NLS-1$
          m_writtenToPos = edit.getExclusiveEnd();
          return false;
        }
      }
      EditAnnotator ea = new EditAnnotator();
      rootEdit.accept(ea);

      // Final pre-existing region
      ea.unchangedUntil(previewContent.getLength());
    } catch (CoreException e) {
      TestNGPlugin.log(e);
    }
    return buf.toString();
  }

  private final int m_surroundLines= 1;

  private void appendContent(IDocument text, int startOffset, int endOffset, StringBuffer buf, 
      boolean surroundLinesOnly) {
    try {
      int startLine= text.getLineOfOffset(startOffset);
      int endLine= text.getLineOfOffset(endOffset);

      boolean dotsAdded= false;
      if (surroundLinesOnly && startOffset == 0) { // no surround lines for the top no-change range
        startLine= Math.max(endLine - m_surroundLines, 0);
        buf.append("...<br>"); //$NON-NLS-1$
        dotsAdded= true;
      }

      for (int i= startLine; i <= endLine; i++) {
        if (surroundLinesOnly) {
          if ((i - startLine > m_surroundLines) && (endLine - i > m_surroundLines)) {
            if (!dotsAdded) {
              buf.append("...<br>"); //$NON-NLS-1$
              dotsAdded= true;
            } else if (endOffset == text.getLength()) {
              return; // no surround lines for the bottom no-change range
            }
            continue;
          }
        }

        IRegion lineInfo= text.getLineInformation(i);
        int start= lineInfo.getOffset();
        int end= start + lineInfo.getLength();

        int from= Math.max(start, startOffset);
        int to= Math.min(end, endOffset);
        String content= text.get(from, to - from);
        if (surroundLinesOnly && (from == start) && containsOnlyWhitespaces(content)) {
          continue; // ignore empty lines except when range started in the middle of a line
        }
        for (int k= 0; k < content.length(); k++) {
          char ch= content.charAt(k);
          if (ch == '<') {
            buf.append("&lt;"); //$NON-NLS-1$
          } else if (ch == '>') {
            buf.append("&gt;"); //$NON-NLS-1$
          } else {
            buf.append(ch);
          }
        }
        if (to == end && to != endOffset) {
          // new line when at the end of the line, and not end of range
          buf.append("<br>"); //$NON-NLS-1$
        }
      }
    } catch (BadLocationException e) {
      // ignore
    }
  }

  private static boolean containsOnlyWhitespaces(String s) {
    int size= s.length();
    for (int i= 0; i < size; i++) {
      if (!Character.isWhitespace(s.charAt(i)))
        return false;
    }
    return true;
  }

  public String getDisplayString() {
    return m_name;
  }

  public Image getImage() {
    return Images.getTestNGImage();
  }

  public IContextInformation getContextInformation() {
    return null;
  }

  public int getRelevance() {
    return 0;
  }

}
