/*
 * 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 jp.sourceforge.dvibrowser.dvicore.DviException;
import jp.sourceforge.dvibrowser.dvicore.DviRect;
import jp.sourceforge.dvibrowser.dvicore.DviResolution;
import jp.sourceforge.dvibrowser.dvicore.api.ImageDevice;

// TODO: use GammaCorrector.
// TODO: test this class

public class ByteRGBImage
{
  private final byte [] buf;
  private final int width;
  private final int height;
  private final int pitch;

  public ByteRGBImage(int width, int height)
  {
    this.width = width;
    this.height = height;
    this.pitch = width * 3;
    this.buf = new byte [pitch * height];
  }

  public ByteRGBImage(byte [] buf, int width, int height, int pitch)
  {
    this.buf = buf;
    this.width = width;
    this.height = height;
    this.pitch = pitch;
  }

  public ByteRGBImage(byte [] buf, int width, int height)
  {
    this(buf, width, height, 3*width);
  }

  public byte [] getBuffer() { return buf; }

  public int width() { return width; }
  public int height() { return height; }

  public void fill(int rgb)
  {
    final byte r = (byte) (rgb >>> 16);
    final byte g = (byte) (rgb >>>  8);
    final byte b = (byte) (rgb >>>  0);
    int p0 = 0;
    for (int i=0; i<height; i++) {
      int p = p0;
      for (int j=0; j<width; j++) {
        buf[p++] = r;
        buf[p++] = g;
        buf[p++] = b;
      }
      p0 += pitch;
    }
  }

  public ImageDevice getImageDevice(DviResolution res)
  {
    return new ImageDeviceImpl(res);
  }

  // TODO: make this static
  private class ImageDeviceImpl
  extends AbstractDevice
  implements ImageDevice
  {
    protected ImageDeviceImpl(DviResolution res) {
      super(res);
    }

    public DviRect getBounds() {
      return new DviRect(-point.x, -point.y, width, height);
    }

    private final AlphaCache alphaCache = new AlphaCache();
    public void begin(int maxval) {
      alphaCache.setMaxValue(maxval);
    }
    public void end() {
    }

    private int ptr = 0;
//    private int gw;
//    private int gh;
    public boolean beginImage(int w, int h)
    throws DviException
    {
//      gw = w;
//      gh = h;
      ptr = 3 * point.x + point.y * pitch;
      return true;
    }
    public void endImage() {
    }

    public void putLine(int [] l_buf, int off, int len)
    throws DviException
    {
      final int color = getColor().toIntRGB();
      final byte r = (byte) (color >>> 16);
      final byte g = (byte) (color >>>  8);
      final byte b = (byte) (color >>>  0);
      int p = ptr;
      for (int i=0; i<len; i++) {
        final int alpha10 = alphaCache.get(l_buf[off + i]);
        if (alpha10 != 0) {
          buf[p+0] = blend(buf[p+0], r, alpha10);
          buf[p+1] = blend(buf[p+1], g, alpha10);
          buf[p+2] = blend(buf[p+2], b, alpha10);
        }
        p += 3;
      }
      ptr += pitch;
    }
  }

  private static class AlphaCache
  {
    private final int [] table = new int[1024];
    private int maxval;
    private boolean canLookUp;
    private double gamma = 0.90;
    private AlphaCache() {}

    /*
    private void setGamma(double gamma) {
      this.gamma = Math.max(0.0, gamma);
    }
    */

    private void setMaxValue(int maxval) {
      if (this.maxval == maxval) return;
      this.maxval = maxval;
      if (maxval < 1024) {
        for (int i=0; i<=maxval; i++)
          table[i] = alpha10Internal(i);
        canLookUp = true;
      } else {
        canLookUp = false;
      }
    }

    private int get(int c) {
      if (!canLookUp || c < 0 || maxval < c)
        return alpha10Internal(c);
      else 
        return table[c];
    }

    private int alpha10Internal(int c) {
      return (int) (1024 * Math.pow((double) c / maxval, gamma) + 0.5);
    }
  }

  private static byte blend(final byte _c1, final byte _c2, final int alpha10)
  {
    final int c1 = ((int) _c1) & 0xff;
    final int c2 = ((int) _c2) & 0xff;
    return (byte) (c1 + ((alpha10 * (c2 - c1)) >>> 10));
  }
}
