// Licensed to the Software Freedom Conservancy (SFC) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The SFC licenses this file
// to you 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 org.openqa.selenium.support;

import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Color {
  private final int red;
  private final int green;
  private final int blue;
  private double alpha;

  private static final Converter[] CONVERTERS = {
    new RgbConverter(),
    new RgbPctConverter(),
    new RgbaConverter(),
    new RgbaPctConverter(),
    new HexConverter(),
    new Hex3Converter(),
    new HslConverter(),
    new HslaConverter(),
    new NamedColorConverter(),
  };

  /*
   * Guesses what format the input color is in.
   */
  public static Color fromString(String value) {
    for (Converter converter : CONVERTERS) {
      Color color = converter.getColor(value);
      if (color != null) {
        return color;
      }
    }
    throw new IllegalArgumentException(
        String.format("Did not know how to convert %s into color", value));
  }

  public Color(int red, int green, int blue, double alpha) {
    this.red = red;
    this.green = green;
    this.blue = blue;
    this.alpha = alpha;
  }

  public void setOpacity(double alpha) {
    this.alpha = alpha;
  }

  public String asRgb() {
    return String.format("rgb(%d, %d, %d)", red, green, blue);
  }

  public String asRgba() {
    String alphaString;
    if (alpha == 1) {
      alphaString = "1";
    } else if (alpha == 0) {
      alphaString = "0";
    } else {
      alphaString = Double.toString(alpha);
    }
    return String.format("rgba(%d, %d, %d, %s)", red, green, blue, alphaString);
  }

  public String asHex() {
    return String.format("#%02x%02x%02x", red, green, blue);
  }

  /**
   * @return a java.awt.Color class instance
   */
  public java.awt.Color getColor() {
    return new java.awt.Color(red, green, blue, (int) (alpha * 255));
  }

  @Override
  public String toString() {
    return "Color: " + asRgba();
  }

  @Override
  public boolean equals(Object other) {
    if (other == null) {
      return false;
    }

    if (!(other instanceof Color)) {
      return false;
    }

    return asRgba().equals(((Color) other).asRgba());
  }

  @Override
  public int hashCode() {
    int result;
    long temp;
    result = red;
    result = 31 * result + green;
    result = 31 * result + blue;
    temp = alpha != +0.0d ? Double.doubleToLongBits(alpha) : 0L;
    result = 31 * result + Long.hashCode(temp);
    return result;
  }

  private abstract static class Converter {
    public Color getColor(String value) {
      Matcher matcher = getPattern().matcher(value);
      if (matcher.find()) {
        double a = 1.0;
        if (matcher.groupCount() == 4) {
          a = Double.parseDouble(matcher.group(4));
        }
        return createColor(matcher, a);
      }
      return null;
    }

    protected Color createColor(Matcher matcher, double a) {
      return new Color(
          fromMatchGroup(matcher, 1), fromMatchGroup(matcher, 2), fromMatchGroup(matcher, 3), a);
    }

    protected short fromMatchGroup(Matcher matcher, int index) {
      return Short.parseShort(matcher.group(index), 10);
    }

    protected abstract Pattern getPattern();
  }

  private static class RgbConverter extends Converter {
    private static final Pattern RGB_PATTERN =
        Pattern.compile(
            "^\\s*rgb\\(\\s*"
                + "(\\d{1,3})\\s*,\\s*"
                + "(\\d{1,3})\\s*,\\s*"
                + "(\\d{1,3})\\s*\\)\\s*$");

    @Override
    protected Pattern getPattern() {
      return RGB_PATTERN;
    }
  }

  private static class RgbPctConverter extends Converter {
    private static final Pattern RGBPCT_PATTERN =
        Pattern.compile(
            "^\\s*rgb\\(\\s*"
                + "(\\d{1,3}|\\d{1,2}\\.\\d+)%\\s*,\\s*"
                + "(\\d{1,3}|\\d{1,2}\\.\\d+)%\\s*,\\s*"
                + "(\\d{1,3}|\\d{1,2}\\.\\d+)%\\s*\\)\\s*$");

    @Override
    protected Pattern getPattern() {
      return RGBPCT_PATTERN;
    }

    @Override
    protected short fromMatchGroup(Matcher matcher, int index) {
      double n = Double.parseDouble(matcher.group(index)) / 100 * 255;
      return (short) n;
    }
  }

  private static class RgbaConverter extends RgbConverter {
    private static final Pattern RGBA_PATTERN =
        Pattern.compile(
            "^\\s*rgba\\(\\s*"
                + "(\\d{1,3})\\s*,\\s*(\\d{1,3})\\s*,\\s*"
                + "(\\d{1,3})\\s*,\\s*(0|1|0\\.\\d+)\\s*\\)\\s*$");

    @Override
    protected Pattern getPattern() {
      return RGBA_PATTERN;
    }
  }

  private static class RgbaPctConverter extends RgbPctConverter {
    private static final Pattern RGBAPCT_PATTERN =
        Pattern.compile(
            "^\\s*rgba\\(\\s*"
                + "(\\d{1,3}|\\d{1,2}\\.\\d+)%\\s*,\\s*"
                + "(\\d{1,3}|\\d{1,2}\\.\\d+)%\\s*,\\s*"
                + "(\\d{1,3}|\\d{1,2}\\.\\d+)%\\s*,\\s*"
                + "(0|1|0\\.\\d+)\\s*\\)\\s*$");

    @Override
    protected Pattern getPattern() {
      return RGBAPCT_PATTERN;
    }
  }

  private static class HexConverter extends Converter {
    private static final Pattern HEX_PATTERN =
        Pattern.compile("#(\\p{XDigit}{2})(\\p{XDigit}{2})(\\p{XDigit}{2})");

    @Override
    protected Pattern getPattern() {
      return HEX_PATTERN;
    }

    @Override
    protected short fromMatchGroup(Matcher matcher, int index) {
      return Short.parseShort(matcher.group(index), 16);
    }
  }

  private static class Hex3Converter extends Converter {
    private static final Pattern HEX3_PATTERN =
        Pattern.compile("#(\\p{XDigit}{1})(\\p{XDigit}{1})(\\p{XDigit}{1})");

    @Override
    protected Pattern getPattern() {
      return HEX3_PATTERN;
    }

    @Override
    protected short fromMatchGroup(Matcher matcher, int index) {
      return Short.parseShort(matcher.group(index) + matcher.group(index), 16);
    }
  }

  private static class HslConverter extends Converter {
    private static final Pattern HSL_PATTERN =
        Pattern.compile(
            "^\\s*hsl\\(\\s*"
                + "(\\d{1,3})\\s*,\\s*"
                + "(\\d{1,3})\\%\\s*,\\s*"
                + "(\\d{1,3})\\%\\s*\\)\\s*$");

    @Override
    protected Pattern getPattern() {
      return HSL_PATTERN;
    }

    @Override
    protected Color createColor(Matcher matcher, double a) {
      double h = Double.parseDouble(matcher.group(1)) / 360;
      double s = Double.parseDouble(matcher.group(2)) / 100;
      double l = Double.parseDouble(matcher.group(3)) / 100;
      double r, g, b;

      if (s == 0) {
        r = l;
        g = r;
        b = r;
      } else {
        double luminocity2 = (l < 0.5) ? l * (1 + s) : l + s - l * s;
        double luminocity1 = 2 * l - luminocity2;
        r = hueToRgb(luminocity1, luminocity2, h + 1.0 / 3.0);
        g = hueToRgb(luminocity1, luminocity2, h);
        b = hueToRgb(luminocity1, luminocity2, h - 1.0 / 3.0);
      }

      return new Color(
          (short) Math.round(r * 255), (short) Math.round(g * 255), (short) Math.round(b * 255), a);
    }

    private double hueToRgb(double luminocity1, double luminocity2, double hue) {
      if (hue < 0.0) hue += 1;
      if (hue > 1.0) hue -= 1;
      if (hue < 1.0 / 6.0) return (luminocity1 + (luminocity2 - luminocity1) * 6.0 * hue);
      if (hue < 1.0 / 2.0) return luminocity2;
      if (hue < 2.0 / 3.0)
        return (luminocity1 + (luminocity2 - luminocity1) * ((2.0 / 3.0) - hue) * 6.0);
      return luminocity1;
    }
  }

  private static class HslaConverter extends HslConverter {
    private static final Pattern HSLA_PATTERN =
        Pattern.compile(
            "^\\s*hsla\\(\\s*"
                + "(\\d{1,3})\\s*,\\s*"
                + "(\\d{1,3})\\%\\s*,\\s*"
                + "(\\d{1,3})\\%\\s*,\\s*"
                + "(0|1|0\\.\\d+)\\s*\\)\\s*$");

    @Override
    protected Pattern getPattern() {
      return HSLA_PATTERN;
    }
  }

  private static class NamedColorConverter extends Converter {
    @Override
    public Color getColor(String value) {
      return Colors.valueOf(value.toUpperCase(Locale.ENGLISH)).getColorValue();
    }

    @Override
    public Pattern getPattern() {
      throw new UnsupportedOperationException("getPattern is unsupported");
    }
  }
}
