/*******************************************************************************
 * Copyright (c) 2000, 2004 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Common Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/cpl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.testng.eclipse.ui;


import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.eclipse.compare.CompareConfiguration;
import org.eclipse.compare.CompareViewerPane;
import org.eclipse.compare.IEncodedStreamContentAccessor;
import org.eclipse.compare.ITypedElement;
import org.eclipse.compare.contentmergeviewer.TextMergeViewer;
import org.eclipse.compare.structuremergeviewer.DiffNode;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.DialogSettings;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.IDialogSettings;
import org.eclipse.jface.text.DocumentEvent;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITypedRegion;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.TextAttribute;
import org.eclipse.jface.text.TextPresentation;
import org.eclipse.jface.text.TextViewer;
import org.eclipse.jface.text.presentation.IPresentationDamager;
import org.eclipse.jface.text.presentation.IPresentationReconciler;
import org.eclipse.jface.text.presentation.IPresentationRepairer;
import org.eclipse.jface.text.presentation.PresentationReconciler;
import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.jface.text.source.SourceViewer;
import org.eclipse.jface.text.source.SourceViewerConfiguration;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyleRange;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.ControlListener;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.testng.eclipse.TestNGPlugin;
import org.testng.eclipse.util.ResourceUtil;

/**
 * Small modifications to fit TestNG.
 * 
 * @author <a href='mailto:the_mindstorm@evolva.ro'>Alexandru Popescu</a>
 */
public class CompareResultDialog extends Dialog {

  /* workaround - to make prefix and suffix accessible to the CompareResultViewerConfiguration */
  private static CompareResultDialog fgThis;

  // dialog store id constants
  private final static String        DIALOG_BOUNDS_KEY = "CompareResultDialogBounds"; //$NON-NLS-1$
  private static final String        X = "x"; //$NON-NLS-1$
  private static final String        Y = "y"; //$NON-NLS-1$
  private static final String        WIDTH = "width"; //$NON-NLS-1$
  private static final String        HEIGHT = "height"; //$NON-NLS-1$

  private TextMergeViewer            fViewer;
  private String                     fExpected;
  private String                     fActual;
  private String                     fTestName;

  private int                        fPrefix;
  private int                        fSuffix;
  private IDialogSettings            fSettings;
  protected Rectangle                fNewBounds;

  static List<Pattern> patternList;
  static String[] regexes = { 
      ".*expected:<(.*)> but was:<(.*)>.*", 
      ".*expected not same with:<(.*)> but was same:<(.*)>.*", 
      ".*expected same with:<(.*)> but was:<(.*)>.*", 
      ".*expected \\[(.*)\\] but found \\[(.*)\\].*" 
      };

  public CompareResultDialog(Shell parentShell, RunInfo failure) {
    super(parentShell);
    fgThis = this;
    setShellStyle(getShellStyle() | SWT.RESIZE | SWT.MAX);
    fTestName = failure.getMethodName();
    initializeActualExpected(failure.getStackTrace());
    computePrefixSuffix();
    fSettings = TestNGPlugin.getDefault().getDialogSettings();
  }

  // TESTNG-21
  private void initializeActualExpected(String trace) {
    if (trace.indexOf("hamcrest") > 0) parseHamCrestTrace(trace);
    else parseTestNGTrace(trace);
  }

  private void parseHamCrestTrace(String trace) {
    String IS = "is ";
    String WAS = "was ";
    int ind1 = trace.indexOf(IS);
    int ind2 = trace.indexOf("\n", ind1);
    int ind3 = trace.indexOf(WAS, ind2);
    int ind4 = trace.indexOf("\n", ind3);
    fExpected = trace.substring(ind1 + IS.length(), ind2);
    fActual = trace.substring(ind3 + WAS.length(), ind4);
  }
  
  static List<Pattern> getPatterns() {
    if (patternList == null) {
      patternList = new ArrayList<Pattern>();
      for (String rgx : regexes) {
        patternList.add(Pattern.compile(rgx, Pattern.DOTALL));
      }
    }
    return patternList;
  }
  
