package net.sourceforge.phpdt.externaltools.actions;

import java.io.IOException;
import java.io.InputStream;
import java.text.MessageFormat;
import java.util.Hashtable;

import net.sourceforge.phpdt.externaltools.util.StringUtil;
import net.sourceforge.phpeclipse.externaltools.ExternalToolsPlugin;
import net.sourceforge.phpeclipse.externaltools.PHPConsole;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.ui.texteditor.MarkerUtilities;

/**
 * Calls the external parser and generates problem markers if necessary
 */
public class ExternalPHPParser {
  private final static String PROBLEM_ID = "net.sourceforge.phpeclipse.problem";

  // strings for external parser call
  private static final String PARSE_ERROR_STRING = "Parse error"; //$NON-NLS-1$

  private static final String PARSE_WARNING_STRING = "Warning"; //$NON-NLS-1$

  public static final int ERROR = 2;

  public static final int WARNING = 1;

  public static final int INFO = 0;

  public static final int TASK = 3;

  // TODO design error? Analyze why fileToParse must be static ???
  final protected IFile fFileToParse;

  public ExternalPHPParser(IFile file) {
    fFileToParse = file;
  }

  /**
   * Call the php parse command ( php -l -f <filename> ) and create markers according to the external parser output.
   * 
   * @param file
   *          the file that will be parsed
   */
  public void phpExternalParse() {
    //IFile file = (IFile) resource;
    //  final IPath path = file.getFullPath();
    final IPreferenceStore store = ExternalToolsPlugin.getDefault().getPreferenceStore();
    final String filename = fFileToParse.getLocation().toString();

    final String[] arguments = { filename };
    final MessageFormat form = new MessageFormat(store.getString(ExternalToolsPlugin.EXTERNAL_PARSER_PREF));
    final String command = form.format(arguments);

    final String parserResult = getParserOutput(command, "External parser: ");

    try {
      // parse the buffer to find the errors and warnings
      createMarkers(parserResult, fFileToParse);
    } catch (CoreException e) {
    }
  }

  /**
   * Create markers according to the external parser output.
   * 
   * @param output
   *          the external parser output
   * @param file
   *          the file that was parsed.
   */
  protected void createMarkers(final String output, final IFile file) throws CoreException {
    // delete all markers
    file.deleteMarkers(PROBLEM_ID, false, 0);

    int indx = 0;
    int brIndx;
    boolean flag = true;
    while ((brIndx = output.indexOf("<br />", indx)) != -1) {
      // newer php error output (tested with 4.2.3)
      scanLine(output, file, indx, brIndx);
      indx = brIndx + 6;
      flag = false;
    }
    if (flag) {
      while ((brIndx = output.indexOf("<br>", indx)) != -1) {
        // older php error output (tested with 4.2.3)
        scanLine(output, file, indx, brIndx);
        indx = brIndx + 4;
      }
    }
  }

  private void scanLine(final String output, final IFile file, final int indx, final int brIndx) throws CoreException {
    String current;
    //  String outLineNumberString; never used
    final StringBuffer lineNumberBuffer = new StringBuffer(10);
    char ch;
    current = output.substring(indx, brIndx);

    if (current.indexOf(PARSE_WARNING_STRING) != -1 || current.indexOf(PARSE_ERROR_STRING) != -1) {
      final int onLine = current.indexOf("on line <b>");
      if (onLine != -1) {
        lineNumberBuffer.delete(0, lineNumberBuffer.length());
        for (int i = onLine; i < current.length(); i++) {
          ch = current.charAt(i);
          if ('0' <= ch && '9' >= ch) {
            lineNumberBuffer.append(ch);
          }
        }

        final int lineNumber = Integer.parseInt(lineNumberBuffer.toString());

        final Hashtable attributes = new Hashtable();

        current = StringUtil.replaceAll(current, "\n", "");
        current = StringUtil.replaceAll(current, "<b>", "");
        current = StringUtil.replaceAll(current, "</b>", "");
        MarkerUtilities.setMessage(attributes, current);
  
        if (current.indexOf(PARSE_ERROR_STRING) != -1)
          attributes.put(IMarker.SEVERITY, new Integer(IMarker.SEVERITY_ERROR));
        else if (current.indexOf(PARSE_WARNING_STRING) != -1)
          attributes.put(IMarker.SEVERITY, new Integer(IMarker.SEVERITY_WARNING));
        else
          attributes.put(IMarker.SEVERITY, new Integer(IMarker.SEVERITY_INFO));
        MarkerUtilities.setLineNumber(attributes, lineNumber);
        MarkerUtilities.createMarker(file, attributes, PROBLEM_ID);
      }
    }
  }

