/*
 * jNPad v0.3 - jNPad's an Simple Text Editor written in Java
 *
 * Copyright (C) 2014-2017  rgs
 *
 * Require JDK 1.6 (or later)
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * 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
 *
 *
 * Info, Questions, Suggestions & Bugs Report to rgsevero@gmail.com
 */

package jnpad.text.syntax;

import java.awt.Color;
import java.awt.Font;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import jnpad.JNPad;
import jnpad.config.Config;
import jnpad.util.Utilities;

/**
 * The Class JavaScheme.
 * 
 * @version 0.3
 * @since jNPad 0.1
 */
public class JavaScheme extends SyntaxScheme {
  static Color                modifierColor;
  static Color                exceptionColor;
  static Color                flowControlColor;
  static Color                dataTypeColor;
  static Color                dataValueColor;
  static Color                annotationColor;
  static Color                popClassColor;
  static Color                javadocColor;                                                                                   // [v0.3]
  static Color                javadocTagColor;
  static Color                javadocParamColor;
  static Color                operatorColor_;
  static Color                reservedColor;
  static Color                classColor;                                                                                     // [v0.3]
  static Color                constantColor;                                                                                  // [v0.3]

  Font                        modifierFont;
  Font                        exceptionFont;
  Font                        flowControlFont;
  Font                        dataTypeFont;
  Font                        dataValueFont;
  Font                        annotationFont;
  Font                        popClassFont;
  Font                        javadocFont;                                                                                    // [v0.3]
  Font                        javadocTagFont;
  Font                        javadocParamFont;
  Font                        operatorFont_;
  Font                        reservedFont;
  Font                        classFont;                                                                                      // [v0.3]
  Font                        constantFont;                                                                                   // [v0.3]

  static boolean              classify;
  static boolean              classify2;                                                                                      // [v0.3]
  static boolean              known;

  private static final char[] OPERATORS = new char[] { '-', '+', '*', '/', '<', '>', '!', '~', '%', '^', '&', '|', '=', '.' };

  /**
   * The Enum WordType.
   */
  static enum WordType {
    RESERVED, 
    KEYWORD, 
    MODIFIER, 
    EXCEPTION, 
    FLOW_CONTROL, 
    DATA_TYPE, 
    DATA_VALUE, 
    OPERATOR, 
    POP_CLASS, 
    CLASS,    // [v0.3]
    CONSTANT, // [v0.3]
    TEXT
  }

  static Map<String, WordType> m_specialWords   = new HashMap<String, WordType>();

  static List<String>          l_javadocTags    = new ArrayList<String>();

  /** Logger. */
  private static final Logger  LOGGER           = Logger.getLogger(JavaScheme.class.getName());

  /** UID */
  private static final long    serialVersionUID = 394666138406715548L;

