/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */

/*
Part of the Processing project - http://processing.org
Copyright (c) 2012-15 The Processing Foundation

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2
as published by the Free Software Foundation.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software Foundation, Inc.
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

package processing.mode.java;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.eclipse.jdt.core.compiler.IProblem;
import org.eclipse.jdt.internal.compiler.problem.DefaultProblem;

import processing.app.Language;
import processing.app.Messages;
import processing.core.PApplet;
import processing.data.StringList;


public class CompileErrorMessageSimplifier {
  /**
   * Mapping between ProblemID constant and the constant name. Holds about 650
   * of them. Also, this is just temporary, will be used to find the common
   * error types, cos you know, identifying String names is easier than
   * identifying 8 digit int constants!
   * TODO: this is temporary
   */
  private static TreeMap<Integer, String> constantsMap;

  private static final boolean DEBUG = false;

  private static final Pattern tokenRegExp = Pattern.compile("\\b token\\b");

  private static void prepareConstantsList() {
    constantsMap = new TreeMap<>();
    Class<DefaultProblem> probClass = DefaultProblem.class;
    Field[] f = probClass.getFields();
    for (Field field : f) {
      if (Modifier.isStatic(field.getModifiers()))
        try {
          if (DEBUG) {
            Messages.log(field.getName() + " :" + field.get(null));
          }
          Object val = field.get(null);
          if (val instanceof Integer) {
            constantsMap.put((Integer) (val), field.getName());
          }
        } catch (Exception e) {
          e.printStackTrace();
          break;
        }
    }
    if (DEBUG) {
      Messages.log("Total items: " + constantsMap.size());
    }
  }


  public static String getIDName(int id) {
    if (constantsMap == null) {
      prepareConstantsList();
    }
    return constantsMap.get(id);
  }


  /**
   * Tones down the jargon in the ecj reported errors.
   */
  public static String getSimplifiedErrorMessage(IProblem iprob, String badCode) {
    if (iprob == null) return null;

    String[] args = iprob.getArguments();

    if (DEBUG) {
      Messages.log("Simplifying message: " + iprob.getMessage() +
                       " ID: " + getIDName(iprob.getID()));
      Messages.log("Arg count: " + args.length);
      for (String arg : args) {
        Messages.log("Arg " + arg);
      }
      Messages.log("Bad code: " + badCode);
    }

    String result = null;

    switch (iprob.getID()) {

      case IProblem.ParsingError:
        if (args.length > 0) {
          result = Language.interpolate("editor.status.error_on", args[0]);
        }
        break;

      case IProblem.ParsingErrorDeleteToken:
        if (args.length > 0) {
          if (args[0].equalsIgnoreCase("Invalid Character")) {
            result = getErrorMessageForCurlyQuote(badCode);
          }
        }
        break;

      case IProblem.ParsingErrorDeleteTokens:
        result = getErrorMessageForCurlyQuote(badCode);
        if (result == null) {
          result = Language.interpolate("editor.status.error_on", args[0]);
        }
        break;

      case IProblem.ParsingErrorInsertToComplete:
        if (args.length > 0) {
          if (args[0].length() == 1) {
            result = getErrorMessageForBracket(args[0].charAt(0));

          } else {
            if (args[0].equals("AssignmentOperator Expression")) {
              result = Language.interpolate("editor.status.missing.add", "=");

            } else if (args[0].equalsIgnoreCase(") Statement")) {
              result = getErrorMessageForBracket(args[0].charAt(0));

            } else {
              result = Language.interpolate("editor.status.error_on", args[0]);
            }
          }
        }
        break;

      case IProblem.ParsingErrorInvalidToken:
        if (args.length > 0) {
          if (args[0].equals("int")) {
            if (args[1].equals("VariableDeclaratorId")) {
              result = Language.text("editor.status.reserved_words");
            } else {
              result = Language.interpolate("editor.status.error_on", args[0]);
            }
          } else if (args[0].equalsIgnoreCase("Invalid Character")) {
            result = getErrorMessageForCurlyQuote(badCode);
          }
          if (result == null) {
            result = Language.interpolate("editor.status.error_on", args[0]);
          }
        }
        break;

      case IProblem.ParsingErrorInsertTokenAfter:
        if (args.length > 0) {
          if (args[1].length() == 1) {
            result = getErrorMessageForBracket(args[1].charAt(0));
          } else {
            // https://github.com/processing/processing/issues/3104
            if (args[1].equalsIgnoreCase("Statement")) {
              result = Language.interpolate("editor.status.error_on", args[0]);
            } else {
              result =
                  Language.interpolate("editor.status.error_on", args[0]) + " " +
                      Language.interpolate("editor.status.missing.add", args[1]);
            }
          }
        }
        break;

      case IProblem.ParsingErrorReplaceTokens:
        result = getErrorMessageForCurlyQuote(badCode);

      case IProblem.UndefinedConstructor:
        if (args.length == 2) {
          String constructorName = args[0];
          // For messages such as "contructor sketch_name.ClassXYZ() is undefined", change
          // constructor name to "ClassXYZ()". See #3434
          if (constructorName.contains(".")) {
            // arg[0] contains sketch name twice: sketch_150705a.sketch_150705a.Thing
            constructorName = constructorName.substring(constructorName.indexOf('.') + 1);
            constructorName = constructorName.substring(constructorName.indexOf('.') + 1);
          }
          String constructorArgs = removePackagePrefixes(args[args.length - 1]);
          result = Language.interpolate("editor.status.undefined_constructor", constructorName, constructorArgs);
        }
        break;

      case IProblem.UndefinedMethod:
        if (args.length > 2) {
          String methodName = args[args.length - 2];
          String methodArgs = removePackagePrefixes(args[args.length - 1]);
          result = Language.interpolate("editor.status.undefined_method", methodName, methodArgs);
        }
        break;

      case IProblem.ParameterMismatch:
        if (args.length > 3) {
          // 2nd arg is method name, 3rd arg is correct param list
          if (args[2].trim().length() == 0) {
            // the case where no params are needed.
            result = Language.interpolate("editor.status.empty_param", args[1]);

          } else {
            result = Language.interpolate("editor.status.wrong_param",
                                          args[1], args[1], removePackagePrefixes(args[2]));
          }
        }
        break;

      case IProblem.UndefinedField:
        if (args.length > 0) {
          result = Language.interpolate("editor.status.undef_global_var", args[0]);
        }
        break;

      case IProblem.UndefinedType:
        if (args.length > 0) {
          result = Language.interpolate("editor.status.undef_class", args[0]);
        }
        break;

      case IProblem.UnresolvedVariable:
        if (args.length > 0) {
          result = Language.interpolate("editor.status.undef_var", args[0]);
        }
        break;

      case IProblem.UndefinedName:
        if (args.length > 0) {
          result = Language.interpolate("editor.status.undef_name", args[0]);
        }
        break;

      case IProblem.UnterminatedString:
        if (badCode.contains("“") || badCode.contains("”")) {
          result = Language.interpolate("editor.status.unterm_string_curly",
              badCode.replaceAll("[^“”]", ""));
        }
        break;

      case IProblem.TypeMismatch:
        if (args.length > 1) {
          result = Language.interpolate("editor.status.type_mismatch", args[0], args[1]);
        }
        break;

      case IProblem.LocalVariableIsNeverUsed:
        if (args.length > 0) {
          result = Language.interpolate("editor.status.unused_variable", args[0]);
        }
        break;

      case IProblem.UninitializedLocalVariable:
        if (args.length > 0) {
          result = Language.interpolate("editor.status.uninitialized_variable", args[0]);
        }
        break;

      case IProblem.AssignmentHasNoEffect:
        if (args.length > 0) {
          result = Language.interpolate("editor.status.no_effect_assignment", args[0]);
        }
        break;

      case IProblem.HidingEnclosingType:
        if (args.length > 0) {
          result = Language.interpolate("editor.status.hiding_enclosing_type", args[0]);
        }
        break;
    }

    if (result == null) {
      String message = iprob.getMessage();
      if (message != null) {
        // Remove all instances of token
        // "Syntax error on token 'blah', delete this token"
        Matcher matcher = tokenRegExp.matcher(message);
        message = matcher.replaceAll("");
        result = message;
      }
    }

    if (DEBUG) {
      Messages.log("Simplified Error Msg: " + result);
    }

    return result;
  }


  /**
   * Converts java.lang.String into String, etc
   */
  static private String removePackagePrefixes(String input) {
    if (!input.contains(".")) {
      return input;
    }
    String[] names = PApplet.split(input, ',');
    StringList result = new StringList();
    for (String name : names) {
      int dot = name.lastIndexOf('.');
      if (dot >= 0) {
        name = name.substring(dot + 1);
      }
      result.append(name);
    }
    return result.join(", ");
  }


  static private String getErrorMessageForBracket(char c) {
    switch (c) {
      case ';': return Language.text("editor.status.missing.semicolon");
      case '[': return Language.text("editor.status.missing.left_sq_bracket");
      case ']': return Language.text("editor.status.missing.right_sq_bracket");
      case '(': return Language.text("editor.status.missing.left_paren");
      case ')': return Language.text("editor.status.missing.right_paren");
      case '{': return Language.text("editor.status.missing.left_curly_bracket");
      case '}': return Language.text("editor.status.missing.right_curly_bracket");
    }
    // This seems to be unreachable and wasn't in PDE.properties.
    // I've added it for 3.0a8, but that seems gross. [fry]
    return Language.interpolate("editor.status.missing.default", c);
  }


  /**
   * @param badCode The code which may contain curly quotes
   * @return Friendly error message if there is a curly quote in badCode,
   *         null otherwise.
   */
  static private String getErrorMessageForCurlyQuote(String badCode) {
    if (badCode.contains("‘") || badCode.contains("’") ||
        badCode.contains("“") || badCode.contains("”")) {
      return Language.interpolate("editor.status.bad_curly_quote",
          badCode.replaceAll("[^‘’“”]", ""));
    } else return null;
  }


//  static private final String q(Object quotable) {
//    return "\"" + quotable + "\"";
//  }


//  static private final String qs(Object quotable) {
//    return " " + q(quotable);
//  }
}
