/*
 * Copyright 2000-2015 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.
 */

/*
 * @author max
 */
package com.intellij.ui;

import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.awt.*;
import java.lang.annotation.Annotation;

/**
 * @author max
 * @author Konstantin Bulenkov
 */
public class ColorUtil {
  private ColorUtil() {
  }

  public static Color softer(@NotNull Color color) {
    if (color.getBlue() > 220 && color.getRed() > 220 && color.getGreen() > 220) return color;
    final float[] hsb = Color.RGBtoHSB(color.getRed(), color.getGreen(), color.getBlue(), null);
    return Color.getHSBColor(hsb[0], 0.6f * hsb[1], hsb[2]);
  }

  public static Color darker(@NotNull Color color, int tones) {
    final float[] hsb = Color.RGBtoHSB(color.getRed(), color.getGreen(), color.getBlue(), null);
    float brightness = hsb[2];
    for (int i = 0; i < tones; i++) {
      brightness = Math.max(0, brightness / 1.1F);
      if (brightness == 0) break;
    }
    return Color.getHSBColor(hsb[0], hsb[1], brightness);
  }

  public static Color brighter(@NotNull Color color, int tones) {
    final float[] hsb = Color.RGBtoHSB(color.getRed(), color.getGreen(), color.getBlue(), null);
    float brightness = hsb[2];
    for (int i = 0; i < tones; i++) {
      brightness = Math.min(1, brightness * 1.1F);
      if (brightness == 1) break;
    }
    return Color.getHSBColor(hsb[0], hsb[1], brightness);
  }

  public static Color dimmer(@NotNull Color color) {
    float[] rgb = color.getRGBColorComponents(null);

    float alpha = 0.80f;
    float rem = 1 - alpha;
    return new Color(rgb[0] * alpha + rem, rgb[1] * alpha + rem, rgb[2] * alpha + rem);
  }

  private static int shift(int colorComponent, double d) {
    final int n = (int)(colorComponent * d);
    return n > 255 ? 255 : n < 0 ? 0 : n;
  }

  public static Color shift(Color c, double d) {
    return new Color(shift(c.getRed(), d), shift(c.getGreen(), d), shift(c.getBlue(), d), c.getAlpha());
  }

  public static Color withAlpha(Color c, double a) {
    return toAlpha(c, (int)(255 * a));
  }

  public static Color toAlpha(Color color, int a) {
    Color c = color != null ? color : Color.black;
    return new Color(c.getRed(), c.getGreen(), c.getBlue(), a);
  }

  public static Color withAlphaAdjustingDarkness(Color c, double d) {
    return shift(withAlpha(c, d), d);
  }

  public static String toHex(@NotNull final Color c) {
    final String R = Integer.toHexString(c.getRed());
    final String G = Integer.toHexString(c.getGreen());
    final String B = Integer.toHexString(c.getBlue());
    return (R.length() < 2 ? "0" : "") + R + (G.length() < 2 ? "0" : "") + G + (B.length() < 2 ? "0" : "") + B;
  }

  /**
   * Return Color object from string. The following formats are allowed:
   * <code>#abc123</code>,
   * <code>ABC123</code>,
   * <code>ab5</code>,
   * <code>#FFF</code>.
   *
   * @param str hex string
   * @return Color object
   */
  public static Color fromHex(String str) {
    str = StringUtil.trimStart(str, "#");
    if (str.length() == 3) {
      return new Color(
        17 * Integer.valueOf(String.valueOf(str.charAt(0)), 16).intValue(),
        17 * Integer.valueOf(String.valueOf(str.charAt(1)), 16).intValue(),
        17 * Integer.valueOf(String.valueOf(str.charAt(2)), 16).intValue());
    }
    else if (str.length() == 6) {
      return Color.decode("0x" + str);
    }
    else {
      throw new IllegalArgumentException("Should be String of 3 or 6 chars length.");
    }
  }

  @Nullable
  public static Color fromHex(String str, @Nullable Color defaultValue) {
    try {
      return fromHex(str);
    }
    catch (Exception e) {
      return defaultValue;
    }
  }

  @Nullable
  public static Color getColor(@NotNull Class<?> cls) {
    final Annotation annotation = cls.getAnnotation(Colored.class);
    if (annotation instanceof Colored) {
      final Colored colored = (Colored)annotation;
      return fromHex(UIUtil.isUnderDarcula() ? colored.darkVariant() : colored.color(), null);
    }
    return null;
  }

  /**
   * Checks whether color is dark or not based on perceptional luminosity
   * http://stackoverflow.com/questions/596216/formula-to-determine-brightness-of-rgb-color
   *
   * @param c color to check
   * @return dark or not
   */
  public static boolean isDark(@NotNull final Color c) {
    // based on perceptional luminosity, see
    return (1 - (0.299 * c.getRed() + 0.587 * c.getGreen() + 0.114 * c.getBlue()) / 255) >= 0.5;
  }

  @NotNull
  public static Color mix(@NotNull Color c1, @NotNull Color c2, double balance) {
    balance = Math.min(1, Math.max(0, balance));
    return new Color((int)((1 - balance) * c1.getRed() + c2.getRed() * balance + .5),
                     (int)((1 - balance) * c1.getGreen() + c2.getGreen() * balance + .5),
                     (int)((1 - balance) * c1.getBlue() + c2.getBlue() * balance + .5),
                     (int)((1 - balance) * c1.getAlpha() + c2.getAlpha() * balance + .5));
  }
}