  private void parseTestNGTrace(String trace) {
    Matcher m;
    for (Pattern p : getPatterns()) {
      m = p.matcher(trace);
      if (m.find()) {
        fExpected = m.group(1);
        fActual = m.group(2);
        return;
      }
    }
    fActual= "N/A";
    fExpected= "N/A";
    return;
  }
  
  protected Point getInitialSize() {
    int         width = 0;
    int         height = 0;

    final Shell s = getShell();
    if(s != null) {
      s.addControlListener(new ControlListener() {
          public void controlMoved(ControlEvent arg) {
            fNewBounds = s.getBounds();
          }

          public void controlResized(ControlEvent arg) {
            fNewBounds = s.getBounds();
          }
        });
    }
    IDialogSettings bounds = fSettings.getSection(DIALOG_BOUNDS_KEY);
    if(bounds == null) {
      return super.getInitialSize();
    }
    else {
      try {
        width = bounds.getInt(WIDTH);
      }
      catch(NumberFormatException e) {
        width = 400;
      }
      try {
        height = bounds.getInt(HEIGHT);
      }
      catch(NumberFormatException e) {
        height = 300;
      }
    }

    return new Point(width, height);
  }

  protected Point getInitialLocation(Point initialSize) {
    Point           loc = super.getInitialLocation(initialSize);

    IDialogSettings bounds = fSettings.getSection(DIALOG_BOUNDS_KEY);
    if(bounds != null) {
      try {
        loc.x = bounds.getInt(X);
      }
      catch(NumberFormatException e) {
      }
      try {
        loc.y = bounds.getInt(Y);
      }
      catch(NumberFormatException e) {
      }
    }

    return loc;
  }

  public boolean close() {
    boolean closed = super.close();
    if(closed && (fNewBounds != null)) {
      saveBounds(fNewBounds);
    }

    return closed;
  }

  private void saveBounds(Rectangle bounds) {
    IDialogSettings dialogBounds = fSettings.getSection(DIALOG_BOUNDS_KEY);
    if(dialogBounds == null) {
      dialogBounds = new DialogSettings(DIALOG_BOUNDS_KEY);
      fSettings.addSection(dialogBounds);
    }
    dialogBounds.put(X, bounds.x);
    dialogBounds.put(Y, bounds.y);
    dialogBounds.put(WIDTH, bounds.width);
    dialogBounds.put(HEIGHT, bounds.height);
  }

  private void computePrefixSuffix() {
    int end = Math.min(fExpected.length(), fActual.length());
    int i = 0;
    for(; i < end; i++) {
      if(fExpected.charAt(i) != fActual.charAt(i)) {
        break;
      }
    }
    fPrefix = i;

    int j = fExpected.length() - 1;
    int k = fActual.length() - 1;
    int l = 0;
    for(; (k >= fPrefix) && (j >= fPrefix); k--, j--) {
      if(fExpected.charAt(j) != fActual.charAt(k)) {
        break;
      }
      l++;
    }
    fSuffix = l;
  }

  protected void configureShell(Shell newShell) {
    super.configureShell(newShell);
    newShell.setText(ResourceUtil.getString("CompareResultDialog.title")); //$NON-NLS-1$
  }

  protected void createButtonsForButtonBar(Composite parent) {
    createButton(parent,
                 IDialogConstants.OK_ID,
                 ResourceUtil.getString("CompareResultDialog.labelOK"),
                 true); //$NON-NLS-1$
  }

  protected Control createDialogArea(Composite parent) {
    Composite  composite = (Composite) super.createDialogArea(parent);
    GridLayout layout = new GridLayout();
    layout.numColumns = 1;
    composite.setLayout(layout);

    CompareViewerPane pane = new CompareViewerPane(composite, SWT.BORDER | SWT.FLAT);
    pane.setText(fTestName);
    GridData data = new GridData(GridData.FILL_HORIZONTAL | GridData.FILL_VERTICAL);
    data.widthHint = convertWidthInCharsToPixels(120);
    data.heightHint = convertHeightInCharsToPixels(13);
    pane.setLayoutData(data);

    Control previewer = createPreviewer(pane);
    pane.setContent(previewer);
    GridData gd = new GridData(GridData.FILL_BOTH);
    previewer.setLayoutData(gd);
    applyDialogFont(parent);

    return composite;
  }