  static {
    if (LOGGER.isLoggable(Level.CONFIG)) 
      LOGGER.config("Java Scheme - init."); //$NON-NLS-1$

    classify  = Config.SYNTAX_CLASSIFY_ENABLED.getValue();
    classify2 = Config.SYNTAX_CLASSIFY2_ENABLED.getValue();
    known     = Config.SYNTAX_KNOWN_ENABLED.getValue();

    BufferedReader in = null;
    try {
      String dir = JNPad.PROPS_DIR + Utilities.DIR_SEPARATOR + "schemes"; //$NON-NLS-1$
      String file = dir + Utilities.DIR_SEPARATOR + "java.words"; //$NON-NLS-1$
      in = new BufferedReader(new InputStreamReader(new FileInputStream(file), "UTF-8")); //$NON-NLS-1$
      String line;
      // Ignore anything before "KEYWORDS"
      while ((line = in.readLine()) != null && !line.equals(":KEYWORDS")); //$NON-NLS-1$
      // Everything is KEYWORDS until we run into "MODIFIERS"
      while ((line = in.readLine()) != null && !line.equals(":MODIFIERS")) //$NON-NLS-1$
        read(line, WordType.KEYWORD);
      // Everything is MODIFIERS until we run into "EXCEPTIONS"
      while ((line = in.readLine()) != null && !line.equals(":EXCEPTIONS")) //$NON-NLS-1$
        read(line, WordType.MODIFIER);
      // Everything is EXCEPTIONS until we run into "FLOW_CONTROLS"
      while ((line = in.readLine()) != null && !line.equals(":FLOW_CONTROLS")) //$NON-NLS-1$
        read(line, WordType.EXCEPTION);
      // Everything is FLOW_CONTROLS until we run into "DATA_TYPES"
      while ((line = in.readLine()) != null && !line.equals(":DATA_TYPES")) //$NON-NLS-1$
        read(line, WordType.FLOW_CONTROL);
      // Everything is DATA_TYPES until we run into "DATA_VALUES"
      while ((line = in.readLine()) != null && !line.equals(":DATA_VALUES")) //$NON-NLS-1$
        read(line, WordType.DATA_TYPE);
      // Everything is DATA_VALUES until we run into "OPERATORS"
      while ((line = in.readLine()) != null && !line.equals(":OPERATORS")) //$NON-NLS-1$
        read(line, WordType.DATA_VALUE);
      // Everything is OPERATORS until we run into "RESERVED"
      while ((line = in.readLine()) != null && !line.equals(":RESERVED")) //$NON-NLS-1$
        read(line, WordType.OPERATOR);
      // Everything is RESERVED until we run into "POP_CLASSES"
      while ((line = in.readLine()) != null && !line.equals(":POP_CLASSES")) //$NON-NLS-1$
        read(line, WordType.RESERVED);
      if (known) {
        // The rest of the file is POP_CLASSES
        while ((line = in.readLine()) != null) 
          read(line, WordType.POP_CLASS);
      }
    }
    catch (IOException ex) {
      LOGGER.log(Level.WARNING, ex.getMessage(), ex);
    }
    finally {
      try {
        if (in != null) {
          in.close();
        }
      }
      catch (IOException ex) {
        LOGGER.log(Level.WARNING, ex.getMessage(), ex);
      }
    }

    l_javadocTags.add("@author");      // Introduced in JDK/SDK 1.0 //$NON-NLS-1$
    l_javadocTags.add("@deprecated");  // Introduced in JDK/SDK 1.0 //$NON-NLS-1$
    l_javadocTags.add("@exception");   // Introduced in JDK/SDK 1.0 //$NON-NLS-1$
    l_javadocTags.add("@param");       // Introduced in JDK/SDK 1.0 //$NON-NLS-1$
    l_javadocTags.add("@return");      // Introduced in JDK/SDK 1.0 //$NON-NLS-1$
    l_javadocTags.add("@see");         // Introduced in JDK/SDK 1.0 //$NON-NLS-1$
    l_javadocTags.add("@serial");      // Introduced in JDK/SDK 1.2 //$NON-NLS-1$
    l_javadocTags.add("@serialData");  // Introduced in JDK/SDK 1.2 //$NON-NLS-1$
    l_javadocTags.add("@serialField"); // Introduced in JDK/SDK 1.2 //$NON-NLS-1$
    l_javadocTags.add("@since");       // Introduced in JDK/SDK 1.1 //$NON-NLS-1$
    l_javadocTags.add("@throws");      // Introduced in JDK/SDK 1.2 //$NON-NLS-1$
    l_javadocTags.add("@version");     // Introduced in JDK/SDK 1.0 //$NON-NLS-1$
  }

  /**
   * Read.
   *
   * @param line the line
   * @param type the type
   */
  private static void read(final String line, final WordType type) {
    if (Utilities.isBlankString(line) || line.startsWith("::")) // blank or comment //$NON-NLS-1$
      return;
    m_specialWords.put(line.trim(), type);
  }
  
  /**
   * Instantiates a new java scheme.
   *
   * @param mini the mini
   */
  public JavaScheme(boolean mini) {
    super(mini);
    doUpdateColors();
    doUpdateFonts();
  }

  /**
   * Update colors.
   */
  private void doUpdateColors() {
    classify          = Config.SYNTAX_CLASSIFY_ENABLED.getValue();
    classify2         = Config.SYNTAX_CLASSIFY2_ENABLED.getValue();

    modifierColor     = Config.SYNTAX_KEYWORD2_COLOR.getValue();
    exceptionColor    = Config.SYNTAX_KEYWORD3_COLOR.getValue();
    flowControlColor  = Config.SYNTAX_KEYWORD4_COLOR.getValue();
    dataTypeColor     = Config.SYNTAX_KEYWORD5_COLOR.getValue();
    dataValueColor    = Config.SYNTAX_KEYWORD6_COLOR.getValue();
    operatorColor_    = Config.SYNTAX_KEYWORD7_COLOR.getValue();
    popClassColor     = Config.SYNTAX_KEYWORD8_COLOR.getValue();
    reservedColor     = Config.SYNTAX_KEYWORD9_COLOR.getValue();
    classColor        = Config.SYNTAX_KEYWORD10_COLOR.getValue();
    constantColor     = Config.SYNTAX_KEYWORD11_COLOR.getValue();
    javadocColor      = Config.SYNTAX_COMMENT1_COLOR.getValue();
    javadocTagColor   = Config.SYNTAX_COMMENT2_COLOR.getValue();
    annotationColor   = Config.SYNTAX_COMMENT3_COLOR.getValue();
    javadocParamColor = Config.SYNTAX_COMMENT4_COLOR.getValue();
  }

