/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */

/*
  Part of the Processing project - http://processing.org
  Copyright (c) 2012-15 The Processing Foundation

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License version 2
  as published by the Free Software Foundation.

  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.
*/

package processing.mode.java.tweak;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.geom.AffineTransform;
import java.util.ArrayList;

import processing.mode.java.JavaTextAreaPainter;


public class ColorControlBox {
  public boolean visible;
  ArrayList<Handle> handles;
  ColorMode colorMode;
  Color color;
  boolean ilegalColor = false;
  boolean isBW;
  boolean isHex;

  String drawContext;

  // interface
  int x, y, width, height;
  JavaTextAreaPainter painter;


  public ColorControlBox(String context, ColorMode mode,
                         ArrayList<Handle> handles) {
    this.drawContext = context;
    this.colorMode = mode;
    this.handles = handles;

    // add this box to the handles so they can update this color on change
    for (Handle h : handles) {
      h.setColorBox(this);
    }

    isBW = isGrayScale();
    isHex = isHexColor();
    color = getCurrentColor();

    visible = Settings.alwaysShowColorBoxes;
  }


  public void initInterface(JavaTextAreaPainter textAreaPainter,
                            int x, int y, int w, int h) {
    this.painter = textAreaPainter;
    this.x = x;
    this.y = y;
    this.width = w;
    this.height = h;
  }


  public void setPos(int x, int y) {
    this.x = x;
    this.y = y;
  }


  public void draw(Graphics2D g2d) {
    if (!visible) {
      return;
    }

    AffineTransform trans = g2d.getTransform();
    g2d.translate(x, y);

    // draw current color
    g2d.setColor(color);
    g2d.fillRoundRect(0, 0, width, height, 5, 5);

    // draw black outline
    g2d.setStroke(new BasicStroke(1));
    g2d.setColor(Color.BLACK);
    g2d.drawRoundRect(0, 0, width, height, 5, 5);

    if (ilegalColor) {
      g2d.setColor(Color.RED);
      g2d.setStroke(new BasicStroke(2));
      g2d.drawLine(width-3, 3, 3, height-3);
    }

    g2d.setTransform(trans);
  }


  public boolean isGrayScale() {
    if (handles.size() <= 2) {
      int value = handles.get(0).newValue.intValue();
      if ((value&0xff000000) == 0) {
        return true;
      }
    }

    return false;
  }


  /**
   * @return true if number is hex or webcolor
   */
  private boolean isHexColor() {
    if (handles.get(0).type == "hex" || handles.get(0).type == "webcolor") {
      int value = handles.get(0).value.intValue();
      if ((value & 0xff000000) != 0) {
        return true;
      }
    }
    return false;
  }


  public Color getCurrentColor()
  {
    try {
      if (handles.size() == 1)
      {
        if (isBW) {
          // treat as color(gray)
          float gray = handles.get(0).newValue.floatValue();
          return verifiedGrayColor(gray);
        }
        else {
          // treat as color(argb)
          int argb = handles.get(0).newValue.intValue();
          return verifiedHexColor(argb);
        }
      }
      else if (handles.size() == 2)
      {
        if (isBW) {
          // color(gray, alpha)
          float gray = handles.get(0).newValue.floatValue();
          return verifiedGrayColor(gray);
        }
        else {
          // treat as color(argb, a)
          int argb = handles.get(0).newValue.intValue();
          float a = handles.get(1).newValue.floatValue();
          return verifiedHexColor(argb, a);
        }
      }
      else if (handles.size() == 3)
      {
        // color(v1, v2, v3)
        float v1 = handles.get(0).newValue.floatValue();
        float v2 = handles.get(1).newValue.floatValue();
        float v3 = handles.get(2).newValue.floatValue();

        if (colorMode.modeType == ColorMode.RGB) {
          return verifiedRGBColor(v1, v2, v3, colorMode.aMax);
        }
        else {
          return verifiedHSBColor(v1, v2, v3, colorMode.aMax);
        }
      }
      else if (handles.size() == 4)
      {
        // color(v1, v2, v3, alpha)
        float v1 = handles.get(0).newValue.floatValue();
        float v2 = handles.get(1).newValue.floatValue();
        float v3 = handles.get(2).newValue.floatValue();
        float a = handles.get(3).newValue.floatValue();

        if (colorMode.modeType == ColorMode.RGB) {
          return verifiedRGBColor(v1, v2, v3, a);
        }
        else {
          return verifiedHSBColor(v1, v2, v3, a);
        }
      }
    }
    catch (Exception e) {
      System.out.println("error parsing color value: " + e.toString());
      ilegalColor = true;
      return Color.WHITE;
    }

    // couldn't figure out this color, return WHITE color
    ilegalColor = true;
    return Color.WHITE;
  }

