/**
 * Copyright (c) 2005, www.pdfbox.org
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 * 2. 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.
 * 3. Neither the name of pdfbox; nor the names of its
 *    contributors may be used to endorse or promote products derived from this
 *    software without specific prior written permission.
 *
 * 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 REGENTS 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.
 *
 * http://www.pdfbox.org
 *
 */
package org.pdfbox.pdmodel.graphics.predictor;

import java.util.Random;

/**
 * Implements different PNG predictor algorithms that is used in PDF files.
 * 
 * @author xylifyx@yahoo.co.uk
 * @version $Revision: 1.3 $
 * @see <a href="http://www.w3.org/TR/PNG-Filters.html">PNG Filters</a>
 */
public abstract class PredictorAlgorithm 
{
    private int width;

    private int height;

    private int bpp;

    /**
     * check that buffer sizes matches width,height,bpp. This implementation is
     * used by most of the filters, but not Uptimum.
     * 
     * @param src The source buffer.
     * @param dest The destination buffer.
     */
    public void checkBufsiz(byte[] src, byte[] dest) 
    {
        if (src.length != dest.length) 
        {
            throw new IllegalArgumentException("src.length != dest.length");
        }
        if (src.length != getWidth() * getHeight() * getBpp()) 
        {
            throw new IllegalArgumentException(
                    "src.length != width * height * bpp");
        }
    }

    /**
     * encode line of pixel data in src from srcOffset and width*bpp bytes
     * forward, put the decoded bytes into dest.
     * 
     * @param src
     *            raw image data
     * @param dest
     *            encoded data
     * @param srcDy
     *            byte offset between lines
     * @param srcOffset
     *            beginning of line data
     * @param destDy
     *            byte offset between lines
     * @param destOffset
     *            beginning of line data
     */
    public abstract void encodeLine(byte[] src, byte[] dest, int srcDy,
            int srcOffset, int destDy, int destOffset);

    /**
     * decode line of pixel data in src from src_offset and width*bpp bytes
     * forward, put the decoded bytes into dest.
     * 
     * @param src
     *            encoded image data
     * @param dest
     *            raw data
     * @param srcDy
     *            byte offset between lines
     * @param srcOffset
     *            beginning of line data
     * @param destDy
     *            byte offset between lines
     * @param destOffset
     *            beginning of line data
     */
    public abstract void decodeLine(byte[] src, byte[] dest, int srcDy,
            int srcOffset, int destDy, int destOffset);

    /**
     * Simple command line program to test the algorithm.
     * 
     * @param args The command line arguments.
     */
    public static void main(String[] args) 
    {
        Random rnd = new Random();
        int width = 5;
        int height = 5;
        int bpp = 3;
        byte[] raw = new byte[width * height * bpp];
        rnd.nextBytes(raw);
        System.out.println("raw:   ");
        dump(raw);
        for (int i = 10; i < 15; i++) 
        {
            byte[] decoded = new byte[width * height * bpp];
            byte[] encoded = new byte[width * height * bpp];

            PredictorAlgorithm filter = PredictorAlgorithm.getFilter(i);
            filter.setWidth(width);
            filter.setHeight(height);
            filter.setBpp(bpp);
            filter.encode(raw, encoded);
            filter.decode(encoded, decoded);
            System.out.println(filter.getClass().getName());
            dump(decoded);
        }
    }

    /**
     * Get the left pixel from the buffer.
     * 
     * @param buf The buffer.
     * @param offset The offset into the buffer.
     * @param dy The dy value.
     * @param x The x value.
     * 
     * @return The left pixel.
     */
    public int leftPixel(byte[] buf, int offset, int dy, int x) 
    {
        return x >= getBpp() ? buf[offset + x - getBpp()] : 0;
    }

