/*
 * Copyright (c) 2009, Takeyuki Nagao
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or
 * without modification, are permitted provided that the
 * following conditions are met:
 * 
 *  * Redistributions of source code must retain the above
 *    copyright notice, this list of conditions and the
 *    following disclaimer.
 *  * Redistributions in binary form must reproduce the above
 *    copyright notice, this list of conditions and the
 *    following disclaimer in the documentation and/or other
 *    materials provided with the distribution.
 *    
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
 * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
 * OF SUCH DAMAGE.
 */

package jp.sourceforge.dvibrowser.dvicore.render;

import java.util.logging.Logger;

import jp.sourceforge.dvibrowser.dvicore.DviByteRange;
import jp.sourceforge.dvibrowser.dvicore.DviColor;
import jp.sourceforge.dvibrowser.dvicore.DviException;
import jp.sourceforge.dvibrowser.dvicore.DviFontSpec;
import jp.sourceforge.dvibrowser.dvicore.DviObject;
import jp.sourceforge.dvibrowser.dvicore.DviRegister;
import jp.sourceforge.dvibrowser.dvicore.DviResolution;
import jp.sourceforge.dvibrowser.dvicore.DviUnit;
import jp.sourceforge.dvibrowser.dvicore.api.BinaryDevice;
import jp.sourceforge.dvibrowser.dvicore.api.DevicePainter;
import jp.sourceforge.dvibrowser.dvicore.api.DviContextSupport;
import jp.sourceforge.dvibrowser.dvicore.api.DviData;
import jp.sourceforge.dvibrowser.dvicore.api.DviFont;
import jp.sourceforge.dvibrowser.dvicore.api.DviPage;
import jp.sourceforge.dvibrowser.dvicore.api.FullMetrics;
import jp.sourceforge.dvibrowser.dvicore.api.GeometerContext;
import jp.sourceforge.dvibrowser.dvicore.api.Glyph;
import jp.sourceforge.dvibrowser.dvicore.cmd.DviBop;
import jp.sourceforge.dvibrowser.dvicore.ctx.DviToolkit;
import jp.sourceforge.dvibrowser.dvicore.font.LogicalFont;
import jp.sourceforge.dvibrowser.dvicore.special.Anchor;
import jp.sourceforge.dvibrowser.dvicore.special.AnchorSet;


// TODO: support color special across pages.

