/*
 * 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.HashMap;
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 ASN1Scheme.
 * 
 * @version 0.3
 * @since jNPad 0.1
 */
public class ASN1Scheme extends SyntaxScheme {
  static Color                externalColor;
  static Color                boolValueColor;
  static Color                asnNumberColor;
  static Color                stringTypeColor;
  static Color                tagModifierColor;
  static Color                typeInfoColor;
  static Color                popTypeColor;
  static Color                popValueColor;
  static Color                fieldOptionColor;

  Font                        externalFont;
  Font                        boolValueFont;
  Font                        asnNumberFont;
  Font                        stringTypeFont;
  Font                        tagModifierFont;
  Font                        typeInfoFont;
  Font                        popTypeFont;
  Font                        popValueFont;
  Font                        fieldOptionFont;

  static boolean              classify;
  static boolean              known;

  private static final char[] OPERATORS = new char[] { '=', ':' };

  /**
   * The Enum WordType.
   */
  static enum WordType {
    KEYWORD, 
    EXTERNAL, 
    BOOL_VALUE, 
    NUMBER, 
    TYPE_INFO, 
    TAG_MODIFIER, 
    STRING_TYPE, 
    FIELD_OPTION, 
    POP_TYPE, 
    POP_VALUE, 
    TEXT
  }

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

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

  /** UID */
  private static final long    serialVersionUID = -4256830535183354642L;

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

