/*
 * 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 CScheme.
 *
 * @version 0.3
 * @since   jNPad v0.1
 */
public class CScheme extends SyntaxScheme {
  static Color                additionalColor;
  static Color                flowControlColor;
  static Color                dataTypeColor;
  static Color                dataValueColor;
  static Color                constantColor;
  static Color                directiveColor;

  Font                        additionalFont;
  Font                        flowControlFont;
  Font                        dataTypeFont;
  Font                        dataValueFont;
  Font                        constantFont;
  Font                        directiveFont;

  static boolean              classify;

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

  /**
   * The Enum WordType.
   */
  static enum WordType {
    KEYWORD, 
    ADDITIONAL,
    FLOW_CONTROL,
    DATA_TYPE,
    DATA_VALUE,
    CONSTANT,
    DIRECTIVE,
    MODIFIER,  // C++
    EXCEPTION, // C++
    OPERATOR,  // C++
    TEXT
  }

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

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

  /** UID */
  private static final long    serialVersionUID = 7210306544383499474L;
  
  static {
    if (LOGGER.isLoggable(Level.CONFIG)) 
      LOGGER.config("C Scheme - init."); //$NON-NLS-1$

    classify  = Config.SYNTAX_CLASSIFY_ENABLED.getValue();

    BufferedReader in = null;
    try {
      String dir = JNPad.PROPS_DIR + Utilities.DIR_SEPARATOR + "schemes"; //$NON-NLS-1$
      String file = dir + Utilities.DIR_SEPARATOR + "c.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 "ADDITIONALS"
      while ((line = in.readLine()) != null && !line.equals(":ADDITIONALS")) //$NON-NLS-1$
        read(line, WordType.KEYWORD);
      // Everything is ADDITIONALS until we run into "FLOW_CONTROLS"
      while ((line = in.readLine()) != null && !line.equals(":FLOW_CONTROLS")) //$NON-NLS-1$
        read(line, WordType.ADDITIONAL);
      // 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 "CONSTANTS"
      while ((line = in.readLine()) != null && !line.equals(":CONSTANTS")) //$NON-NLS-1$
        read(line, WordType.DATA_VALUE);
      // Everything is CONSTANTS until we run into "DIRECTIVES"
      while ((line = in.readLine()) != null && !line.equals(":DIRECTIVES")) //$NON-NLS-1$
        read(line, WordType.CONSTANT);
      // The rest of the file is DIRECTIVES
      while ((line = in.readLine()) != null)
        read(line, WordType.DIRECTIVE);
    }
    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
   */
  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 c scheme.
   *
   * @param mini the mini
   */
  public CScheme(boolean mini) {
    super(mini);
    doUpdateColors();
    doUpdateFonts();
  }

  /**
   * Update colors.
   */
  private void doUpdateColors() {
    classify          = Config.SYNTAX_CLASSIFY_ENABLED.getValue();
    
    additionalColor   = Config.SYNTAX_KEYWORD2_COLOR.getValue();
    flowControlColor  = Config.SYNTAX_KEYWORD4_COLOR.getValue();
    dataTypeColor     = Config.SYNTAX_KEYWORD5_COLOR.getValue();
    dataValueColor    = Config.SYNTAX_KEYWORD6_COLOR.getValue();
    constantColor     = Config.SYNTAX_KEYWORD8_COLOR.getValue();
    directiveColor    = Config.SYNTAX_COMMENT3_COLOR.getValue();
  }

  /**
   * Update fonts.
   */
  private void doUpdateFonts() {
    additionalFont   = textFont.deriveFont(Config.SYNTAX_KEYWORD2_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());
    constantFont     = textFont.deriveFont(Config.SYNTAX_KEYWORD8_STYLE.getValue());
    directiveFont    = textFont.deriveFont(Config.SYNTAX_COMMENT3_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 DIRECTIVE   : return directiveColor;
      case ADDITIONAL  : return classify ? additionalColor  : keywordColor;
      case FLOW_CONTROL: return classify ? flowControlColor : keywordColor;
      case DATA_TYPE   : return classify ? dataTypeColor    : keywordColor;
      case DATA_VALUE  : return classify ? dataValueColor   : keywordColor;
      case CONSTANT    : return classify ? constantColor    : 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 DIRECTIVE   : return directiveFont;
      case ADDITIONAL  : return classify ? additionalFont  : keywordFont;
      case FLOW_CONTROL: return classify ? flowControlFont : keywordFont;
      case DATA_TYPE   : return classify ? dataTypeFont    : keywordFont;
      case DATA_VALUE  : return classify ? dataValueFont   : keywordFont;
      case CONSTANT    : return classify ? constantFont    : 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.C;
  }

  /**
   * 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$
  }
  
}