  /**
   * Update fonts.
   */
  private void doUpdateFonts() {
    modifierFont     = textFont.deriveFont(Config.SYNTAX_KEYWORD2_STYLE.getValue());
    exceptionFont    = textFont.deriveFont(Config.SYNTAX_KEYWORD3_STYLE.getValue());
    flowControlFont  = textFont.deriveFont(Config.SYNTAX_KEYWORD4_STYLE.getValue());
    dataTypeFont     = textFont.deriveFont(Config.SYNTAX_KEYWORD5_STYLE.getValue());
    dataValueFont    = textFont.deriveFont(Config.SYNTAX_KEYWORD6_STYLE.getValue());
    operatorFont_    = textFont.deriveFont(Config.SYNTAX_KEYWORD7_STYLE.getValue());
    popClassFont     = textFont.deriveFont(Config.SYNTAX_KEYWORD8_STYLE.getValue());
    reservedFont     = textFont.deriveFont(Config.SYNTAX_KEYWORD9_STYLE.getValue());
    classFont        = textFont.deriveFont(Config.SYNTAX_KEYWORD10_STYLE.getValue());
    constantFont     = textFont.deriveFont(Config.SYNTAX_KEYWORD11_STYLE.getValue());
    javadocFont      = textFont.deriveFont(Config.SYNTAX_COMMENT1_STYLE.getValue());
    javadocTagFont   = textFont.deriveFont(Config.SYNTAX_COMMENT2_STYLE.getValue());
    annotationFont   = textFont.deriveFont(Config.SYNTAX_COMMENT3_STYLE.getValue());
    javadocParamFont = textFont.deriveFont(Config.SYNTAX_COMMENT4_STYLE.getValue());
  }

  /**
   * Sets the text font.
   *
   * @param f the new text font
   * @see jnpad.text.syntax.SyntaxScheme#setTextFont(java.awt.Font)
   */
  @Override
  public void setTextFont(Font f) {
    super.setTextFont(f);
    doUpdateFonts();
  }
  
  /**
   * Configure.
   * 
   * @param cfg the cfg
   * @see jnpad.text.syntax.SyntaxScheme#configure(int)
   */
  @Override
  public void configure(final int cfg) {
    super.configure(cfg);
    if ((cfg & CFG_COLOR) != 0) {
      doUpdateColors();
    }
    if ((cfg & CFG_FONT) != 0) {
      doUpdateFonts();
    }
  }
  
  /**
   * Checks if is tag.
   *
   * @param tag the tag
   * @return true, if is tag
   */
  public boolean isJavadocTag(String tag) {
    return l_javadocTags.contains(tag);
  }

  /**
   * Gets the javadoc tag color.
   *
   * @return the javadoc tag color
   */
  public Color getJavadocTagColor() {
    return javadocTagColor;
  }
  
  /**
   * Gets the javadoc tag font.
   *
   * @return the javadoc tag font
   */
  public Font getJavadocTagFont() {
    return javadocTagFont;
  }

  /**
   * Gets the javadoc color.
   *
   * @return the javadoc color
   * @since 0.3
   */
  public Color getJavadocColor() {
    return javadocColor;
  }

  /**
   * Gets the javadoc font.
   *
   * @return the javadoc font
   * @since 0.3
   */
  public Font getJavadocFont() {
    return javadocFont;
  }
  
  /**
   * Gets the javadoc param color.
   *
   * @return the javadoc param color
   */
  public Color getJavadocParamColor() {
    return javadocParamColor;
  }
  
  /**
   * Gets the javadoc param font.
   *
   * @return the javadoc param font
   */
  public Font getJavadocParamFont() {
    return javadocParamFont;
  }
  
  /**
   * Gets the word type.
   * 
   * @param word the word
   * @return the word type
   */
  public WordType getWordType(String word) {
    WordType type = m_specialWords.get(word);
    
    // --- [v0.3] ---
    if (classify2 && type == null) {
      if (Utilities.isUpperCase2(word)) return WordType.CONSTANT;
      if (Utilities.isTitleCase(word))  return WordType.CLASS;
    }
    // ---  
    
    return type != null ? type : WordType.TEXT;
  }