    classify = Config.SYNTAX_CLASSIFY_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 + "asn1.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 "EXTERNAL"
      while ((line = in.readLine()) != null && !line.equals(":EXTERNAL")) //$NON-NLS-1$
        read(line, WordType.KEYWORD);
      // Everything is EXTERNAL until we run into "BOOL_VALUES"
      while ((line = in.readLine()) != null && !line.equals(":BOOL_VALUES")) //$NON-NLS-1$
        read(line, WordType.EXTERNAL);
      // Everything is BOOL_VALUES until we run into "NUMBERS"
      while ((line = in.readLine()) != null && !line.equals(":NUMBERS")) //$NON-NLS-1$
        read(line, WordType.BOOL_VALUE);
      // Everything is NUMBERS until we run into "TYPE_INFO"
      while ((line = in.readLine()) != null && !line.equals(":TYPE_INFO")) //$NON-NLS-1$
        read(line, WordType.NUMBER);
      // Everything is TYPE_INFO until we run into "TAG_MODIFIERS"
      while ((line = in.readLine()) != null && !line.equals(":TAG_MODIFIERS")) //$NON-NLS-1$
        read(line, WordType.TYPE_INFO);
      // Everything is TAG_MODIFIERS until we run into "STRING_TYPES"
      while ((line = in.readLine()) != null && !line.equals(":STRING_TYPES")) //$NON-NLS-1$
        read(line, WordType.TAG_MODIFIER);
      // Everything is STRING_TYPES until we run into "FIELD_OPTIONS"
      while ((line = in.readLine()) != null && !line.equals(":FIELD_OPTIONS")) //$NON-NLS-1$
        read(line, WordType.STRING_TYPE);
      // Everything is FIELD_OPTIONS until we run into "POP_TYPES"
      while ((line = in.readLine()) != null && !line.equals(":POP_TYPES")) //$NON-NLS-1$
        read(line, WordType.FIELD_OPTION);
      if (known) {
        // Everything is POP_TYPES until we run into "POP_VALUES"
        while ((line = in.readLine()) != null && !line.equals(":POP_VALUES")) //$NON-NLS-1$
          read(line, WordType.POP_TYPE);
        // The rest of the file is POP_VALUES
        while ((line = in.readLine()) != null)
          read(line, WordType.POP_VALUE);
      }
    }
    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);
      }
    }
  }
  
  /**
   * 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 aS n1 scheme.
   *
   * @param mini the mini
   */
  public ASN1Scheme(boolean mini) {
    super(mini);
    doUpdateColors();
    doUpdateFonts();
  }

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

    externalColor     = Config.SYNTAX_KEYWORD2_COLOR.getValue();
    boolValueColor    = Config.SYNTAX_KEYWORD3_COLOR.getValue();
    stringTypeColor   = Config.SYNTAX_KEYWORD4_COLOR.getValue();
    asnNumberColor    = Config.SYNTAX_KEYWORD5_COLOR.getValue();
    tagModifierColor  = Config.SYNTAX_KEYWORD6_COLOR.getValue();
    typeInfoColor     = Config.SYNTAX_KEYWORD9_COLOR.getValue();
    fieldOptionColor  = Config.SYNTAX_KEYWORD7_COLOR.getValue();
    popTypeColor      = Config.SYNTAX_KEYWORD8_COLOR.getValue();
    popValueColor     = Config.SYNTAX_KEYWORD8_COLOR.getValue();
  }

  /**
   * Update fonts.
   */
  private void doUpdateFonts() {
    externalFont    = textFont.deriveFont(Config.SYNTAX_KEYWORD2_STYLE.getValue());
    boolValueFont   = textFont.deriveFont(Config.SYNTAX_KEYWORD3_STYLE.getValue());
    stringTypeFont  = textFont.deriveFont(Config.SYNTAX_KEYWORD4_STYLE.getValue());
    asnNumberFont   = textFont.deriveFont(Config.SYNTAX_KEYWORD5_STYLE.getValue());
    tagModifierFont = textFont.deriveFont(Config.SYNTAX_KEYWORD6_STYLE.getValue());
    typeInfoFont    = textFont.deriveFont(Config.SYNTAX_KEYWORD9_STYLE.getValue());
    fieldOptionFont = textFont.deriveFont(Config.SYNTAX_KEYWORD7_STYLE.getValue());
    popTypeFont     = textFont.deriveFont(Config.SYNTAX_KEYWORD8_STYLE.getValue());
    popValueFont    = textFont.deriveFont(Config.SYNTAX_KEYWORD8_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();
    }
  }

  /**
   * Gets the word type.
   * 
   * @param word the word
   * @return the word type
   */
  public WordType getWordType(String word) {
    WordType type = m_specialWords.get(word);
    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 EXTERNAL    : return classify          ? externalColor    : keywordColor;
      case BOOL_VALUE  : return classify          ? boolValueColor   : keywordColor;
      case NUMBER      : return classify          ? asnNumberColor   : keywordColor;
      case STRING_TYPE : return classify          ? stringTypeColor  : keywordColor;
      case TAG_MODIFIER: return classify          ? tagModifierColor : keywordColor;
      case TYPE_INFO   : return classify          ? typeInfoColor    : keywordColor;
      case FIELD_OPTION: return classify          ? fieldOptionColor : keywordColor;
      case POP_TYPE    : return classify && known ? popTypeColor     : textColor;
      case POP_VALUE   : return classify && known ? popValueColor    : textColor;
      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 EXTERNAL    : return classify          ? externalFont    : keywordFont;
      case BOOL_VALUE  : return classify          ? boolValueFont   : keywordFont;
      case NUMBER      : return classify          ? asnNumberFont   : keywordFont;
      case STRING_TYPE : return classify          ? stringTypeFont  : keywordFont;
      case TAG_MODIFIER: return classify          ? tagModifierFont : keywordFont;
      case TYPE_INFO   : return classify          ? typeInfoFont    : keywordFont;
      case FIELD_OPTION: return classify          ? fieldOptionFont : keywordFont;
      case POP_TYPE    : return classify && known ? popTypeFont     : textFont;
      case POP_VALUE   : return classify && known ? popValueFont    : textFont;
      default          : return textFont;
    }
  }

  /**
   * 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.ASN1;
  }

  /**
   * 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 }; //$NON-NLS-1$
  }
  
}