public class DefaultDevicePainter
extends DviObject
implements DevicePainter
{
  private static final Logger LOGGER = Logger.getLogger(DefaultDevicePainter.class.getName());
  private BinaryDevice out = null;
  private DviColor defaultColor;
  
  public DefaultDevicePainter(DviContextSupport dcs)
  {
    super(dcs);
  }

  public void setOutput(BinaryDevice out)
  throws DviException
  {
    this.out = out;
    defaultColor = out.getColor();
  }

  public BinaryDevice getOutput() { return out; }


  private GeometerContext geom_ctx = null;

  protected GeometerContext getGeometerContext()
  {
    return geom_ctx;
  }

  private DviUnit dviUnit = null;

  protected DviUnit getDviUnit()
  {
    return dviUnit;
  }

  private DviResolution res = null;
  private double factor;

  public void begin(GeometerContext ctx)
  throws DviException
  {
    geom_ctx = ctx;
    dviUnit = geom_ctx.getExecuterContext()
                      .getData()
                      .getDviUnit();
    res = out.getResolution();
    out.begin();
    out.translate(res.dpi(), res.dpi());
    factor = dviUnit.factorDouble(res.dpi());
  }

  public void end()
  throws DviException
  {
    out.end();
    res = null;
    dviUnit = null;
    geom_ctx = null;
  }

  private AnchorSet anchors = null;

  public void beginPage(DviBop bop)
  throws DviException
  {
    DviToolkit utils = getDviContext().getDviToolkit();
    DviData data = geom_ctx.getExecuterContext().getData();
    if (data instanceof DviPage) {
      anchors = utils.getAnchorSet((DviPage) data);
      LOGGER.finer("anchors=" + anchors);
    }
  }

  public void endPage()
  throws DviException
  {
    anchors = null;
  }

  private LogicalFont lf = null;

  protected LogicalFont getLogicalFont()
  {
    return lf;
  }

  private boolean fontResolved = false;
  private DviFont font = null;

  protected DviFont getFont()
  {
    return font;
  }

  public void beginFont(DviFontSpec fs)
  throws DviException
  {
    lf = LogicalFont.getInstance(fs, dviUnit, res);
    fontResolved = false;
    font = null;
  }

  public void endFont()
  throws DviException
  {
    fontResolved = false;
    font = null;
    lf = null;
  }

  private boolean enableCharRendering = true;
  private boolean enableCharBoundingBox = false;
  
  private void drawRectInternal(int ax, int ay, int ex, int ey)
  throws DviException
  {
    out.save();
    try {
      out.translate(ax, ay);
      realDrawRule(ex - ax + 1, ey - ay + 1);
    } finally {
      out.restore();
    }
  }
  
  protected void drawCharBoundingBox(int code)
  throws DviException
  {
    final DviRegister reg = geom_ctx.getRegister();

    DviFontSpec fs = lf.fontSpec();
    FullMetrics fm = getDviContext().findDviFullMetrics(fs);
    if (fm != null) {
      int width = fs.tfmToDvi(fm.getTfmWidth(code));
      int height = fs.tfmToDvi(fm.getTfmHeight(code));
      int depth = fs.tfmToDvi(fm.getTfmDepth(code));

      final int ax_sf = (int)(factor * reg.getH() + 0.5);
      final int ex_sf = (int)(factor * (reg.getH() + width - 1) + 0.5);
      final int ay_sf = (int)(factor * (reg.getV() - height + 1) + 0.5);
      final int ey_sf = (int)(factor * (reg.getV() + depth - 1) + 0.5);
      final int by_sf = (int)(factor * (reg.getV() ) + 0.5);
      
      drawRectInternal(ax_sf, ay_sf, ex_sf, ay_sf);
      drawRectInternal(ax_sf, ey_sf, ex_sf, ey_sf);
      drawRectInternal(ax_sf, ay_sf, ax_sf, ey_sf);
      drawRectInternal(ex_sf, ay_sf, ex_sf, ey_sf);
      drawRectInternal(ax_sf, by_sf, ex_sf, by_sf);
    }
  }
  
  public void drawChar(int code)
  throws DviException
  {
    handleAnchors();

    if (getEnableCharRendering()) {
      final DviRegister reg = geom_ctx.getRegister();
      final int rx_sf = (int) (factor * reg.getH() + 0.5);
      final int ry_sf = (int) (factor * reg.getV() + 0.5);

      out.save();
      try {
        out.translate(rx_sf, ry_sf);
        realDrawChar(lf, code);
      } finally {
        out.restore();
      }
    }
    if (getEnableCharBoundingBox()) {
      drawCharBoundingBox(code);
    }
  }
  
  protected void resolveFont()
  {
    if (!fontResolved) {
      try {
        font = getDviContext().findDviFont(lf);
      } catch (DviException ex) {
        // TODO: logging
        ex.printStackTrace();
        font = null;
      } finally {
        fontResolved = true;
      }
    }
  }
  
  protected void realDrawChar(LogicalFont lf, int code)
  throws DviException
  {
    resolveFont();
    if (font == null)
      return;

    determineColor(out);
    Glyph g = font.getGlyph(lf, code);

    if (g != null) {
      g.rasterizeTo(out);
    }
  }

  public void drawRule(int width, int height)
  throws DviException
  {
    handleAnchors();
    if (width <= 0 || height <= 0) return;

    final DviRegister reg = geom_ctx.getRegister();

    final int ax_sf = (int)(factor * reg.getH() + 0.5);
    final int ex_sf = (int)(factor * (reg.getH() + width - 1) + 0.5);
    final int ay_sf = (int)(factor * (reg.getV() - height + 1) + 0.5);
    final int ey_sf = (int)(factor * reg.getV() + 0.5);

    out.save();
    try {
      out.translate(ax_sf, ay_sf);
      realDrawRule(ex_sf - ax_sf + 1, ey_sf - ay_sf + 1);
    } finally {
      out.restore();
    }
  }

  protected void realDrawRule(int w_sf, int h_sf)
  throws DviException
  {
    if (out.beginRaster(w_sf, h_sf)) {
      determineColor(out);
      out.beginLine();
      out.putBits(w_sf, true);
      out.endLine(h_sf);
    }
    out.endRaster();
  }

  private final java.util.Stack<DviColor> colorStack
    = new java.util.Stack<DviColor>();
  private static final java.util.regex.Pattern pushPat
    = java.util.regex.Pattern.compile(
      "color\\s+push\\s+(.*)",
      java.util.regex.Pattern.CASE_INSENSITIVE
    );
  private static final java.util.regex.Pattern popPat
    = java.util.regex.Pattern.compile(
      "color\\s+pop",
      java.util.regex.Pattern.CASE_INSENSITIVE
    );
  private static final java.util.regex.Pattern colorPat
    = java.util.regex.Pattern.compile(
      "color\\s+(.*)",
      java.util.regex.Pattern.CASE_INSENSITIVE
    );
  private DviColor specialColor = DviColor.INVALID;

  
  // TODO: outsource color special handler.
  public void drawSpecial(byte [] _xxx)
  throws DviException
  {
    handleAnchors();
    String xxx = new String(_xxx);
    java.util.regex.Matcher mat;
    if ((mat = pushPat.matcher(xxx)).matches()) {
      DviColor aColor = DviColor.parseColor(mat.group(1));
      colorStack.push(out.getColor());
      specialColor = aColor;
    } else if ((mat = popPat.matcher(xxx)).matches()) {
      if (colorStack.empty())
        // TODO: Handle this as a warning.
        throw new IllegalStateException
          ("Color stack underflow");
      specialColor = colorStack.pop();
    } else if ((mat = colorPat.matcher(xxx)).matches()) {
      DviColor aColor = DviColor.parseColor(mat.group(1));
      specialColor = aColor;
    }
  }

  // TODO: make anchor renderer injectable.
  private DviColor anchorColor = DviColor.INVALID;
  private void handleAnchors()
  throws DviException
  {
    if (anchors == null || anchors.size() == 0) return;
    LOGGER.finer("Rendering Anchor" + anchors);
    long begin = geom_ctx.getExecuterContext().getCommandRange().begin();
    anchorColor = DviColor.INVALID;
    for (DviByteRange br : anchors) {
      if (!br.contains(begin)) continue;
      if (br instanceof Anchor.Href) {
  //      Anchor.Href href = (Anchor.Href) br;
        //TODO: outsource the color settings.
        anchorColor = DviColor.parseColor("blue");
//      } else if (br instanceof Anchor.Source) {
//        Anchor.Source source = (Anchor.Source) br;
        // anchorColor = DviColor.parseColor("yellow");
      }
    }
  }

  private void determineColor(BinaryDevice out) 
  throws DviException
  {
    if (anchorColor.isValid()) {
      out.setColor(anchorColor);
    } else if (specialColor.isValid()) {
      out.setColor(specialColor);
    } else {
      out.setColor(defaultColor);
    }
  }

  public void setEnableCharRendering(boolean enableCharRendering)
  {
    this.enableCharRendering = enableCharRendering;
  }

  public boolean getEnableCharRendering()
  {
    return enableCharRendering;
  }

  public void setEnableCharBoundingBox(boolean enableCharBoundingBox)
  {
    this.enableCharBoundingBox = enableCharBoundingBox;
  }

  public boolean getEnableCharBoundingBox()
  {
    return enableCharBoundingBox;
  }
}
