/*
 * @(#)SEQDeltaFrame.java  1.0  2010-12-25
 *
 * Copyright (c) 2010 Werner Randelshofer, Immensee, Switzerland.
 * All rights reserved.
 *
 * You may not use, copy or modify this file, except in compliance with the
 * license agreement you entered into with Werner Randelshofer.
 * For details see accompanying license terms.
 */
package org.monte.media.seq;

import org.monte.media.image.BitmapImage;
import java.util.Arrays;

/**
 * Represents a delta frame in a movie track.
 * <p>
 * References:<br>
 * <a href="http://www.fileformat.info/format/atari/egff.htm">http://www.fileformat.info/format/atari/egff.htm</a><br>
 * <a href="http://www.atari-forum.com/wiki/index.php/ST_Picture_Formats">http://www.atari-forum.com/wiki/index.php/ST_Picture_Formats</a>
 *
 * @author  Werner Randelshofer, Hausmatt 10, CH-6405 Immensee, Switzerland
 * @version 1.0  2010-12-25 Created.
 */
public class SEQDeltaFrame
        extends SEQFrame {

    private int leftBound, topBound, rightBound, bottomBound;
    public final static int //
            OP_Copy = 0,
            OP_XOR = 1;
    public final static int //
            SM_UNCOMPRESSED = 0,
            SM_COMPRESSED = 1;
    private final static int //
            ENCODING_COPY_UNCOMPRESSED = (OP_Copy << 1) | SM_UNCOMPRESSED,
            ENCODING_COPY_COMPRESSED = (OP_Copy << 1) | SM_COMPRESSED,
            ENCODING_XOR_UNCOMPRESSED = (OP_XOR << 1) | SM_UNCOMPRESSED,
            ENCODING_XOR_COMPRESSED = (OP_XOR << 1) | SM_COMPRESSED;
    /** Wether we already printed a warning about a broken encoding. */
    private boolean isWarningPrinted = false;

    public SEQDeltaFrame() {
    }

    private int getEncoding() {
        return (getOperation() << 1) | getStorageMethod();
    }

    @Override
    public void decode(BitmapImage bitmap, SEQMovieTrack track) {
        switch (getEncoding()) {
            case ENCODING_COPY_UNCOMPRESSED:
                decodeCopyUncompressed(bitmap, track);
                break;
            case ENCODING_COPY_COMPRESSED:
                decodeCopyCompressed(bitmap, track);
                break;
            case ENCODING_XOR_UNCOMPRESSED:
                decodeXORUncompressed(bitmap, track);
                break;
            case ENCODING_XOR_COMPRESSED:
                decodeXORCompressed(bitmap, track);
                break;
            default:
                throw new InternalError("Unsupported encoding." + getEncoding());
        }
    }

    private void decodeCopyUncompressed(BitmapImage bitmap, SEQMovieTrack track) {
    }

    /**
     * Compressed data contains a sequence of control WORDs (16-bit signed WORDs)
     * and data. A control WORD with a value between 1 and 32,767 indicates that
     * the next WORD is to be repeated a number of times equal to the control
     * WORD value. A control WORD with a negative value indicates that a run
     * of bytes equal to the absolute value of the control WORD value is to be
     * read from the compressed data.
     */
    private void decodeCopyCompressed(BitmapImage bitmap, SEQMovieTrack track) {
        int di = 0; // data index
        byte[] screen = bitmap.getBitmap();
        Arrays.fill(screen, (byte) 0);

        int bStride = bitmap.getBitplaneStride();
        int sStride = bitmap.getScanlineStride();
        int x = leftBound; // screen x
        int y = topBound; // screen y
        int shift = x & 0x7;
        int b = 0; // screen bitplane
        int si = y * sStride + x / 8; // screen index
        int width = bitmap.getWidth();

        if (shift == 0) {
            while (di < data.length) {
                int op = (((data[di++] & 0xff) << 8) | ((data[di++] & 0xff))); // opcode
                if ((op & 0x8000) == 0) {
                    // => Repeat the next data word op-times
                    byte d1 = data[di++];
                    byte d2 = data[di++];
                    for (int i = 0; i < op; i++) {
                        screen[si] = d1;
                        if (x < width - 8) {
                            screen[si + 1] = d2;
                        }
                        y++;
                        si += sStride;
                        if (y >= bottomBound) {
                            y = topBound;
                            x = x + 16;
                            if (x >= rightBound) {
                                x = leftBound;
                                y = topBound;
                                b = b + 1;
                            }
                            si = b * bStride + y * sStride + x / 8;
                        }
                    }
                } else {
                    // => Copy the next abs(op) words
                    op = op ^ 0x8000;
                    for (int i = 0; i < op; i++) {
                        byte d1 = data[di++];
                        byte d2 = data[di++];
                        screen[si] = d1;
                        if (x < width - 8) {
                            screen[si + 1] = d2;
                        }
                        y++;
                        si += sStride;
                        if (y >= bottomBound) {
                            y = topBound;
                            x = x + 16;
                            if (x >= rightBound) {
                                x = leftBound;
                                y = topBound;
                                b = b + 1;
                            }
                            si = b * bStride + y * sStride + x / 8;
                        }
                    }
                }
            }
        } else {
            int invShift = 8 - shift;
            int mask = (0xff << shift) & 0xff;
            int invMask = (0xff << invShift) & 0xff;
            int xorInvMask = 0xff >>> shift;
            while (di < data.length) {
                int op = (((data[di++] & 0xff) << 8) | ((data[di++] & 0xff))); // opcode
                if ((op & 0x8000) == 0) {
                    // => Repeat the next data word op-times
                    byte d1 = data[di++];
                    byte d2 = data[di++];
                    byte d3 = (byte) (d2 << invShift);
                    d2 = (byte) (((d1 << invShift) & invMask) | ((d2 & 0xff) >>> shift));
                    d1 = (byte) ((d1 & 0xff) >>> shift);
                    for (int i = 0; i < op; i++) {
                        screen[si] = (byte) ((screen[si] & invMask) | d1);
                        if (x < width - 8) {
                            screen[si + 1] = d2;
                            if (x < width - 16) {
                                screen[si + 2] = (byte) ((screen[si + 2] & xorInvMask) | d3);
                            }
                        }
                        //screen[si + 2] = (byte) (d3);
                        y++;
                        si += sStride;
                        if (y >= bottomBound) {
                            y = topBound;
                            x = x + 16;
                            if (x >= rightBound) {
                                x = leftBound;
                                y = topBound;
                                b = b + 1;
                            }
                            si = b * bStride + y * sStride + x / 8;
                        }
                    }
                } else {
                    // => Copy the next abs(op) words
                    op = op ^ 0x8000;
                    for (int i = 0; i < op; i++) {
                        byte d1 = data[di++];
                        byte d2 = data[di++];
                        byte d3 = (byte) (d2 << invShift);
                        d2 = (byte) (((d1 << invShift) & invMask) | ((d2 & 0xff) >>> shift));
                        d1 = (byte) ((d1 & 0xff) >>> shift);
                        screen[si] = (byte) ((screen[si] & invMask) | d1);
                        if (x < width - 8) {
                            screen[si + 1] = d2;
                            if (x < width - 16) {
                                screen[si + 2] = (byte) ((screen[si + 2] & xorInvMask) | d3);
                            }
                        }
                        y++;
                        si += sStride;
                        if (y >= bottomBound) {
                            y = topBound;
                            x = x + 16;
                            if (x >= rightBound) {
                                x = leftBound;
                                y = topBound;
                                b = b + 1;
                            }
                            si = b * bStride + y * sStride + x / 8;
                        }
                    }
                }
            }
        }
    }

    private void decodeXORUncompressed(BitmapImage bitmap, SEQMovieTrack track) {
    }

    private void decodeXORCompressed(BitmapImage bitmap, SEQMovieTrack track) {
        int di = 0; // data index
        byte[] screen = bitmap.getBitmap();
        int bStride = bitmap.getBitplaneStride();
        int sStride = bitmap.getScanlineStride();
        int x = leftBound; // screen x
        int y = topBound; // screen y
        int shift = x & 0x7;
        int b = 0; // screen bitplane
        int si = y * sStride + x / 8; // screen index
        int width = bitmap.getWidth();

        if (shift == 0) {
            while (di < data.length) {
                int op = (((data[di++] & 0xff) << 8) | ((data[di++] & 0xff))); // opcode
                if ((op & 0x8000) == 0) {
                    // => Repeat the next data word op-times
                    byte d1 = data[di++];
                    byte d2 = data[di++];
                    for (int i = 0; i < op; i++) {
                        screen[si] ^= d1;
                        if (x < width - 8) {
                            screen[si + 1] ^= d2;
                        }
                        y++;
                        si += sStride;
                        if (y >= bottomBound) {
                            y = topBound;
                            x = x + 16;
                            if (x >= rightBound) {
                                x = leftBound;
                                y = topBound;
                                b = b + 1;
                            }
                            si = b * bStride + y * sStride + x / 8;
                        }
                    }
                } else {
                    // => Copy the next abs(op) words
                    op = op ^ 0x8000;
                    for (int i = 0; i < op; i++) {
                        byte d1 = data[di++];
                        byte d2 = data[di++];
                        screen[si] ^= d1;
                        if (x < width - 8) {
                            screen[si + 1] ^= d2;
                        }
                        y++;
                        si += sStride;
                        if (y >= bottomBound) {
                            y = topBound;
                            x = x + 16;
                            if (x >= rightBound) {
                                x = leftBound;
                                y = topBound;
                                b = b + 1;
                            }
                            si = b * bStride + y * sStride + x / 8;
                        }
                    }
                }
            }
        } else {
            int invShift = 8 - shift;
            int mask = (0xff << shift) & 0xff;
            int xorMask = 0xff ^ mask;
            int invMask = (0xff << invShift) & 0xff;
            int xorInvMask = 0xff >>> shift;
            while (di < data.length) {
                int op = (((data[di++] & 0xff) << 8) | ((data[di++] & 0xff))); // opcode
                if ((op & 0x8000) == 0) {
                    // => Repeat the next data word op-times
                    byte d1 = data[di++];
                    byte d2 = data[di++];
                    byte d3 = (byte) (d2 << invShift);
                    d2 = (byte) (((d1 << invShift) & invMask) | ((d2 & 0xff) >>> shift));
                    d1 = (byte) ((d1 & 0xff) >>> shift);
                    for (int i = 0; i < op; i++) {
                        screen[si] = (byte) ((screen[si] & invMask) | ((screen[si] & xorInvMask) ^ d1));
                        if (x < width - 8) {
                            screen[si + 1] ^= d2;
                            if (x < width - 16) {
                                screen[si + 2] = (byte) ((screen[si + 2] & xorInvMask) | ((screen[si + 2] & invMask) ^ d3));
                            }
                        }
                        y++;
                        si += sStride;
                        if (y >= bottomBound) {
                            y = topBound;
                            x = x + 16;
                            if (x >= rightBound) {
                                x = leftBound;
                                y = topBound;
                                b = b + 1;
                            }
                            si = b * bStride + y * sStride + x / 8;
                        }
                    }
                } else {
                    // => Copy the next abs(op) words
                    op = op ^ 0x8000;
                    for (int i = 0; i < op; i++) {
                        byte d1 = data[di++];
                        byte d2 = data[di++];
                        byte d3 = (byte) (d2 << invShift);
                        d2 = (byte) (((d1 << invShift) & invMask) | ((d2 & 0xff) >>> shift));
                        d1 = (byte) ((d1 & 0xff) >>> shift);
                        //screen[si] = (byte) ((screen[si] & mask) | ((screen[si]&xorMask)^d1));
                        screen[si] = (byte) ((screen[si] & invMask) | ((screen[si] & xorInvMask) ^ d1));
                        if (x < width - 8) {
                            screen[si + 1] ^= d2;
                            if (x < width - 16) {
                                screen[si + 2] = (byte) ((screen[si + 2] & xorInvMask) | ((screen[si + 2] & invMask) ^ d3));
                            }
                        }
                        y++;
                        si += sStride;
                        if (y >= bottomBound) {
                            y = topBound;
                            x = x + 16;
                            if (x >= rightBound) {
                                x = leftBound;
                                y = topBound;
                                b = b + 1;
                            }
                            si = b * bStride + y * sStride + x / 8;
                        }
                    }
                }
            }
        }
    }

    public void setBounds(int x, int y, int w, int h) {
        leftBound = x;
        topBound = y;
        rightBound = x + w;
        bottomBound = y + h;
    }

    @Override
    public int getTopBound(SEQMovieTrack track) {
        return topBound;
    }

    @Override
    public int getBottomBound(SEQMovieTrack track) {
        return bottomBound;
    }

    @Override
    public int getLeftBound(SEQMovieTrack track) {
        return leftBound;
    }

    @Override
    public int getRightBound(SEQMovieTrack track) {
        return rightBound;
    }

    /** Returns true if the frame can be decoded over both the previous frame
     * or the subsequent frame. Bidirectional frames can be used efficiently
     * for forward and backward playing a movie.
     * <p>
     * All key frames are bidirectional. Delta frames which use an XOR OP-mode
     * are bidirectional as well.
     */
    @Override
    public boolean isBidirectional() {
        return getOperation() == OP_XOR;
    }
}
