/*******************************************************************************
 * 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
 *     Sebastian Davids: sdavids@gmx.de bug 37333, 26653
 *     Johan Walles: walles@mailblocks.com bug 68737
 *******************************************************************************/
package org.testng.eclipse.ui;


import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.ToolBarManager;
import org.eclipse.jface.util.IOpenEventListener;
import org.eclipse.jface.util.OpenStrategy;
import org.eclipse.swt.SWT;
import org.eclipse.swt.dnd.Clipboard;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.swt.widgets.ToolBar;
import org.testng.eclipse.TestNGPlugin;
import org.testng.eclipse.util.PreferenceStoreUtil;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
import java.util.regex.Pattern;

/**
 * A pane that shows a stack trace of a failed test.
 */
class FailureTrace implements IMenuListener {
  private static final String  FRAME_PREFIX = "at "; //$NON-NLS-1$
  
  private final Image m_stackIcon = TestNGPlugin.getImageDescriptor(
      "obj16/stkfrm_obj.gif").createImage(); //$NON-NLS-1$
  private final Image m_exceptionIcon = TestNGPlugin.getImageDescriptor(
      "obj16/exc_catch.gif").createImage(); //$NON-NLS-1$

  private final Clipboard fClipboard;
  private Table fTable;
  private TestRunnerViewPart fTestRunner;
  private String fInputTrace;
  private RunInfo fFailure;
  private CompareResultsAction fCompareAction;
  private String fMessage;

  public FailureTrace(Composite parent,
                      TestRunnerViewPart testRunner,
                      ToolBar toolBar) {

    // fill the failure trace viewer toolbar
    ToolBarManager failureToolBarmanager = new ToolBarManager(toolBar);
    fCompareAction = new CompareResultsAction(this);
    fCompareAction.setEnabled(false);
    failureToolBarmanager.add(fCompareAction);
    failureToolBarmanager.update(true);

    fTable = new Table(parent, SWT.SINGLE | SWT.V_SCROLL | SWT.H_SCROLL);
    fTestRunner = testRunner;

    fClipboard= new Clipboard(parent.getDisplay());

    OpenStrategy handler = new OpenStrategy(fTable);
    handler.addOpenListener(new IOpenEventListener() {
        public void handleOpen(SelectionEvent e) {

          if (fTable.getSelectionIndex() == 0) {
            (new CompareResultsAction(FailureTrace.this)).run();
            return;
          }
          if(fTable.getSelection().length != 0) {
            Action a = createOpenEditorAction(getSelectedText());
            if(a != null) {
              a.run();
            }
          }
        }
      });

    initMenu();

    parent.addDisposeListener(new DisposeListener() {
      public void widgetDisposed(DisposeEvent e) {
         disposeIcons();
         if(null != fClipboard && fClipboard.isDisposed()) {
           fClipboard.dispose();
         }
      }
    });
  }

    public String getTrace() {
        return fInputTrace;
    }
    
  private void initMenu() {
    MenuManager menuMgr = new MenuManager();
    menuMgr.setRemoveAllWhenShown(true);
    menuMgr.addMenuListener(this);

    Menu menu = menuMgr.createContextMenu(fTable);
    fTable.setMenu(menu);
  }

  /**
   * @see org.eclipse.jface.action.IMenuListener#menuAboutToShow(org.eclipse.jface.action.IMenuManager)
   */
  public void menuAboutToShow(IMenuManager manager) {
    if(fTable.getSelectionCount() > 0) {
      Action action = createOpenEditorAction(getSelectedText());
      if(null != null) {
        manager.add(action);
      }
      
      manager.add(new TraceCopyAction(FailureTrace.this, fClipboard));
      manager.add(new MessageCopyAction(FailureTrace.this, fClipboard));
    }
  }

  private String getSelectedText() {
    return fTable.getSelection()[0].getText();
  }