    /**
     * Get the above pixel from the buffer.
     * 
     * @param buf The buffer.
     * @param offset The offset into the buffer.
     * @param dy The dy value.
     * @param x The x value.
     * 
     * @return The above pixel.
     */
    public int abovePixel(byte[] buf, int offset, int dy, int x) 
    {
        return offset >= dy ? buf[offset + x - dy] : 0;
    }

    /**
     * Get the above-left pixel from the buffer.
     * 
     * @param buf The buffer.
     * @param offset The offset into the buffer.
     * @param dy The dy value.
     * @param x The x value.
     * 
     * @return The above-left pixel.
     */
    public int aboveLeftPixel(byte[] buf, int offset, int dy, int x) 
    {
        return offset >= dy && x >= getBpp() ? buf[offset + x - dy - getBpp()]
                : 0;
    }
    
    /**
     * Simple helper to print out a buffer.
     * 
     * @param raw The bytes to print out.
     */
    private static void dump(byte[] raw) 
    {
        for (int i = 0; i < raw.length; i++)
        {
            System.out.print(raw[i] + " ");
        }
        System.out.println();
    }

    /**
     * @return Returns the bpp.
     */
    public int getBpp() 
    {
        return bpp;
    }

    /**
     * @param newBpp
     *            The bpp to set.
     */
    public void setBpp(int newBpp) 
    {
        bpp = newBpp;
    }

    /**
     * @return Returns the height.
     */
    public int getHeight() 
    {
        return height;
    }

    /**
     * @param newHeight
     *            The height to set.
     */
    public void setHeight(int newHeight) 
    {
        height = newHeight;
    }

    /**
     * @return Returns the width.
     */
    public int getWidth() 
    {
        return width;
    }

    /**
     * @param newWidth
     *            The width to set.
     */
    public void setWidth(int newWidth) 
    {
        this.width = newWidth;
    }
    

    /**
     * encode a byte array full of image data using the filter that this object
     * implements.
     * 
     * @param src
     *            buffer
     * @param dest
     *            buffer
     */
    public void encode(byte[] src, byte[] dest) 
    {
        checkBufsiz(dest, src);
        int dy = getWidth()*getBpp();
        for (int y = 0; y < height; y++) 
        {
            int yoffset = y * dy;
            encodeLine(src, dest, dy, yoffset, dy, yoffset);
        }
    }

    /**
     * decode a byte array full of image data using the filter that this object
     * implements.
     * 
     * @param src
     *            buffer
     * @param dest
     *            buffer
     */
    public void decode(byte[] src, byte[] dest) 
    {
        checkBufsiz(src, dest);
        int dy = width * bpp;
        for (int y = 0; y < height; y++) 
        {
            int yoffset = y * dy;
            decodeLine(src, dest, dy, yoffset, dy, yoffset);
        }
    }

    /**
     * @param predictor
     *            <ul>
     *            <li>1 No prediction (the default value)
     *            <li>2 TIFF Predictor 2
     *            <li>10 PNG prediction (on encoding, PNG None on all rows)
     *            <li>11 PNG prediction (on encoding, PNG Sub on all rows)
     *            <li>12 PNG prediction (on encoding, PNG Up on all rows)
     *            <li>13 PNG prediction (on encoding, PNG Average on all rows)
     *            <li>14 PNG prediction (on encoding, PNG Paeth on all rows)
     *            <li>15 PNG prediction (on encoding, PNG optimum)
     *            </ul>
     * 
     * @return The predictor class based on the predictor code.
     */
    public static PredictorAlgorithm getFilter(int predictor) 
    {
        PredictorAlgorithm filter;
        switch (predictor) 
        {
            case 10:
                filter = new None();
                break;
            case 11:
                filter = new Sub();
                break;
            case 12:
                filter = new Up();
                break;
            case 13:
                filter = new Average();
                break;
            case 14:
                filter = new Paeth();
                break;
            case 15:
                filter = new Uptimum();
                break;
            default:
                filter = new None();
        }
        return filter;
    }
}