  /**
   * Gets the word color.
   * 
   * @param type the type
   * @return the word color
   */
  public Color getWordColor(WordType type) {
    if (type == null)    return textColor;
    switch (type) {
      case KEYWORD     : return keywordColor;
      case RESERVED    : return reservedColor;
      case OPERATOR    : return classify          ? operatorColor_   : keywordColor;
      case EXCEPTION   : return classify          ? exceptionColor   : keywordColor;
      case MODIFIER    : return classify          ? modifierColor    : keywordColor;
      case FLOW_CONTROL: return classify          ? flowControlColor : keywordColor;
      case DATA_TYPE   : return classify          ? dataTypeColor    : keywordColor;
      case DATA_VALUE  : return classify          ? dataValueColor   : keywordColor;
      case POP_CLASS   : return classify && known ? popClassColor    : classify2 ? classColor : textColor; // [changed v0.3]
      case CLASS       : return classify2         ? classColor       : textColor; // [v0.3]
      case CONSTANT    : return classify2         ? constantColor    : textColor; // [v0.3]
      default          : return textColor;
    }
  }

  /**
   * Gets the word font.
   * 
   * @param type the type
   * @return the word font
   */
  public Font getWordFont(WordType type) {
    if (type == null)    return textFont;
    switch (type) {
      case KEYWORD     : return keywordFont;
      case RESERVED    : return reservedFont;
      case OPERATOR    : return classify          ? operatorFont_   : keywordFont;
      case EXCEPTION   : return classify          ? exceptionFont   : keywordFont;
      case MODIFIER    : return classify          ? modifierFont    : keywordFont;
      case FLOW_CONTROL: return classify          ? flowControlFont : keywordFont;
      case DATA_TYPE   : return classify          ? dataTypeFont    : keywordFont;
      case DATA_VALUE  : return classify          ? dataValueFont   : keywordFont;
      case POP_CLASS   : return classify && known ? popClassFont    : classify2 ? classFont : textFont; // [changed v0.3]
      case CLASS       : return classify2         ? classFont       : textFont; // [v0.3]
      case CONSTANT    : return classify2         ? constantFont    : textFont; // [v0.3]
      default          : return textFont;
    }
  }

  /**
   * Gets the annotation color.
   * 
   * @return the annotation color
   */
  public Color getAnnotationColor() {
    return annotationColor;
  }

  /**
   * Gets the annotation font.
   *
   * @return the annotation font
   */
  public Font getAnnotationFont() {
    return annotationFont;
  }
  
  /**
   * Gets the operators.
   * 
   * @return the operators
   * @see jnpad.text.syntax.SyntaxScheme#getOperators()
   */
  @Override
  public char[] getOperators() {
    //return OPERATORS;                 // Original
    //return OPERATORS.clone();         // Keep FindBugs happy [v0.1]
    return Utilities.copyOf(OPERATORS); // Keep FindBugs happy [v0.3]
  }

  /**
   * Gets the content type.
   *
   * @return the content type
   * @see jnpad.text.syntax.PlainScheme#getContentType()
   */
  @Override
  public String getContentType() {
    return ContentTypes.JAVA;
  }

  /**
   * Gets the start comment.
   *
   * @return the start comment
   * @see jnpad.text.syntax.PlainScheme#getStartComment()
   */
  @Override
  public String[] getStartComment() {
    return new String[] { "//" }; //$NON-NLS-1$
  }

  /**
   * Gets the end comment.
   *
   * @return the end comment
   * @see jnpad.text.syntax.PlainScheme#getEndComment()
   */
  @Override
  public String[] getEndComment() {
    return new String[] { Utilities.LF_STRING };
  }
  
  /**
   * Gets the start multiline comment.
   *
   * @return the start multiline comment
   * @see jnpad.text.syntax.PlainScheme#getStartMultilineComment()
   */
  @Override
  public String[] getStartMultilineComment() {
    return new String[] { "/*" }; //$NON-NLS-1$
  }

  /**
   * Gets the end multiline comment.
   *
   * @return the end multiline comment
   * @see jnpad.text.syntax.PlainScheme#getEndMultilineComment()
   */
  @Override
  public String[] getEndMultilineComment() {
    return new String[] { "*/" }; //$NON-NLS-1$
  }
  
}