  /**
   * This will set a marker.
   * 
   * @param file
   *          the file that generated the marker
   * @param message
   *          the message
   * @param charStart
   *          the starting character
   * @param charEnd
   *          the end character
   * @param errorLevel
   *          the error level ({@link ExternalPHPParser#ERROR},{@link ExternalPHPParser#INFO},{@link ExternalPHPParser#WARNING}),
   *          {@link ExternalPHPParser#TASK})
   * @throws CoreException
   *           an exception throwed by the MarkerUtilities
   */
  private void setMarker(final IFile file, final String message, final int charStart, final int charEnd, final int errorLevel)
      throws CoreException {
    if (file != null) {
      final Hashtable attributes = new Hashtable();
      MarkerUtilities.setMessage(attributes, message);
      switch (errorLevel) {
      case ERROR:
        attributes.put(IMarker.SEVERITY, new Integer(IMarker.SEVERITY_ERROR));
        break;
      case WARNING:
        attributes.put(IMarker.SEVERITY, new Integer(IMarker.SEVERITY_WARNING));
        break;
      case INFO:
        attributes.put(IMarker.SEVERITY, new Integer(IMarker.SEVERITY_INFO));
        break;
      case TASK:
        attributes.put(IMarker.SEVERITY, new Integer(IMarker.TASK));
        break;
      }
      MarkerUtilities.setCharStart(attributes, charStart);
      MarkerUtilities.setCharEnd(attributes, charEnd);
      MarkerUtilities.createMarker(file, attributes, PROBLEM_ID);
    }
  }

  /**
   * This will set a marker.
   * 
   * @param file
   *          the file that generated the marker
   * @param message
   *          the message
   * @param line
   *          the line number
   * @param errorLevel
   *          the error level ({@link ExternalPHPParser#ERROR},{@link ExternalPHPParser#INFO},{@link ExternalPHPParser#WARNING})
   * @throws CoreException
   *           an exception throwed by the MarkerUtilities
   */
  private void setMarker(final IFile file, final String message, final int line, final int errorLevel, final String location)
      throws CoreException {
    if (file != null) {
      String markerKind = PROBLEM_ID;
      final Hashtable attributes = new Hashtable();
      MarkerUtilities.setMessage(attributes, message);
      switch (errorLevel) {
      case ERROR:
        attributes.put(IMarker.SEVERITY, new Integer(IMarker.SEVERITY_ERROR));
        break;
      case WARNING:
        attributes.put(IMarker.SEVERITY, new Integer(IMarker.SEVERITY_WARNING));
        break;
      case INFO:
        attributes.put(IMarker.SEVERITY, new Integer(IMarker.SEVERITY_INFO));
        break;
      case TASK:
        attributes.put(IMarker.SEVERITY, new Integer(IMarker.SEVERITY_INFO));
        markerKind = IMarker.TASK;
        break;
      }
      attributes.put(IMarker.LOCATION, location);
      MarkerUtilities.setLineNumber(attributes, line);
      MarkerUtilities.createMarker(file, attributes, markerKind);
    }
  }

  /**
   * This will set a marker.
   * 
   * @param message
   *          the message
   * @param charStart
   *          the starting character
   * @param charEnd
   *          the end character
   * @param errorLevel
   *          the error level ({@link ExternalPHPParser#ERROR},{@link ExternalPHPParser#INFO},{@link ExternalPHPParser#WARNING})
   * @throws CoreException
   *           an exception throwed by the MarkerUtilities
   */
  private void setMarker(final String message, final int charStart, final int charEnd, final int errorLevel, final String location)
      throws CoreException {
    if (fFileToParse != null) {
      setMarker(fFileToParse, message, charStart, charEnd, errorLevel, location);
    }
  }

  /**
   * This will set a marker.
   * 
   * @param file
   *          the file that generated the marker
   * @param message
   *          the message
   * @param charStart
   *          the starting character
   * @param charEnd
   *          the end character
   * @param errorLevel
   *          the error level ({@link ExternalPHPParser#ERROR},{@link ExternalPHPParser#INFO},{@link ExternalPHPParser#WARNING})
   * @param location
   *          the location of the error
   * @throws CoreException
   *           an exception throwed by the MarkerUtilities
   */
  private void setMarker(final IFile file, final String message, final int charStart, final int charEnd, final int errorLevel,
      final String location) throws CoreException {
    if (file != null) {
      final Hashtable attributes = new Hashtable();
      MarkerUtilities.setMessage(attributes, message);
      switch (errorLevel) {
      case ERROR:
        attributes.put(IMarker.SEVERITY, new Integer(IMarker.SEVERITY_ERROR));
        break;
      case WARNING:
        attributes.put(IMarker.SEVERITY, new Integer(IMarker.SEVERITY_WARNING));
        break;
      case INFO:
        attributes.put(IMarker.SEVERITY, new Integer(IMarker.SEVERITY_INFO));
        break;
      case TASK:
        attributes.put(IMarker.SEVERITY, new Integer(IMarker.TASK));
        break;
      }
      attributes.put(IMarker.LOCATION, location);
      MarkerUtilities.setCharStart(attributes, charStart);
      MarkerUtilities.setCharEnd(attributes, charEnd);
      MarkerUtilities.createMarker(file, attributes, PROBLEM_ID); //IMarker.PROBLEM);
    }
  }

  private String getParserOutput(String command, String consoleMessage) {
    try {
      PHPConsole console = new PHPConsole();
      try {
        console.println(consoleMessage + command);
      } catch (Throwable th) {

      }

      Runtime runtime = Runtime.getRuntime();

      // runs the command
      Process p = runtime.exec(command);

      // gets the input stream to have the post-compile-time information
      InputStream stream = p.getInputStream();

      // get the string from Stream
      String consoleOutput = PHPConsole.getStringFromStream(stream);

      // prints out the information
      if (console != null) {
        console.print(consoleOutput);
      }
      return consoleOutput;

    } catch (IOException e) {
      MessageDialog.openInformation(null, "IOException: ", e.getMessage());
    }
    return "";
  }
}