/*
 * Copyright 2000-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 com.intellij.openapi.editor.markup;

import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.InvalidDataException;
import org.intellij.lang.annotations.JdkConstants;
import org.jdom.Element;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.awt.*;

/**
 * Defines the visual representation (colors and effects) of text.
 */
public class TextAttributes implements Cloneable {
  private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.editor.markup.TextAttributes");

  public static final TextAttributes ERASE_MARKER = new TextAttributes();

  private boolean myEnforceEmpty;

  @SuppressWarnings("NullableProblems")
  @NotNull
  private AttributesFlyweight myAttrs;

  /**
   * Merges (layers) the two given text attributes.
   *
   * @param under Text attributes to merge "under".
   * @param above Text attributes to merge "above", overriding settings from "under".
   * @return Merged attributes instance.
   */
  @Contract("!null, !null -> !null")
  public static TextAttributes merge(TextAttributes under, TextAttributes above) {
    if (under == null) return above;
    if (above == null) return under;

    TextAttributes attrs = under.clone();
    if (above.getBackgroundColor() != null){
      attrs.setBackgroundColor(above.getBackgroundColor());
    }
    if (above.getForegroundColor() != null){
      attrs.setForegroundColor(above.getForegroundColor());
    }
    attrs.setFontType(above.getFontType() | under.getFontType());

    if (above.getEffectColor() != null){
      attrs.setEffectColor(above.getEffectColor());
      attrs.setEffectType(above.getEffectType());
    }
    return attrs;
  }

  public TextAttributes() {
    this(null, null, null, EffectType.BOXED, Font.PLAIN);
  }

  private TextAttributes(@NotNull AttributesFlyweight attributesFlyweight, boolean enforced) {
    myAttrs = attributesFlyweight;
    myEnforceEmpty = enforced;
  }

  public TextAttributes(@NotNull Element element) {
    readExternal(element);
  }

  public TextAttributes(@Nullable Color foregroundColor, @Nullable Color backgroundColor, @Nullable Color effectColor, EffectType effectType, @JdkConstants.FontStyle int fontType) {
    setAttributes(foregroundColor, backgroundColor, effectColor, null, effectType, fontType);
  }

  public void setAttributes(Color foregroundColor,
                            Color backgroundColor,
                            Color effectColor,
                            Color errorStripeColor,
                            EffectType effectType,
                            @JdkConstants.FontStyle int fontType) {
    myAttrs = AttributesFlyweight.create(foregroundColor, backgroundColor, fontType, effectColor, effectType, errorStripeColor);
  }

  public boolean isEmpty(){
    return getForegroundColor() == null && getBackgroundColor() == null && getEffectColor() == null && getFontType() == Font.PLAIN;
  }

  public boolean isFallbackEnabled() {
    return isEmpty() && !myEnforceEmpty;
  }

  public boolean containsValue() {
    return !isEmpty() || myEnforceEmpty;
  }

  public void reset() {
    setForegroundColor(null);
    setBackgroundColor(null);
    setEffectColor(null);
    setFontType(Font.PLAIN);
  }

  @NotNull
  public AttributesFlyweight getFlyweight() {
    return myAttrs;
  }

  @NotNull
  public static TextAttributes fromFlyweight(@NotNull AttributesFlyweight flyweight) {
    TextAttributes f = new TextAttributes();
    f.myAttrs = flyweight;
    return f;
  }

  public Color getForegroundColor() {
    return myAttrs.getForeground();
  }

  public void setForegroundColor(Color color) {
    myAttrs = myAttrs.withForeground(color);
  }

  public Color getBackgroundColor() {
    return myAttrs.getBackground();
  }

  public void setBackgroundColor(Color color) {
    myAttrs = myAttrs.withBackground(color);
  }

  public Color getEffectColor() {
    return myAttrs.getEffectColor();
  }

  public void setEffectColor(Color color) {
    myAttrs = myAttrs.withEffectColor(color);
  }

  public Color getErrorStripeColor() {
    return myAttrs.getErrorStripeColor();
  }

  public void setErrorStripeColor(Color color) {
    myAttrs = myAttrs.withErrorStripeColor(color);
  }

  public EffectType getEffectType() {
    return myAttrs.getEffectType();
  }

  public void setEffectType(EffectType effectType) {
    myAttrs = myAttrs.withEffectType(effectType);
  }

  @JdkConstants.FontStyle
  public int getFontType() {
    return myAttrs.getFontType();
  }

  public void setFontType(@JdkConstants.FontStyle int type) {
    if (type < 0 || type > 3) {
      LOG.error("Wrong font type: " + type);
      type = 0;
    }
    myAttrs = myAttrs.withFontType(type);
  }

  @Override
  public TextAttributes clone() {
    return new TextAttributes(myAttrs, myEnforceEmpty);
  }

  public boolean equals(Object obj) {
    if(!(obj instanceof TextAttributes)) {
      return false;
    }
    // myAttrs are interned, see com.intellij.openapi.editor.markup.AttributesFlyweight.create()
    return myAttrs == ((TextAttributes)obj).myAttrs;
  }

  public int hashCode() {
    return myAttrs.hashCode();
  }

  public void readExternal(Element element) {
    try {
      myAttrs = AttributesFlyweight.create(element);
    }
    catch (InvalidDataException e) {
      throw new RuntimeException(e);
    }

    if (isEmpty()) {
      myEnforceEmpty = true;
    }
  }

  public void writeExternal(Element element) {
    myAttrs.writeExternal(element);
  }

  @Override
  public String toString() {
    return "[" + getForegroundColor() + "," + getBackgroundColor() + "," + getFontType() + "," + getEffectType() + "," +
           getEffectColor() + "," + getErrorStripeColor() + "]";
  }

  /**
   * Enforces empty attributes instead of treating empty values as undefined.
   *
   * @param enforceEmpty True if empty values should be used as is (fallback is disabled).
   */
  public void setEnforceEmpty(boolean enforceEmpty) {
    myEnforceEmpty = enforceEmpty;
  }

  public boolean isEnforceEmpty() {
    return myEnforceEmpty;
  }
}
