/*
 * 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 dvi.render;

import dvi.DviColor;
import dvi.DviException;
import dvi.DviPoint;
import dvi.DviRect;
import dvi.DviResolution;
import dvi.api.BinaryDevice;
import dvi.api.ImageDevice;

public class RunLengthSampler
extends AbstractDevice
implements BinaryDevice
{
  private final ImageDevice out;
  private final int sf;

  public RunLengthSampler(ImageDevice out)
  throws DviException
  {
    super(out.getResolution());
    this.out = out;
    sf = out.getResolution().shrinkFactor();
  }

  public DviResolution getResolution()
  throws DviException
  {
    return out.getResolution();
  }

  private int level = 0;

  public void begin()
  throws DviException
  {
    if (level == 0) {
      point = out.getReferencePoint().magnify(sf);
      // TODO: fix by a floored mod.

      super.setColor(out.getColor());
      out.begin(sf*sf);
      out.save();
      out.setReferencePoint(DviPoint.ORIGIN);
    } else {
      out.save();
    }
    level++;
  }

  public void end()
  throws DviException
  {
    level--;
    out.restore();
    if (level == 0) {
      out.end();
    }
  }

/*
  public void translate(int dx, int dy) throws DviException {
    super.translate(dx, dy);
    // TODO: propagate the change to out
    // if this method is called from outside begin() .. end() block.
  }

  public void save() throws DviException {
    super.save();
  }

  public void restore() throws DviException {
    super.restore();
    // TODO: propagate the change to out
    // if this method is called from outside begin() .. end() block.
  }
  */

  public void setColor(DviColor color)
  throws DviException
  {
    super.setColor(color);
    out.setColor(color);
  }

  private boolean needPaint = false;
  private DviRect cpy;
  private int l_pad_sf;

  private int [] s_buf = null;

  private int cph;
  private int s_sx;
  private int s_sy;

  private int yy;

  private int [] l_buf = null;
  private int [] empty_buf = null;

  public boolean beginRaster(int gw_sf, int gh_sf)
  throws DviException
  {
    out.save();
    needPaint = false;

    DviRect r_in_sf = new DviRect(point, gw_sf, gh_sf);
    DviRect r_in = r_in_sf.shrink(sf);
    l_pad_sf = r_in_sf.x() - r_in.x() * sf;
    int t_pad_sf = r_in_sf.y() - r_in.y() * sf;

    cpy = r_in;
    DviRect bounds = out.getBounds();
    if (bounds != null) {
      cpy = cpy.intersect(bounds);
    }

    if (cpy.isEmpty()) {
      return false;
    }

    s_buf = new int [cpy.width()];
    yy = t_pad_sf;

    s_sx = cpy.x() - r_in.x();
    s_sy = cpy.y() - r_in.y();
    l_buf = new int [r_in.width()];
    empty_buf = new int [r_in.width()];
    cph = cpy.height();

    out.setReferencePoint(cpy.topLeft());
    needPaint = out.beginImage(cpy.width(), cpy.height());
    return needPaint;
  }

  public void endRaster()
  throws DviException
  {
    out.restore();
    flushLine();
    out.endImage();
    s_buf = null;
    l_buf = null;
    empty_buf = null;
  }

  private void flushLine()
  throws DviException
  {
    if (needPaint == false) return;
    if (cpy.width() == 0) return; // In this case s_buf == null.

    if (s_sy > 0) {
      s_sy--;
    } else {
      if (cph > 0) {
        out.putLine(s_buf, 0, s_buf.length);
        cph--;
      }
    }
  }

  private int xx;
  private int x_pos;
  public void beginLine()
  throws DviException
  {
    System.arraycopy(empty_buf, 0, l_buf, 0, l_buf.length);

    x_pos = 0;
    xx = l_pad_sf;
  }

  public void endLine(int count)
  throws DviException
  {
    count++;
    final int left = sf - yy;
    if (left <= count) {
      for (int j=0; j<s_buf.length; j++)
        s_buf[j] += l_buf[j + s_sx] * left;
      flushLine();
      count -= left;
      if (count >= sf) {
        for (int j=0; j<s_buf.length; j++)
          s_buf[j] = l_buf[j + s_sx] * sf;
        while (count >= sf) {
          flushLine();
          count -= sf;
        }
      }
      System.arraycopy(empty_buf, 0, s_buf, 0, s_buf.length);
      yy = count;
    } else {
      yy += count;
    }

    if (count > 0) {
      for (int j=0; j<s_buf.length; j++)
        s_buf[j] += l_buf[j + s_sx] * count;
    }
  }

  public void putBits(int count, boolean paintFlag)
  throws DviException
  {
    if (paintFlag) {
      final int left = sf - xx;
      if (left <= count) {
        l_buf[x_pos++] += left;
        count -= left;
        while (count >= sf) {
          l_buf[x_pos++] += sf;
          count -= sf;
        }
        xx = count;
      } else {
        xx += count;
      }
      if (count > 0) {
        l_buf[x_pos] += count;
      }
    } else {
      xx += count;
      x_pos += xx / sf;
      xx %= sf;
      /*
      while (xx >= sf) {
        x_pos++;
        xx -= sf;
      }
      */
    }
  }
}
