/** Starts the Error Checker Service thread */
  @Override
  public void run() {
    lastTab = editor.getSketch().getCodeIndex(editor.getSketch().getCurrentCode());
    initializeErrorWindow();
    xqpreproc = new XQPreprocessor();
    // Run check code just once before entering into loop.
    // Makes sure everything is initialized and set.
    checkCode();
    editor.getTextArea().repaint();
    stopThread = false;

    while (!stopThread) {
      try {
        // Take a nap.
        Thread.sleep(sleepTime);
      } catch (Exception e) {
        System.out.println("Oops! [ErrorCheckerThreaded]: " + e);
        // e.printStackTrace();
      }

      if (pauseThread) continue;

      // Check every x seconds
      checkCode();
      if (runCount < 5) {
        runCount++;
      }

      if (runCount == 3) {
        Package p = XQMode.class.getPackage();
        System.out.println(p.getImplementationTitle() + " v" + p.getImplementationVersion());
      }
    }
  }
  /** Repaints the textarea if required */
  public void updateTextAreaPainter() {
    currentTab = editor.getSketch().getCodeIndex(editor.getSketch().getCurrentCode());
    if (currentTab != lastTab) {
      lastTab = currentTab;
      editor.getTextArea().repaint();
      // System.out.println("1 Repaint " + System.currentTimeMillis());
      return;
    }

    if (errorBar.errorPointsChanged()) editor.getTextArea().repaint();
  }
 /** Updates editor status bar, depending on whether the caret is on an error line or not */
 public void updateEditorStatus() {
   // editor.statusNotice("Position: " +
   // editor.getTextArea().getCaretLine());
   boolean notFound = true;
   for (ErrorMarker emarker : errorBar.errorPoints) {
     if (emarker.problem.lineNumber == editor.getTextArea().getCaretLine() + 1) {
       if (emarker.type == ErrorMarker.Warning) editor.statusNotice(emarker.problem.message);
       else editor.statusError(emarker.problem.message);
       return;
     }
   }
   if (notFound) editor.statusEmpty();
 }
  /** Updates the error table in the Error Window. */
  public synchronized void updateErrorTable() {

    try {
      String[][] errorData = new String[problemsList.size()][3];
      for (int i = 0; i < problemsList.size(); i++) {
        errorData[i][0] = problemsList.get(i).message;
        errorData[i][1] = editor.getSketch().getCode(problemsList.get(i).tabIndex).getPrettyName();
        errorData[i][2] = problemsList.get(i).lineNumber + "";
      }

      // initializeErrorWindow();

      if (errorWindow != null) {
        DefaultTableModel tm = new DefaultTableModel(errorData, XQErrorTable.columnNames);
        if (errorWindow.isVisible()) errorWindow.updateTable(tm);
        ((XQEditor) editor).updateTable(tm);

        // A rotating slash animation on the title bar to show
        // that error checker thread is running

        slashAnimationIndex++;
        if (slashAnimationIndex == slashAnimation.length) slashAnimationIndex = 0;
        if (editor != null) {
          String info =
              slashAnimation[slashAnimationIndex]
                  + " T:"
                  + (System.currentTimeMillis() - lastTimeStamp)
                  + "ms";
          errorWindow.setTitle("Problems - " + editor.getSketch().getName() + " " + info);
        }
      }

    } catch (Exception e) {
      System.out.println("Exception at updateErrorTable() " + e);
      e.printStackTrace();
      stopThread();
    }
  }
  /**
   * Scrolls to the error source in code. And selects the line text. Used by XQErrorTable and
   * ErrorBar
   *
   * @param errorIndex - index of error
   */
  public void scrollToErrorLine(int errorIndex) {
    if (editor == null) return;
    if (errorIndex < problemsList.size() && errorIndex >= 0) {
      Problem p = problemsList.get(errorIndex);
      try {
        editor.toFront();
        editor.getSketch().setCurrentCode(p.tabIndex);
        editor.getTextArea().scrollTo(p.lineNumber - 1, 0);
        editor.setSelection(
            editor.getTextArea().getLineStartNonWhiteSpaceOffset(p.lineNumber - 1)
                + editor.getTextArea().getLineText(p.lineNumber - 1).trim().length(),
            editor.getTextArea().getLineStartNonWhiteSpaceOffset(p.lineNumber - 1));
        editor.repaint();
      } catch (Exception e) {
        System.err.println(e + " : Error while selecting text in scrollToErrorLine()");
        // e.printStackTrace();
      }
      // System.out.println("---");

    }
  }
  /**
   * Processes import statements to obtain classpaths of contributed libraries. This would be needed
   * for compilation check. Also, adds stuff(jar files, class files, candy) from the code folder.
   * And it looks messed up.
   */
  private void prepareCompilerClasspath() {
    if (!loadCompClass) return;
    // System.out.println("1..");
    classpathJars = new ArrayList<URL>();
    String entry = "";
    boolean codeFolderChecked = false;
    for (ImportStatement impstat : programImports) {
      String item = impstat.importName;
      int dot = item.lastIndexOf('.');
      entry = (dot == -1) ? item : item.substring(0, dot);

      entry = entry.substring(6).trim();
      // System.out.println("Entry--" + entry);
      if (ignorableImport(entry)) {
        // System.out.println("Ignoring: " + entry);
        continue;
      }
      Library library = null;

      // Try to get the library classpath and add it to the list
      try {
        library = editor.getMode().getLibrary(entry);
        // System.out.println("lib->" + library.getClassPath() + "<-");
        String libraryPath[] =
            PApplet.split(library.getClassPath().substring(1).trim(), File.pathSeparatorChar);
        for (int i = 0; i < libraryPath.length; i++) {
          // System.out.println(entry + " ::"
          // + new File(libraryPath[i]).toURI().toURL());
          classpathJars.add(new File(libraryPath[i]).toURI().toURL());
        }
        // System.out.println("-- ");
        // classpath[count] = (new File(library.getClassPath()
        // .substring(1))).toURI().toURL();
        // System.out.println("  found ");
        // System.out.println(library.getClassPath().substring(1));
      } catch (Exception e) {
        if (library == null && !codeFolderChecked) {
          // System.out.println(1);
          // Look around in the code folder for jar files
          if (editor.getSketch().hasCodeFolder()) {
            File codeFolder = editor.getSketch().getCodeFolder();

            // get a list of .jar files in the "code" folder
            // (class files in subfolders should also be picked up)
            String codeFolderClassPath = Base.contentsToClassPath(codeFolder);
            codeFolderChecked = true;
            if (codeFolderClassPath.equalsIgnoreCase("")) {
              System.err.println(
                  "XQMODE: Yikes! Can't find \""
                      + entry
                      + "\" library! Line: "
                      + impstat.lineNumber
                      + " in tab: "
                      + editor.getSketch().getCode(impstat.tab).getPrettyName());
              System.out.println(
                  "Please make sure that the library is present in <sketchbook "
                      + "folder>/libraries folder or in the code folder of your sketch");
            }
            String codeFolderPath[] =
                PApplet.split(codeFolderClassPath.substring(1).trim(), File.pathSeparatorChar);
            try {
              for (int i = 0; i < codeFolderPath.length; i++) {
                classpathJars.add(new File(codeFolderPath[i]).toURI().toURL());
              }

            } catch (Exception e2) {
              System.out.println("Yikes! codefolder, prepareImports(): " + e2);
            }
          } else {
            System.err.println(
                "XQMODE: Yikes! Can't find \""
                    + entry
                    + "\" library! Line: "
                    + impstat.lineNumber
                    + " in tab: "
                    + editor.getSketch().getCode(impstat.tab).getPrettyName());
            System.out.println(
                "Please make sure that the library is present in <sketchbook "
                    + "folder>/libraries folder or in the code folder of your sketch");
          }

        } else {
          System.err.println("Yikes! There was some problem in prepareImports(): " + e);
          System.err.println("I was processing: " + entry);

          // e.printStackTrace();
        }
      }
    }
  }
  /**
   * Fetches code from the editor tabs and pre-processes it into parsable pure java source. And
   * there's a difference between parsable and compilable. XQPrerocessor.java makes this code
   * compilable. <br>
   * Handles:
   * <li>Removal of import statements
   * <li>Conversion of int(), char(), etc to (int)(), (char)(), etc.
   * <li>Replacing '#' with 0xff for color representation
   * <li>Converts all 'color' datatypes to int (experimental)
   * <li>Appends class declaration statement after determining the mode the sketch is in - ACTIVE or
   *     STATIC
   *
   * @return String - Pure java representation of PDE code. Note that this code is not yet compile
   *     ready.
   */
  private String preprocessCode() {

    String sourceAlt = "";
    programImports = new ArrayList<ImportStatement>();
    if (editor == null) {
      try {
        sourceAlt = readFile(PATH);
      } catch (IOException e) {
        e.printStackTrace();
      }
      System.out.println(sourceAlt);
      System.out.println("-----------PreProcessed----------");
      return sourceAlt;
    }
    // Super wicked regular expressions! (Used from Processing source)
    final Pattern FUNCTION_DECL =
        Pattern.compile(
            "(^|;)\\s*((public|private|protected|final|static)\\s+)*"
                + "(void|int|float|double|String|char|byte)"
                + "(\\s*\\[\\s*\\])?\\s+[a-zA-Z0-9]+\\s*\\(",
            Pattern.MULTILINE);

    // Handle code input from editor/java file
    try {
      if (editor == null) {
        System.out.println("Reading .java file: " + PATH);
      } else {
        rawCode = new StringBuffer();

        for (SketchCode sc : editor.getSketch().getCode()) {
          if (sc.isExtension("pde")) {
            sc.setPreprocOffset(scPreProcOffset);

            try {

              if (editor.getSketch().getCurrentCode().equals(sc)) {

                // rawCode.append(sc.getDocument().getText(0,
                // sc.getDocument().getLength()));
                rawCode.append(
                    scrapImportStatements(
                        sc.getDocument().getText(0, sc.getDocument().getLength()),
                        editor.getSketch().getCodeIndex(sc)));
              } else {

                // rawCode.append(sc.getProgram());
                rawCode.append(
                    scrapImportStatements(sc.getProgram(), editor.getSketch().getCodeIndex(sc)));
              }
              rawCode.append('\n');
            } catch (Exception e) {
              System.err.println("Exception in preprocessCode() - bigCode " + e.toString());
            }
            rawCode.append('\n');
            scPreProcOffset += sc.getLineCount();
          }
        }

        sourceAlt = rawCode.toString();
        // System.out.println("Obtaining source from editor.");
      }
    } catch (Exception e) {

      System.out.println("Exception in preprocessCode()");
    }

    // Replace comments with whitespaces
    // sourceAlt = scrubComments(sourceAlt);

    // Find all int(*), replace with PApplet.parseInt(*)

    // \bint\s*\(\s*\b , i.e all exclusive "int("

    String dataTypeFunc[] = {"int", "char", "float", "boolean", "byte"};
    for (String dataType : dataTypeFunc) {
      String dataTypeRegexp = "\\b" + dataType + "\\s*\\(";
      Pattern pattern = Pattern.compile(dataTypeRegexp);
      Matcher matcher = pattern.matcher(sourceAlt);

      // while (matcher.find()) {
      // System.out.print("Start index: " + matcher.start());
      // System.out.println(" End index: " + matcher.end() + " ");
      // System.out.println("-->" + matcher.group() + "<--");
      // }
      sourceAlt =
          matcher.replaceAll(
              "PApplet.parse"
                  + Character.toUpperCase(dataType.charAt(0))
                  + dataType.substring(1)
                  + "(");
    }

    // Find all #[web color] and replace with 0xff[webcolor]
    // Should be 6 digits only.
    final String webColorRegexp = "#{1}[A-F|a-f|0-9]{6}\\W";
    Pattern webPattern = Pattern.compile(webColorRegexp);
    Matcher webMatcher = webPattern.matcher(sourceAlt);
    while (webMatcher.find()) {
      // System.out.println("Found at: " + webMatcher.start());
      String found = sourceAlt.substring(webMatcher.start(), webMatcher.end());
      // System.out.println("-> " + found);
      sourceAlt = webMatcher.replaceFirst("0xff" + found.substring(1));
      webMatcher = webPattern.matcher(sourceAlt);
    }

    // TODO: Experimental.
    // Replace all color data types with int
    // Regex, Y U SO powerful?
    final String colorTypeRegex = "color(?![a-zA-Z0-9_])(?=\\[*)(?!(\\s*\\())";
    Pattern colorPattern = Pattern.compile(colorTypeRegex);
    Matcher colorMatcher = colorPattern.matcher(sourceAlt);
    sourceAlt = colorMatcher.replaceAll("int");

    checkForChangedImports();

    className = (editor == null) ? "DefaultClass" : editor.getSketch().getName();

    // Check whether the code is being written in STATIC mode(no function
    // declarations) - append class declaration and void setup() declaration
    Matcher matcher = FUNCTION_DECL.matcher(sourceAlt);
    if (!matcher.find()) {
      sourceAlt =
          "public class "
              + className
              + " extends PApplet {\n"
              + "public void setup() {\n"
              + sourceAlt
              + "\nnoLoop();\n}\n"
              + "\n}\n";
      staticMode = true;
      mainClassOffset = 2;

    } else {
      sourceAlt = "public class " + className + " extends PApplet {\n" + sourceAlt + "\n}";
      staticMode = false;
      mainClassOffset = 1;
    }

    // Handle unicode characters
    sourceAlt = substituteUnicode(sourceAlt);

    // System.out.println("-->\n" + sourceAlt + "\n<--");
    // System.out.println("PDE code processed - "
    // + editor.getSketch().getName());
    sourceCode = sourceAlt;
    return sourceAlt;
  }
 /** Stops the Error Checker Service thread */
 public void stopThread() {
   stopThread = true;
   System.out.println(editor.getSketch().getName() + " - Error Checker stopped.");
 }
  /**
   * Calculates the tab number and line number of the error in that particular tab. Provides mapping
   * between pure java and pde code.
   *
   * @param problem - IProblem
   * @return int[0] - tab number, int[1] - line number
   */
  public int[] calculateTabIndexAndLineNumber(IProblem problem) {
    // String[] lines = {};// = PApplet.split(sourceString, '\n');
    int codeIndex = 0;
    int bigCount = 0;

    int x = problem.getSourceLineNumber() - mainClassOffset;
    if (x < 0) {
      // System.out.println("Negative line number "
      // + problem.getSourceLineNumber() + " , offset "
      // + mainClassOffset);
      x = problem.getSourceLineNumber() - 2; // Another -1 for 0 index
      if (x < programImports.size() && x >= 0) {
        ImportStatement is = programImports.get(x);
        // System.out.println(is.importName + ", " + is.tab + ", "
        // + is.lineNumber);
        return new int[] {is.tab, is.lineNumber};
      } else {

        // Some seriously ugly stray error, just can't find the source
        // line! Simply return first line for first tab.
        return new int[] {0, 1};
      }
    }

    try {
      for (SketchCode sc : editor.getSketch().getCode()) {
        if (sc.isExtension("pde")) {
          sc.setPreprocOffset(bigCount);
          int len = 0;
          if (editor.getSketch().getCurrentCode().equals(sc)) {
            len = Base.countLines(sc.getDocument().getText(0, sc.getDocument().getLength())) + 1;
          } else {
            len = Base.countLines(sc.getProgram()) + 1;
          }

          // System.out.println("x,len, CI: " + x + "," + len + ","
          // + codeIndex);

          if (x >= len) {

            // We're in the last tab and the line count is greater
            // than the no.
            // of lines in the tab,
            if (codeIndex >= editor.getSketch().getCodeCount() - 1) {
              // System.out.println("Exceeds lc " + x + "," + len
              // + problem.toString());
              // x = len
              x = editor.getSketch().getCode(codeIndex).getLineCount();
              // TODO: Obtain line having last non-white space
              // character in the code.
              break;
            } else {
              x -= len;
              codeIndex++;
            }
          } else {

            if (codeIndex >= editor.getSketch().getCodeCount())
              codeIndex = editor.getSketch().getCodeCount() - 1;
            break;
          }
        }
        bigCount += sc.getLineCount();
      }
    } catch (Exception e) {
      System.err.println(
          "Things got messed up in ErrorCheckerService.calculateTabIndexAndLineNumber()");
    }

    return new int[] {codeIndex, x};
  }
  /** The name cannot get any simpler, can it? */
  private void compileCheck() {

    // Currently (Sept, 2012) I'm using Java's reflection api to load the
    // CompilationChecker class(from CompilationChecker.jar) that houses the
    // Eclispe JDT compiler and call its getErrorsAsObj method to obtain
    // errors. This way, I'm able to add the paths of contributed libraries
    // to the classpath of CompilationChecker, dynamically.

    try {

      // NOTE TO SELF: If classpath contains null Strings
      // URLClassLoader gets angry. Drops NPE bombs.

      // If imports have changed, reload classes with new classpath.
      if (loadCompClass) {

        if (classpathJars.size() > 0)
          System.out.println(
              "XQMode: Loading contributed libraries referenced by import statements.");
        File f =
            new File(
                editor.getBase().getSketchbookFolder().getAbsolutePath()
                    + File.separator
                    + "modes"
                    + File.separator
                    + "XQMode"
                    + File.separator
                    + "mode");

        FileFilter fileFilter =
            new FileFilter() {
              public boolean accept(File file) {
                return (file.getName().endsWith(".jar") && !file.getName().startsWith("XQMode"));
              }
            };

        File[] jarFiles = f.listFiles(fileFilter);
        for (File jarFile : jarFiles) {
          classpathJars.add(jarFile.toURI().toURL());
        }

        // for (int i = 0; i < tempPath.length; i++) {
        // classpathJars.add(new URL(tempPath[i]));
        // }

        classpath = new URL[classpathJars.size()]; // + 1 for
        // Compilation
        // Checker class
        for (int i = 0; i < classpathJars.size(); i++) {
          classpath[i] = classpathJars.get(i);
        }

        // System.out.println("-- " + classpath.length);
        URLClassLoader classLoader = new URLClassLoader(classpath);
        // System.out.println("1.");
        checkerClass = Class.forName("CompilationChecker", true, classLoader);
        // System.out.println("2.");
        compilationChecker = checkerClass.newInstance();
        loadCompClass = false;
      }

      if (compilerSettings == null) prepareCompilerSetting();
      Method getErrors =
          checkerClass.getMethod(
              "getErrorsAsObjArr", new Class[] {String.class, String.class, Map.class});
      // Method disp = checkerClass.getMethod("test", (Class<?>[])null);

      Object[][] errorList =
          (Object[][])
              getErrors.invoke(compilationChecker, className, sourceCode, compilerSettings);

      if (errorList == null) return;

      problems = new DefaultProblem[errorList.length];

      for (int i = 0; i < errorList.length; i++) {

        // for (int j = 0; j < errorList[i].length; j++)
        // System.out.print(errorList[i][j] + ", ");

        problems[i] =
            new DefaultProblem(
                (char[]) errorList[i][0],
                (String) errorList[i][1],
                ((Integer) errorList[i][2]).intValue(),
                (String[]) errorList[i][3],
                ((Integer) errorList[i][4]).intValue(),
                ((Integer) errorList[i][5]).intValue(),
                ((Integer) errorList[i][6]).intValue(),
                ((Integer) errorList[i][7]).intValue(),
                0);

        // System.out
        // .println("ECS: " + problems[i].getMessage() + ","
        // + problems[i].isError() + ","
        // + problems[i].isWarning());

        IProblem problem = problems[i];

        int a[] = calculateTabIndexAndLineNumber(problem);
        Problem p = new Problem(problem, a[0], a[1]);
        if ((Boolean) errorList[i][8]) p.setType(Problem.ERROR);
        if ((Boolean) errorList[i][9]) p.setType(Problem.WARNING);

        if (p.isWarning() && !warningsEnabled) continue;
        problemsList.add(p);
      }

    } catch (ClassNotFoundException e) {
      System.err.println(
          "Compiltation Checker files couldn't be found! " + e + " compileCheck() problem.");
      stopThread();
    } catch (MalformedURLException e) {
      System.err.println(
          "Compiltation Checker files couldn't be found! " + e + " compileCheck() problem.");
      stopThread();
    } catch (Exception e) {
      System.err.println("compileCheck() problem." + e);
      e.printStackTrace();
      stopThread();
    } catch (NoClassDefFoundError e) {
      System.err.println(e + " compileCheck() problem. Somebody tried to mess with XQMode files.");
      stopThread();
    }
    // System.out.println("Compilecheck, Done.");
  }