/*
 * Copyright 2003-2013 JetBrains s.r.o.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package jetbrains.mps.ide.editor;

import com.intellij.openapi.editor.DefaultLanguageHighlighterColors;
import com.intellij.openapi.editor.colors.ColorKey;
import com.intellij.openapi.editor.colors.EditorColorsListener;
import com.intellij.openapi.editor.colors.EditorColorsManager;
import com.intellij.openapi.editor.colors.EditorColorsScheme;
import com.intellij.openapi.editor.colors.TextAttributesKey;
import com.intellij.openapi.editor.markup.EffectType;
import com.intellij.openapi.editor.markup.TextAttributes;
import jetbrains.mps.editor.runtime.style.StyleAttributes;
import jetbrains.mps.editor.runtime.style.StyleImpl;
import jetbrains.mps.nodeEditor.MPSColors;
import jetbrains.mps.openapi.editor.style.Style;
import jetbrains.mps.openapi.editor.style.StyleRegistry;
import jetbrains.mps.util.Pair;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.jetbrains.annotations.NotNull;

import java.awt.Color;
import java.util.HashMap;
import java.util.Map;

public class StyleRegistryIdeaImpl extends StyleRegistry implements EditorColorsListener {
  private static final Logger LOG = LogManager.getLogger(StyleRegistryIdeaImpl.class);

  private final static int brightnessTH = 125;
  private final static int colorTH = 500;
  private final static int colorIterationSteps = 5;
  private final static int colorIterationDelta = 50;

  private EditorColorsManager myColorsManager;
  protected final Map<String, String> myIDEAStylesMapping = new HashMap<String, String>();
  protected final Map<Pair<Color, Color>, Color> myColorsMapping = new HashMap<Pair<Color, Color>, Color>();

  public StyleRegistryIdeaImpl(EditorColorsManager colorsManager) {
    myColorsManager = colorsManager;
    ourInstance = this;
    fillIdeaMappings();
    fillColorMappings();
  }

  @Override
  public Color getEditorBackground() {
    return getColorsScheme().getDefaultBackground();
  }

  @Override
  public Color getEditorForeground() {
    return getColorsScheme().getDefaultForeground();
  }

  @Override
  public Color getColor(String key) {
    if (myIDEAStylesMapping.containsKey(key)) {
      key = myIDEAStylesMapping.get(key);
    }

    Color color = getColorsScheme().getColor(ColorKey.find(key));
    if (color == null) {
      color = getColorsScheme().getAttributes(TextAttributesKey.find(key)).getForegroundColor();
    }
    return color;
  }

  @Override
  public Color getSimpleColor(Color color) {
    return getSimpleColor(color, getEditorBackground());
  }

  @Override
  public Color getSimpleColor(Color color, final Color bg) {
    if (!isDarkTheme() || color == null || bg == null)
      return color;

    final Color original = color;
    Pair<Color, Color> colorPair = new Pair<Color, Color>(original, bg);
    if (myColorsMapping.containsKey(colorPair))
      return myColorsMapping.get(colorPair);

    if ((Math.abs(color.getRGB()) - Math.abs(Color.BLACK.getRGB()) / 2) * (Math.abs(bg.getRGB()) - Math.abs(Color.BLACK.getRGB()) / 2) < 0)
      color = new Color(255 - color.getRed(), 255 - color.getGreen(), 255 - color.getBlue());

    int counter = 0;
    while (!isGoodContrastWithBG(color, bg) && counter < colorIterationSteps) {
      int deltaR = Math.abs(bg.getRed() - color.getRed());
      int deltaG = Math.abs(bg.getGreen() - color.getGreen());
      int deltaB = Math.abs(bg.getBlue() - color.getBlue());
      int deltaMin = Math.min((Math.min(deltaR, deltaG)), deltaB);
      if (deltaMin == deltaR) {
        color = new Color((color.getRed() + colorIterationDelta) % 256, color.getGreen(), color.getBlue());
      } else if (deltaMin == deltaG) {
        color = new Color(color.getRed(), (color.getGreen() + colorIterationDelta) % 256, color.getBlue());
      } else if (deltaMin == deltaB) {
        color = new Color(color.getRed(), color.getGreen(), (color.getBlue() + colorIterationDelta) % 256);
      }
      counter++;
    }

    myColorsMapping.put(colorPair, color);
    return color;
  }

  private boolean isGoodContrastWithBG(Color color, final Color bg) {
    int brightnessColor = (299 * color.getRed() + 587 * color.getGreen() + 114 * color.getBlue()) / 1000;
    int brightnessBG = (299 * bg.getRed() + 587 * bg.getGreen() + 114 * bg.getBlue()) / 1000;

    int brightnessDiff = brightnessBG - brightnessColor;
    int colorDiff = Math.abs(color.getRed() - bg.getRed())
        + Math.abs(color.getGreen() - bg.getGreen())
        + Math.abs(color.getBlue() - bg.getBlue());

    return Math.abs(brightnessDiff) >= brightnessTH || colorDiff >= colorTH;
  }

  @Override
  public Style getStyle(String key) {
    if (myIDEAStylesMapping.containsKey(key)) {
      key = myIDEAStylesMapping.get(key);
    }

    Style style = super.getStyle(key);
    if (style == null) {
      // TODO: check if specified key is valid. We should return null for unknown keys...
      style = new StyleImpl();

      TextAttributes textAttributes = getColorsScheme().getAttributes(TextAttributesKey.find(key));
      if (textAttributes == null)
        textAttributes = new TextAttributes();
      style.set(StyleAttributes.TEXT_COLOR, textAttributes.getForegroundColor());
      style.set(StyleAttributes.TEXT_BACKGROUND_COLOR, textAttributes.getBackgroundColor());
      style.set(StyleAttributes.FONT_STYLE, textAttributes.getFontType());
      if (textAttributes.getEffectColor() != null) {
        style.set(StyleAttributes.UNDERLINED, textAttributes.getEffectType().equals(EffectType.LINE_UNDERSCORE));
        style.set(StyleAttributes.STRIKE_OUT, textAttributes.getEffectType().equals(EffectType.STRIKEOUT));
      }

      setStyle(key, style);
    }
    return style;
  }

  @Override
  public boolean isDarkTheme() {
    return getColorsScheme().getName().contains("Darcula");
  }

  private void fillIdeaMappings() {
    try {
      addIdeaMappingsExt("DEFAULT_NULL_TEXT_COLOR", "NOT_USED_ELEMENT_ATTRIBUTES");

      addIdeaMappingsExt("FOLDED_TEXT", "FOLDED_TEXT_ATTRIBUTES");
      addIdeaMappingsExt("URL", "HYPERLINK_ATTRIBUTES");

      addIdeaMappingsExt("LOCAL_VARIABLE", DefaultLanguageHighlighterColors.LOCAL_VARIABLE.toString());
      addIdeaMappingsExt("PARAMETER", DefaultLanguageHighlighterColors.PARAMETER.toString());
      addIdeaMappingsExt("INSTANCE_FIELD", "INSTANCE_FIELD_ATTRIBUTES");
      addIdeaMappingsExt("METHOD_DECLARATION", DefaultLanguageHighlighterColors.FUNCTION_DECLARATION.toString());
      addIdeaMappingsExt("METHOD_CALL", DefaultLanguageHighlighterColors.FUNCTION_CALL.toString());
      addIdeaMappingsExt("STATIC_FIELD", "STATIC_FIELD_ATTRIBUTES");
      addIdeaMappingsExt("STATIC_FINAL_FIELD", "STATIC_FINAL_FIELD_ATTRIBUTES");
      addIdeaMappingsExt("STATIC_METHOD", DefaultLanguageHighlighterColors.STATIC_METHOD.toString());
      addIdeaMappingsExt("DEPRECATED", "DEPRECATED_ATTRIBUTES");

      addIdeaMappingsExt("CLASS_NAME", DefaultLanguageHighlighterColors.CLASS_NAME.toString());

      addIdeaMappingsExt("ANNOTATION", "ANNOTATION_NAME_ATTRIBUTES");
      addIdeaMappingsExt("NOT_USED_ELEMENT", "NOT_USED_ELEMENT_ATTRIBUTES");
      addIdeaMappingsExt("TODO", "TODO_DEFAULT_ATTRIBUTES");

      addIdeaMappingsExt("DOC_TAG", DefaultLanguageHighlighterColors.DOC_COMMENT_TAG.toString());
      addIdeaMappingsExt("DOC_COMMENT", DefaultLanguageHighlighterColors.DOC_COMMENT.toString());
      addIdeaMappingsExt("KEYWORD", DefaultLanguageHighlighterColors.KEYWORD.toString());
      addIdeaMappingsExt("LINE_COMMENT", DefaultLanguageHighlighterColors.LINE_COMMENT.toString());
      addIdeaMappingsExt("BLOCK_COMMENT", DefaultLanguageHighlighterColors.BLOCK_COMMENT.toString());
      addIdeaMappingsExt("NUMBER", DefaultLanguageHighlighterColors.NUMBER.toString());
      addIdeaMappingsExt("STRING", DefaultLanguageHighlighterColors.STRING.toString());
      addIdeaMappingsExt("OPERATION_SIGN", DefaultLanguageHighlighterColors.OPERATION_SIGN.toString());
      addIdeaMappingsExt("PARENTH", DefaultLanguageHighlighterColors.PARENTHESES.toString());
      addIdeaMappingsExt("BRACKETS", DefaultLanguageHighlighterColors.BRACKETS.toString());
      addIdeaMappingsExt("BRACES", DefaultLanguageHighlighterColors.BRACES.toString());
      addIdeaMappingsExt("SEMICOLON", DefaultLanguageHighlighterColors.SEMICOLON.toString());
      addIdeaMappingsExt("DOT", DefaultLanguageHighlighterColors.DOT.toString());

      addIdeaMappingsExt("BREAKPOINT", "BREAKPOINT_ATTRIBUTES");
      addIdeaMappingsExt("EXECUTIONPOINT", "EXECUTIONPOINT_ATTRIBUTES");

      //addIdeaMappingsExt("","");
    } catch (StyleRegistryMappingKeyException e) {
      LOG.error("Exception on registering IDEA style mappings", e);
    }
  }

  private void fillColorMappings() {
    final Color bg = getEditorBackground();
    myColorsMapping.put(new Pair<Color, Color>(MPSColors.LIGHT_BLUE, bg), new Color(104, 151, 186));
    myColorsMapping.put(new Pair<Color, Color>(MPSColors.DARK_BLUE, bg), new Color(204, 120, 50));
    myColorsMapping.put(new Pair<Color, Color>(MPSColors.DARK_GREEN, bg), new Color(98, 151, 85));
    myColorsMapping.put(new Pair<Color, Color>(MPSColors.DARK_MAGENTA, bg), new Color(152, 118, 170));
    myColorsMapping.put(new Pair<Color, Color>(MPSColors.RED, bg), new Color(255, 107, 104));
    myColorsMapping.put(new Pair<Color, Color>(MPSColors.PINK, bg), new Color(90, 100, 126));
    myColorsMapping.put(new Pair<Color, Color>(MPSColors.ORANGE, bg), new Color(255, 198, 109));
    myColorsMapping.put(new Pair<Color, Color>(MPSColors.YELLOW, bg), new Color(0, 99, 0));
    myColorsMapping.put(new Pair<Color, Color>(MPSColors.GREEN, bg), new Color(0, 128, 0));
    myColorsMapping.put(new Pair<Color, Color>(MPSColors.MAGENTA, bg), new Color(174, 138, 190));
    myColorsMapping.put(new Pair<Color, Color>(MPSColors.CYAN, bg), new Color(32, 153, 157));
    myColorsMapping.put(new Pair<Color, Color>(MPSColors.BLUE, bg), new Color(40, 123, 222));
    myColorsMapping.put(new Pair<Color, Color>(MPSColors.LIGHT_GRAY, bg), new Color(96, 96, 96));
    myColorsMapping.put(new Pair<Color, Color>(MPSColors.GRAY, bg), MPSColors.GRAY);
    myColorsMapping.put(new Pair<Color, Color>(MPSColors.DARK_GRAY, bg), MPSColors.LIGHT_GRAY);
    myColorsMapping.put(new Pair<Color, Color>(MPSColors.WHITE, bg), getEditorBackground());
    myColorsMapping.put(new Pair<Color, Color>(MPSColors.BLACK, bg), getEditorForeground());
  }

  private void addIdeaMappingsExt(String mpsKey, String ideaKey) throws StyleRegistryMappingKeyException {
    if (myIDEAStylesMapping.containsKey(mpsKey))
      throw new StyleRegistryMappingKeyException(mpsKey, myIDEAStylesMapping.get(mpsKey), ideaKey);
    myIDEAStylesMapping.put(mpsKey, ideaKey);
  }

  @Override
  public void globalSchemeChange(EditorColorsScheme scheme) {
    clearCache();
  }

  @NotNull
  private EditorColorsScheme getColorsScheme() {
    return myColorsManager.getGlobalScheme();
  }

  private class StyleRegistryMappingKeyException extends Exception {
    public StyleRegistryMappingKeyException(String mpsKey, String oldIdeaKey, String newIdeaKey) {
      super("Can't set value '" + newIdeaKey + "' for key '" + mpsKey
          + "', because it has been already defined with value '" + oldIdeaKey + "'");
    }
  }
}
