/* (C) 2001-2002, DIUF, http://www.unifr.ch/diuf
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2 of the License, or (at your
 * option) any later version.
 *
 * 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.,
 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */

package iiuf.xmillum;

import iiuf.dom.DOMUtils;

import java.awt.AlphaComposite;
import java.awt.BasicStroke;
import java.awt.Composite;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Stroke;
import java.awt.Toolkit;
import java.awt.font.TextAttribute;
import java.util.HashMap;
import java.util.Map;

import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

/**
 * Style
 *
 * Represents a drawing style.
 *
 * @author $Author: ohitz $
 * @version $Revision: 1.1 $
 */
public class Style {

  static Map parameters = new HashMap();

  boolean   isFilled    = false;
  boolean   isXOR       = false;
  boolean   isHilight   = false;

  Color     foreground;
  Color     background;
  Composite composite;

  String    fontFamily    = "helvetica";
  Float     fontSlope     = TextAttribute.POSTURE_REGULAR;
  Float     fontWeight    = TextAttribute.WEIGHT_REGULAR;
  float     fontSize      = 8.0f;
  Boolean   textDirection = TextAttribute.RUN_DIRECTION_LTR;
  Stroke    stroke        = new BasicStroke(1.0f);

  int       resolution    = 300;

  static {
    parameters.put("foreground", new Parameter() {
	public void setParam(BrowserContext c, Object o, String v) throws ParameterException {
	  Style s = (Style) o;
	  s.foreground = Color.getColor(v);
	  if (s.foreground == null) {
	    c.log("Color "+v+" not found, using red instead");
	    s.foreground = Color.red;
	  }
	}
      });
    parameters.put("background", new Parameter() {
	public void setParam(BrowserContext c, Object o, String v) throws ParameterException {
	  Style s = (Style) o;
	  s.background = Color.getColor(v);
	  if (s.background == null) {
	    c.log("Color "+v+" not found, using red instead");
	    s.background = Color.red;
	  }
	}
      });
    parameters.put("xor", new Parameter() {
	public void setParam(BrowserContext c, Object o, String v) throws ParameterException {
	  Style s = (Style) o;
	  if (trueFalse(v)) s.isXOR = true;
	}
      });
    parameters.put("fill", new Parameter() {
	public void setParam(BrowserContext c, Object o, String v) throws ParameterException {
	  Style s = (Style) o;
	  if (trueFalse(v)) s.isFilled = true;
	}
      });
    parameters.put("hilight", new Parameter() {
	public void setParam(BrowserContext c, Object o, String v) throws ParameterException {
	  Style s = (Style) o;
	  if (trueFalse(v)) s.isHilight = true;
	}
      });
    parameters.put("transparency", new Parameter() {
	public void setParam(BrowserContext c, Object o, String v) throws ParameterException {
	  Style s = (Style) o;
	  float transparency = 0.0f;
	  if (v != null) {
	    try {
	      transparency = Float.parseFloat(v);
	    } catch (NumberFormatException n) {
	      throw new ParameterException("Not a valid transparency value "+v);
	    }
	    if (transparency < 0.0f || transparency > 1.0f) {
	      throw new ParameterException("Not a valid transparency value "+transparency);
	    }
	  }
	  s.composite = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, transparency);
	}
      });
    parameters.put("fontfamily", new Parameter() {
	public void setParam(BrowserContext c, Object o, String v) throws ParameterException {
	  Style s = (Style) o;
	  s.fontFamily = v;
	}
      });
    parameters.put("fontweight", new Parameter() {
	public void setParam(BrowserContext c, Object o, String v) throws ParameterException {
	  Style s = (Style) o;
	  if ("regular".equals(v)) {
	    s.fontWeight = TextAttribute.WEIGHT_REGULAR;
	  } else if ("medium".equals(v)) {
	    s.fontWeight = TextAttribute.WEIGHT_MEDIUM;
	  } else if ("bold".equals(v)) {
	    s.fontWeight = TextAttribute.WEIGHT_BOLD;
	  } else if ("extrabold".equals(v)) {
	    s.fontWeight = TextAttribute.WEIGHT_EXTRABOLD;
	  } else {
	    throw new ParameterException("Unknown fontweight value "+v);
	  }
	}
      });
    parameters.put("fontslope", new Parameter() {
	public void setParam(BrowserContext c, Object o, String v) throws ParameterException {
	  Style s = (Style) o;
	  if ("upright".equals(v)) {
	    s.fontSlope = TextAttribute.POSTURE_REGULAR;
	  } else if ("slanted".equals(v)) {
	    s.fontSlope = TextAttribute.POSTURE_OBLIQUE;
	  } else {
	    throw new ParameterException("Unknown fontslope value "+v);
	  }
	}
      });
    parameters.put("fontsize", new Parameter() {
	public void setParam(BrowserContext c, Object o, String v) throws ParameterException {
	  Style s = (Style) o;
	  try {
	    s.fontSize = Float.parseFloat(v);
	  } catch (NumberFormatException n) {
	    throw new ParameterException("Not a valid fontsize value "+v);
	  }
	}
      });
    parameters.put("textdirection", new Parameter() {
	public void setParam(BrowserContext c, Object o, String v) throws ParameterException {
	  Style s = (Style) o;
	  if ("l2r".equals(v)) {
	    s.textDirection = TextAttribute.RUN_DIRECTION_LTR;
	  } else if ("r2l".equals(v)) {
	    s.textDirection = TextAttribute.RUN_DIRECTION_RTL;
	  } else {
	    throw new ParameterException("Not a valid textdirection value "+v);
	  }
	}
      });
    parameters.put("resolution", new Parameter() {
	public void setParam(BrowserContext c, Object o, String v) throws ParameterException {
	  Style s = (Style) o;
	  try {
	    s.resolution = Integer.parseInt(v);
	  } catch (NumberFormatException n) {
	    throw new ParameterException("Not a valid resolution value "+v);
	  }
	}
      });
    parameters.put("stroke-width", new Parameter() {
	public void setParam(BrowserContext c, Object o, String v) throws ParameterException {
	  Style s = (Style) o;
	  try {
	    s.stroke = new BasicStroke(Float.parseFloat(v));
	  } catch (NumberFormatException n) {
	    throw new ParameterException("Not a valid stroke-width value "+v);
	  }
	}
      });
  }

  /**
   * Creates a default style.
   */

  public Style() {
  }

  /**
   * Creates a style with the specified XOR value
   */

  public Style(boolean hl) {
    isHilight = hl;
  }

  /**
   * Creates a new style.
   *
   * @param context Current browser context
   * @param name Name of the style
   * @param e Element containing the style description
   */

  public Style(BrowserContext context, String name, Element e) {
    Parameter.setParameters(context, e, this, parameters);
  }

  /**
   * Set style parameters on a graphics object.
   *
   * @param g Graphics object to update
   */

  public void setStyle(Graphics2D g) {
    g.setStroke(getStroke());
    if (isHilight()) {
      Color c = g.getColor();
      g.setColor(getHilightColor(c));
      c = g.getBackground();
      g.setBackground(getHilightColor(c));
    } else {
      Color c = getForeground();
      if (c != null) g.setColor(c);
      c = getBackground();
      if (c != null) g.setBackground(c);
      if (getComposite() != null) {
	g.setComposite(getComposite());
      } else if (isXOR()) {
	g.setXORMode(Color.white);
      } else {
	g.setPaintMode();
      }
    }
  }

  /**
   * Returns whether to hilight or not
   *
   * @return true for hilight, false otherwise
   */

  public boolean isHilight() {
    return isHilight;
  }

  /**
   * Returns whether to fill areas or not.
   *
   * @return true for fill, false otherwise
   */

  public boolean isFilled() {
    return isFilled;
  }

  /**
   * Returns whether to draw in XOR mode or not.
   *
   * @return true for XOR mode, false otherwise
   */

  public boolean isXOR() {
    return isXOR;
  }

  /**
   * Returns the foreground color.
   *
   * @return foreground color or null
   */

  public Color getForeground() {
    return foreground;
  }

  /**
   * Returns the background color.
   *
   * @return background color or null
   */

  public Color getBackground() {
    return background;
  }

  /**
   * Returns the composite.
   *
   * @return composite or null if the composite is not changed
   */

  public Composite getComposite() {
    return composite;
  }

  /**
   * Returns the stroke.
   *
   * @return composite or null if the composite is not changed
   */

  public Stroke getStroke() {
    return stroke;
  }

  /**
   * Returns the font attributes for use in new Font().
   * 
   * @param scale The current zoom value (0 < scale)
   * @return font attributes
   */

  public Map getFontAttributes(double scale) {
    HashMap fontAttributes = new HashMap();
    fontAttributes.put(TextAttribute.FAMILY,        fontFamily);
    fontAttributes.put(TextAttribute.WEIGHT,        fontWeight);
    fontAttributes.put(TextAttribute.POSTURE,       fontSlope);

    float rs = (float) resolution / (float) Toolkit.getDefaultToolkit().getScreenResolution();
    float size = rs * (float) scale * fontSize;
    fontAttributes.put(TextAttribute.SIZE,          new Float(size));

    fontAttributes.put(TextAttribute.RUN_DIRECTION, textDirection);
    return fontAttributes;
  }

  /** Cache for already calculated colors */

  static Map hilightColors = new HashMap();

  /**
   * Calculates hilight colors.
   *
   * @param rgbColor Base color
   * @return "hilighted" version of rgbColor
   */

  Color getHilightColor(Color rgbColor) {
    Color h = (Color) hilightColors.get(rgbColor);
    if (h == null) {
      float[] rgb = rgbColor.getRGBColorComponents(null);
      // Some implementations return values between 0 and 1, others between 0 and 255...
//       if (rgb[0] <= 1.0 && rgb[1] <= 1.0 && rgb[2] < 1.0) {
// 	rgb[0] = rgb[0]*256;
// 	rgb[1] = rgb[1]*256;
// 	rgb[2] = rgb[2]*256;
//       }
      float[] hsb = Color.RGBtoHSB((int) rgb[0], (int) rgb[1], (int) rgb[2], null);
      h = Color.getHSBColor(hsb[0]+0.5F, hsb[1], Math.max(1.0F, hsb[2]*2));
      hilightColors.put(rgbColor, h);
    }
    return h;
  }
}