  private Control createPreviewer(Composite parent) {
    final CompareConfiguration compareConfiguration = new CompareConfiguration();
    compareConfiguration.setLeftLabel(ResourceUtil.getString("CompareResultDialog.expectedLabel")); //$NON-NLS-1$
    compareConfiguration.setLeftEditable(false);
    compareConfiguration.setRightLabel(ResourceUtil.getString("CompareResultDialog.actualLabel")); //$NON-NLS-1$
    compareConfiguration.setRightEditable(false);
    compareConfiguration.setProperty(CompareConfiguration.IGNORE_WHITESPACE, Boolean.FALSE);

    fViewer = new CompareResultMergeViewer(parent, SWT.NONE, compareConfiguration);
    fViewer.setInput(new DiffNode(new CompareElement(fExpected), new CompareElement(fActual)));

    Control control = fViewer.getControl();
    control.addDisposeListener(new DisposeListener() {
        public void widgetDisposed(DisposeEvent e) {
          if(compareConfiguration != null) {
            compareConfiguration.dispose();
          }
        }
      });

    return control;
  }

  private static class CompareResultMergeViewer extends TextMergeViewer {
    private CompareResultMergeViewer(Composite parent,
                                     int style,
                                     CompareConfiguration configuration) {
      super(parent, style, configuration);
    }

    protected void configureTextViewer(TextViewer textViewer) {
      if(textViewer instanceof SourceViewer) {
        ((SourceViewer) textViewer).configure(new CompareResultViewerConfiguration());
      }
    }
  }

  public static class CompareResultViewerConfiguration extends SourceViewerConfiguration {

    public IPresentationReconciler getPresentationReconciler(ISourceViewer sourceViewer) {
      PresentationReconciler reconciler = new PresentationReconciler();
      SimpleDamagerRepairer  dr = new SimpleDamagerRepairer();
      reconciler.setDamager(dr, IDocument.DEFAULT_CONTENT_TYPE);
      reconciler.setRepairer(dr, IDocument.DEFAULT_CONTENT_TYPE);

      return reconciler;
    }

    public static class SimpleDamagerRepairer implements IPresentationDamager,
      IPresentationRepairer {
      private IDocument fDocument;

      public void setDocument(IDocument document) {
        fDocument = document;
      }

      public IRegion getDamageRegion(ITypedRegion partition, DocumentEvent event, boolean changed) {
        return new Region(0, fDocument.getLength());
      }

      public void createPresentation(TextPresentation presentation, ITypedRegion damage) {
        int           suffix = CompareResultDialog.fgThis.fSuffix;
        int           prefix = CompareResultDialog.fgThis.fPrefix;
        TextAttribute attr = new TextAttribute(Display.getDefault().getSystemColor(SWT.COLOR_RED),
                                               null,
                                               SWT.BOLD);
        presentation.addStyleRange(new StyleRange(prefix,
                                                  fDocument.getLength() - suffix - prefix,
                                                  attr.getForeground(),
                                                  attr.getBackground(),
                                                  attr.getStyle()));
      }
    }
  }

  private static class CompareElement implements ITypedElement, IEncodedStreamContentAccessor {
    private String fContent;

    public CompareElement(String content) {
      fContent = content;
    }

    public String getName() {
      return "<no name>"; //$NON-NLS-1$
    }

    public Image getImage() {
      return null;
    }

    public String getType() {
      return "txt"; //$NON-NLS-1$
    }

    public InputStream getContents() {
      try {
        return new ByteArrayInputStream(fContent.getBytes("UTF-8")); //$NON-NLS-1$
      }
      catch(UnsupportedEncodingException e) {
        return new ByteArrayInputStream(fContent.getBytes());
      }
    }

    public String getCharset() throws CoreException {
      return "UTF-8"; //$NON-NLS-1$
    }
  }
}