  private Action createOpenEditorAction(String traceLine) {
    try {
      String testName = traceLine;
      testName = testName.substring(testName.indexOf(FRAME_PREFIX)); //$NON-NLS-1$
      testName = testName.substring(FRAME_PREFIX.length(), testName.lastIndexOf('(')).trim();
      testName = testName.substring(0, testName.lastIndexOf('.'));

      int innerSeparatorIndex = testName.indexOf('$');
      if(innerSeparatorIndex != -1) {
        testName = testName.substring(0, innerSeparatorIndex);
      }

      String lineNumber = traceLine;
      lineNumber = lineNumber.substring(lineNumber.indexOf(':') + 1, lineNumber.lastIndexOf(')'));

      int    line = Integer.valueOf(lineNumber).intValue();

      //fix for bug 37333
      String cuName = traceLine.substring(traceLine.lastIndexOf('(') + 1,
                                          traceLine.lastIndexOf(':'));

      return new OpenEditorAtLineAction(fTestRunner, cuName, testName, line);
    }
    catch(NumberFormatException e) {
      ;
    }
    catch(IndexOutOfBoundsException e) {
      ;
    }

    return null;
  }

  private void disposeIcons() {
    m_exceptionIcon.dispose();
    m_stackIcon.dispose();
  }

  /**
   * Returns the composite used to present the trace
   */
  Composite getComposite() {
    return fTable;
  }

  /**
   * Shows a TestFailure
   * @param failure the failed test
   */
  public void showFailure(RunInfo failure) {
    if (null == failure || failure.getStackTrace() == null) {
      fCompareAction.setEnabled(false);
      clear();
      return;
    }
    
    fFailure = failure;
    fCompareAction.setEnabled(true);
    String trace = failure.getStackTrace();

    if(fInputTrace == trace) {
      return;
    }
    fInputTrace = trace;
    updateTable(trace);
  }


  private void updateTable(String trace) {
    if((trace == null) || trace.trim().equals("")) { //$NON-NLS-1$
      clear();

      return;
    }
    trace = trace.trim();
    fTable.setRedraw(false);
    fTable.removeAll();
    fillTable(trace);
    fTable.setRedraw(true);
  }

  private void fillTable(String trace) {
    StringReader   stringReader = new StringReader(trace);
    BufferedReader bufferedReader = new BufferedReader(stringReader);
    String         line;
    

    try {    	

      // first line contains the thrown exception
      line = bufferedReader.readLine();
      if(line == null) {
        return;
      }
      boolean stackFound = false;

      TableItem tableItem = new TableItem(fTable, SWT.NONE);
      String    itemLabel = line.replace('\t', ' ');
      tableItem.setText(itemLabel);
      tableItem.setImage(m_exceptionIcon);

      // the stack frames of the trace
      while((line = bufferedReader.readLine()) != null) {
        if (isExcluded(line)) continue;

        itemLabel = line.replace('\t', ' ');
        tableItem = new TableItem(fTable, SWT.NONE);

        // heuristic for detecting a stack frame - works for JDK
        if((itemLabel.indexOf(" at ") >= 0)) { //$NON-NLS-1$
          tableItem.setImage(m_stackIcon);
          if (!stackFound) {
        	  String messagebase = trace.replace('\t', ' ');
        	  fMessage = messagebase.substring(0, messagebase.indexOf(" at "));
        	  stackFound = true;
          }
        }
        tableItem.setText(itemLabel);
      }
      if (!stackFound) fMessage = trace;
    }
    catch(IOException e) {
      TableItem tableItem = new TableItem(fTable, SWT.NONE);
      tableItem.setText(trace);
    }
  }

  /**
   * @return true if this line of the stack trace should not be shown.
   */
  private boolean isExcluded(String line) {
    PreferenceStoreUtil storage =
      new PreferenceStoreUtil(TestNGPlugin.getDefault().getPreferenceStore());

    String projectName = fTestRunner.getLaunchedProject().getProject().getName();
    String excludedStackTraces = storage.getExcludedStackTraces(projectName);
    if (excludedStackTraces.trim().length() > 0) {
      String[] excluded = excludedStackTraces.split(" ");
      for (String e : excluded) {
        if (Pattern.matches(".*" + e + ".*", line)) return true;
      }
    }
    return false;
  }

  /**
   * Clears the non-stack trace info
   */
  public void clear() {
    fTable.removeAll();
    fInputTrace = null;
  }

  public RunInfo getFailedTest() {
    return fFailure;
  }

  public Shell getShell() {
    return fTable.getShell();
  }
  
  public String getMessage() {
	return fMessage;
  }
}