  private Color verifiedGrayColor(float gray)
  {
    if (gray < 0 || gray > colorMode.v1Max) {
      return colorError();
    }

    ilegalColor = false;
    gray = gray/colorMode.v1Max * 255;
    return new Color((int)gray, (int)gray, (int)gray, 255);
  }

  private Color verifiedHexColor(int argb)
  {
    int r = (argb>>16)&0xff;
    int g = (argb>>8)&0xff;
    int b = (argb&0xff);

    ilegalColor = false;
    return new Color(r, g, b, 255);
  }

  private Color verifiedHexColor(int argb, float alpha)
  {
    int r = (argb>>16)&0xff;
    int g = (argb>>8)&0xff;
    int b = (argb&0xff);

    ilegalColor = false;
    return new Color(r, g, b, 255);
  }

  public Color verifiedRGBColor(float r, float g, float b, float a)
  {
    if (r < 0 || r > colorMode.v1Max ||
      g < 0 || g > colorMode.v2Max ||
      b < 0 || b > colorMode.v3Max) {
      return colorError();
    }

    ilegalColor = false;
    r = r/colorMode.v1Max * 255;
    g = g/colorMode.v2Max * 255;
    b = b/colorMode.v3Max * 255;
    return new Color((int)r, (int)g, (int)b, 255);
  }

  public Color verifiedHSBColor(float h, float s, float b, float a)
  {
    if (h < 0 || h > colorMode.v1Max ||
      s < 0 || s > colorMode.v2Max ||
      b < 0 || b > colorMode.v3Max) {
      return colorError();
    }

    ilegalColor = false;
    Color c = Color.getHSBColor(h/colorMode.v1Max, s/colorMode.v2Max, b/colorMode.v3Max);
    return new Color(c.getRed(), c.getGreen(), c.getBlue(), 255);
  }

  private Color colorError()
  {
    ilegalColor = true;
    return Color.WHITE;
  }

  public void colorChanged()
  {
    color = getCurrentColor();
  }

  public int getTabIndex()
  {
    return handles.get(0).tabIndex;
  }

  public int getLine()
  {
    return handles.get(0).line;
  }

  public int getCharIndex()
  {
    int lastHandle = handles.size()-1;
    return handles.get(lastHandle).newEndChar + 2;
  }

  /* Check if the point is in the box
   *
   */
  public boolean pick(int mx, int my)
  {
    if (!visible) {
      return false;
    }

    if (mx>x && mx < x+width && my>y && my<y+height) {
      return true;
    }

    return false;
  }

  /* Only show the color box if mouse is on the same line
   *
   * return true if there was change
   */
  public boolean setMouseY(int my)
  {
    boolean change = false;

    if (my>y && my<y+height) {
      if (!visible) {
        change = true;
      }
      visible = true;
    }
    else {
      if (visible) {
        change = true;
      }
      visible = false;
    }

    return change;
  }

  /* Update the color numbers with the new values that were selected
   * in the color selector
   *
   *  hue, saturation and brightness parameters are always 0-255
   */
  public void selectorChanged(int hue, int saturation, int brightness)
  {
    if (isBW) {
      // color(gray) or color(gray, alpha)
      handles.get(0).setValue((float)hue/255*colorMode.v1Max);
    }
    else {
      if (handles.size() == 1 || handles.size() == 2) {
        // color(argb)
        int prevVal = handles.get(0).newValue.intValue();
        int prevAlpha = (prevVal>>24)&0xff;
        Color c = Color.getHSBColor((float)hue/255, (float)saturation/255, (float)brightness/255);
        int newVal = (prevAlpha<<24) | (c.getRed()<<16) | (c.getGreen()<<8) | (c.getBlue());
        handles.get(0).setValue(newVal);
      }
      else if (handles.size() == 3 || handles.size() == 4) {
        // color(v1, v2, v3) or color(v1, v2, v3, alpha)
        if (colorMode.modeType == ColorMode.HSB) {
          // HSB
          float v1 = (float)hue/255 * colorMode.v1Max;
          float v2 = (float)saturation/255 * colorMode.v2Max;
          float v3 = (float)brightness/255 * colorMode.v3Max;
          handles.get(0).setValue(v1);
          handles.get(1).setValue(v2);
          handles.get(2).setValue(v3);
        }
        else {
          // RGB
          Color c = Color.getHSBColor((float)hue/255, (float)saturation/255, (float)brightness/255);
          handles.get(0).setValue((float)c.getRed()/255*colorMode.v1Max);
          handles.get(1).setValue((float)c.getGreen()/255*colorMode.v2Max);
          handles.get(2).setValue((float)c.getBlue()/255*colorMode.v3Max);
        }
      }
    }

    // update our own color
    color = getCurrentColor();

    // update code text painter so the user will see the changes
    painter.updateCodeText();
    painter.repaint();
  }


  public String toString() {
    return handles.size() + " handles, color mode: " + colorMode.toString();
  }
}
