/*
 * $RCSfile: PktEncoder.java,v $
 * $Revision: 1.1 $
 * $Date: 2005-02-11 05:02:03 $
 * $State: Exp $
 *
 * Class:                   PktEncoder
 *
 * Description:             Builds bit stream packets and keeps
 *                          interpacket dependencies.
 *
 *
 *
 * COPYRIGHT:
 *
 * This software module was originally developed by Raphaël Grosbois and
 * Diego Santa Cruz (Swiss Federal Institute of Technology-EPFL); Joel
 * Askelöf (Ericsson Radio Systems AB); and Bertrand Berthelot, David
 * Bouchard, Félix Henry, Gerard Mozelle and Patrice Onno (Canon Research
 * Centre France S.A) in the course of development of the JPEG2000
 * standard as specified by ISO/IEC 15444 (JPEG 2000 Standard). This
 * software module is an implementation of a part of the JPEG 2000
 * Standard. Swiss Federal Institute of Technology-EPFL, Ericsson Radio
 * Systems AB and Canon Research Centre France S.A (collectively JJ2000
 * Partners) agree not to assert against ISO/IEC and users of the JPEG
 * 2000 Standard (Users) any of their rights under the copyright, not
 * including other intellectual property rights, for this software module
 * with respect to the usage by ISO/IEC and Users of this software module
 * or modifications thereof for use in hardware or software products
 * claiming conformance to the JPEG 2000 Standard. Those intending to use
 * this software module in hardware or software products are advised that
 * their use may infringe existing patents. The original developers of
 * this software module, JJ2000 Partners and ISO/IEC assume no liability
 * for use of this software module or modifications thereof. No license
 * or right to this software module is granted for non JPEG 2000 Standard
 * conforming products. JJ2000 Partners have full right to use this
 * software module for his/her own purpose, assign or donate this
 * software module to any third party and to inhibit third parties from
 * using this software module for non JPEG 2000 Standard conforming
 * products. This copyright notice must be included in all copies or
 * derivative works of this software module.
 *
 * Copyright (c) 1999/2000 JJ2000 Partners.
 * */

package jj2000.j2k.codestream.writer;
import java.awt.Point;

import jj2000.j2k.wavelet.analysis.*;
import jj2000.j2k.entropy.encoder.*;
import jj2000.j2k.codestream.*;
import jj2000.j2k.wavelet.*;
import jj2000.j2k.image.*;
import jj2000.j2k.util.*;
import jj2000.j2k.*;

import java.util.Vector;
import com.sun.media.imageioimpl.plugins.jpeg2000.J2KImageWriteParamJava;
/**
 * This class builds packets and keeps the state information of packet
 * interdependencies. It also supports saving the state and reverting
 * (restoring) to the last saved state, with the save() and restore() methods.
 *
 * <P>Each time the encodePacket() method is called a new packet is encoded,
 * the packet header is returned by the method, and the packet body can be
 * obtained with the getLastBodyBuf() and getLastBodyLen() methods.
 * */
public class PktEncoder {

    /** The prefix for packet encoding options: 'P' */
    public final static char OPT_PREFIX = 'P';

    /** The list of parameters that is accepted for packet encoding.*/
    private final static String [][] pinfo = {
        { "Psop", "[<tile idx>] true|false"+
         "[ [<tile idx>] true|false ...]",
          "Specifies whether start of packet (SOP) markers should be used. "+
          "'true' enables, 'false' disables it.","false"},
        { "Peph", "[<tile idx>] true|false"+
         "[ [<tile  idx>] true|false ...]",
          "Specifies whether end of packet header (EPH) markers should be "+
          " used. 'true' enables, 'false' disables it.","false"}
    };

    /** The initial value for the lblock */
    private final static int INIT_LBLOCK = 3;

    /** The source object */
    private CodedCBlkDataSrcEnc infoSrc;

    /** The encoder specs */
    J2KImageWriteParamJava wp;

    /**
     * The tag tree for inclusion information. The indexes are outlined
     * below. Note that the layer indexes start at 1, therefore, the layer
     * index minus 1 is used. The subband indices are used as they are defined
     * in the Subband class. The tile indices start at 0 and follow a
     * lexicographical order.
     *
     * <ul>
     * <li>1st index: tile index, in lexicographical order</li>
     * <li>2nd index: component index </li>
     * <li>3rd index: resolution level </li>
     * <li>4th index: subband index </li>
     * <li>5th index: precinct index </li>
     * </ul>
     **/
    private TagTreeEncoder ttIncl[][][][][];

    /**
     * The tag tree for the maximum significant bit-plane. The indexes are
     * outlined below. Note that the layer indexes start at 1, therefore, the
     * layer index minus 1 is used. The subband indices are used as they are
     * defined in the Subband class. The tile indices start at 0 and follow a
     * lexicographical order.
     *
     * <ul>
     * <li>1st index: tile index, in lexicographical order</li>
     * <li>2nd index: component index </li>
     * <li>3rd index: resolution level </li>
     * <li>4th index: subband index - subband index offset </li>
     * <li>5th index: precinct index </li>
     * </ul>
     * */
    private TagTreeEncoder ttMaxBP[][][][][];

    /**
     * The base number of bits for sending code-block length information
     * (referred as Lblock in the JPEG 2000 standard). The indexes are
     * outlined below. Note that the layer indexes start at 1, therefore, the
     * layer index minus 1 is used. The subband indices are used as they are
     * defined in the Subband class. The tile indices start at 0 and follow a
     * lexicographical order.
     *
     * <ul>
     * <li>1st index: tile index, in lexicographical order </li>
     * <li>2nd index: component index </li>
     * <li>3rd index: resolution level </li>
     * <li>4th index: subband index - subband index offset </li>
     * <li>5th index: code-block index, in lexicographical order</li>
     * </ul>
     * */
    private int lblock[][][][][];

    /**
     * The last encoded truncation point for each code-block. A negative value
     * means that no information has been included for the block, yet. The
     * indexes are outlined below. The subband indices are used as they are
     * defined in the Subband class. The tile indices start at 0 and follow a
     * lexicographical order. The code-block indices follow a lexicographical
     * order within the subband tile.
     *
     * <P>What is actually stored is the index of the element in
     * CBlkRateDistStats.truncIdxs that gives the real truncation point.
     *
     * <ul>
     * <li>1st index: tile index, in lexicographical order </li>
     * <li>2nd index: component index </li>
     * <li>3rd index: resolution level </li>
     * <li>4th index: subband index - subband index offset </li>
     * <li>5th index: code-block index, in lexicographical order </li>
     * </ul>
     *  */
    private int prevtIdxs[][][][][];

    /**
     * The saved base number of bits for sending code-block length
     * information. It is used for restoring previous saved state by
     * restore(). The indexes are outlined below. Note that the layer indexes
     * start at 1, therefore, the layer index minus 1 is used. The subband
     * indices are used as they are defined in the Subband class. The tile
     * indices start at 0 and follow a lexicographical order.
     *
     * <ul>
     * <li>1st index: tile index, in lexicographical order </li>
     * <li>2nd index: component index </li>
     * <li>3rd index: resolution level </li>
     * <li>4th index: subband index - subband index offset </li>
     * <li>5th index: code-block index, in lexicographical order</li>
     * </ul>
     * */
    private int bak_lblock[][][][][];

    /**
     * The saved last encoded truncation point for each code-block. It is used
     * for restoring previous saved state by restore(). A negative value means
     * that no information has been included for the block, yet. The indexes
     * are outlined below. The subband indices are used as they are defined in
     * the Subband class. The tile indices start at 0 and follow a
     * lexicographical order. The code-block indices follow a lexicographical
     * order within the subband tile.
     *
     * <ul>
     * <li>1st index: tile index, in lexicographical order </li>
     * <li>2nd index: component index </li>
     * <li>3rd index: resolution level </li>
     * <li>4th index: subband index - subband index offset </li>
     * <li>5th index: code-block index, in lexicographical order </li>
     * </ul>
     *  */
    private int bak_prevtIdxs[][][][][];

    /** The body buffer of the last encoded packet */
    private byte[] lbbuf;

    /** The body length of the last encoded packet */
    private int lblen;

    /** The saved state */
    private boolean saved;

    /** Whether or not there is ROI information in the last encoded Packet */
    private boolean roiInPkt = false;

    /** Length to read in current packet body to get all the ROI information */
    private int roiLen = 0;

    /**
     * Array containing the coordinates, width, height, indexes, ... of the
     * precincts.
     *
     * <ul>
     * <li> 1st dim: tile index.</li>
     * <li> 2nd dim: component index.</li>
     * <li> 3rd dim: resolution level index.</li>
     * <li> 4th dim: precinct index.</li>
     * </ul> 
     * */
    private PrecInfo ppinfo[][][][];

    /** Whether or not the current packet is writable */
    private boolean packetWritable;

    /**
     * Creates a new packet header encoder, using the information from the
     * 'infoSrc' object. The information used is the number of components,
     * number of tiles, subband decomposition, etc.
     *
     * <P>Note that this constructor visits all the tiles in the 'infoSrc'
     * object. The 'infoSrc' object is left at the original tile (i.e. the
     * current tile before calling this constructor), but any side effects of
     * visiting the tiles is not reverted.
     *
     * @param infoSrc The source of information to construct the
     * object.
     *
     * @param encSpec The parameters for the encoding
     *
     * @param maxNumPrec Maximum number of precinct in each tile, component
     * and resolution level.
     *
     * @param pl ParameterList instance that holds command line options
     * */
    public PktEncoder(CodedCBlkDataSrcEnc infoSrc, J2KImageWriteParamJava wp,
                      Point[][][] numPrec) {
        this.infoSrc = infoSrc;
        this.wp = wp;
//        this.numPrec = numPrec;

        // Get number of components and tiles
        int nc = infoSrc.getNumComps();
        int nt = infoSrc.getNumTiles();

        // Do initial allocation
        ttIncl = new TagTreeEncoder[nt][nc][][][];
        ttMaxBP = new TagTreeEncoder[nt][nc][][][];
        lblock = new int[nt][nc][][][];
        prevtIdxs = new int[nt][nc][][][];
        ppinfo = new PrecInfo[nt][nc][][];

        // Finish allocation
        SubbandAn root,sb;
        int maxs,mins;
        int mrl;
        Point tmpCoord = null;
        int numcb;       // Number of code-blocks
        Vector cblks = null;
        infoSrc.setTile(0,0);
        for (int t=0; t<nt; t++) { // Loop on tiles
            for (int c=0; c<nc; c++) { // Loop on components
                // Get number of resolution levels
                root = infoSrc.getAnSubbandTree(t,c);
                mrl = root.resLvl;

                lblock[t][c] = new int[mrl+1][][];
                ttIncl[t][c] = new TagTreeEncoder[mrl+1][][];
                ttMaxBP[t][c] = new TagTreeEncoder[mrl+1][][];
                prevtIdxs[t][c] = new int[mrl+1][][];
                ppinfo[t][c] = new PrecInfo[mrl+1][];

                for(int r=0; r<=mrl; r++) { // Loop on resolution levels
                    mins = (r==0) ? 0 : 1;
                    maxs = (r==0) ? 1 : 4;

                    int maxPrec = numPrec[t][c][r].x*numPrec[t][c][r].y;

                    ttIncl[t][c][r] = new TagTreeEncoder[maxPrec][maxs];
                    ttMaxBP[t][c][r] = new TagTreeEncoder[maxPrec][maxs];
                    prevtIdxs[t][c][r] = new int[maxs][];
                    lblock[t][c][r] = new int[maxs][];

                    // Precincts and code-blocks
                    ppinfo[t][c][r] = new PrecInfo[maxPrec];
                    fillPrecInfo(t,c,r);

                    for(int s=mins; s<maxs; s++) {
                        // Loop on subbands
                        sb = (SubbandAn)root.getSubbandByIdx(r,s);
                        numcb = sb.numCb.x*sb.numCb.y;

                        lblock[t][c][r][s] = new int[numcb];
                        ArrayUtil.intArraySet(lblock[t][c][r][s],INIT_LBLOCK);

                        prevtIdxs[t][c][r][s] = new int[numcb];
                        ArrayUtil.intArraySet(prevtIdxs[t][c][r][s],-1);
                    }
                }
            }
            if(t!=nt-1) infoSrc.nextTile();
        }
    }


    /** 
     * Retrives precincts and code-blocks coordinates in the given resolution,
     * component and tile. It terminates TagTreeEncoder initialization as
     * well.
     *
     * @param t Tile index.
     *
     * @param c Component index.
     *
     * @param r Resolution level index.
     * */
    private void fillPrecInfo(int t,int c,int r) {
        if(ppinfo[t][c][r].length==0) return; // No precinct in this
        // resolution level

        Point tileI = infoSrc.getTile(null);
        Point nTiles = infoSrc.getNumTiles(null);

        int x0siz = infoSrc.getImgULX();
        int y0siz = infoSrc.getImgULY();
        int xsiz = x0siz + infoSrc.getImgWidth();
        int ysiz = y0siz + infoSrc.getImgHeight();
        int xt0siz = infoSrc.getTilePartULX();
        int yt0siz = infoSrc.getTilePartULY();
        int xtsiz = infoSrc.getNomTileWidth();
        int ytsiz = infoSrc.getNomTileHeight();

        int tx0 = (tileI.x==0) ? x0siz : xt0siz+tileI.x*xtsiz;
        int ty0 = (tileI.y==0) ? y0siz : yt0siz+tileI.y*ytsiz;
        int tx1 = (tileI.x!=nTiles.x-1) ? xt0siz+(tileI.x+1)*xtsiz : xsiz;
        int ty1 = (tileI.y!=nTiles.y-1) ? yt0siz+(tileI.y+1)*ytsiz : ysiz;

        int xrsiz = infoSrc.getCompSubsX(c);
        int yrsiz = infoSrc.getCompSubsY(c);

        int tcx0 = (int)Math.ceil(tx0/(double)(xrsiz));
        int tcy0 = (int)Math.ceil(ty0/(double)(yrsiz));
        int tcx1 = (int)Math.ceil(tx1/(double)(xrsiz));
        int tcy1 = (int)Math.ceil(ty1/(double)(yrsiz));

        int ndl = infoSrc.getAnSubbandTree(t,c).resLvl-r;
        int trx0 = (int)Math.ceil(tcx0/(double)(1<<ndl));
        int try0 = (int)Math.ceil(tcy0/(double)(1<<ndl));
        int trx1 = (int)Math.ceil(tcx1/(double)(1<<ndl));
        int try1 = (int)Math.ceil(tcy1/(double)(1<<ndl));

        int cb0x = infoSrc.getCbULX();
        int cb0y = infoSrc.getCbULY();

        double twoppx = (double)wp.getPrecinctPartition().getPPX(t,c,r);
        double twoppy = (double)wp.getPrecinctPartition().getPPY(t,c,r);
        int twoppx2 = (int)(twoppx/2);
        int twoppy2 = (int)(twoppy/2);

        // Precincts are located at (cb0x+i*twoppx,cb0y+j*twoppy)
        // Valid precincts are those which intersect with the current
        // resolution level
        int maxPrec = ppinfo[t][c][r].length;
        int nPrec = 0;

        int istart = (int)Math.floor((try0-cb0y)/twoppy);
        int iend = (int)Math.floor((try1-1-cb0y)/twoppy);
        int jstart = (int)Math.floor((trx0-cb0x)/twoppx);
        int jend = (int)Math.floor((trx1-1-cb0x)/twoppx);

        int acb0x,acb0y;

        SubbandAn root = infoSrc.getAnSubbandTree(t,c);
        SubbandAn sb = null;

        int p0x,p0y,p1x,p1y; // Precinct projection in subband
        int s0x,s0y,s1x,s1y; // Active subband portion
        int cw,ch;
        int kstart,kend,lstart,lend,k0,l0;
        int prg_ulx,prg_uly;
        int prg_w = (int)twoppx<<ndl;
        int prg_h = (int)twoppy<<ndl;

        CBlkCoordInfo cb;

        for(int i=istart; i<=iend; i++) { // Vertical precincts
            for(int j=jstart; j<=jend; j++,nPrec++) { // Horizontal precincts
                if(j==jstart && (trx0-cb0x)%(xrsiz*((int)twoppx))!=0) {
                    prg_ulx = tx0;
                } else {
                    prg_ulx = cb0x+j*xrsiz*((int)twoppx<<ndl);
                }
                if(i==istart && (try0-cb0y)%(yrsiz*((int)twoppy))!=0) {
                    prg_uly = ty0;
                } else {
                    prg_uly = cb0y+i*yrsiz*((int)twoppy<<ndl);
                }

                ppinfo[t][c][r][nPrec] =
                    new PrecInfo(r,(int)(cb0x+j*twoppx),(int)(cb0y+i*twoppy),
                                 (int)twoppx,(int)twoppy,
                                 prg_ulx,prg_uly,prg_w,prg_h);

                if(r==0) { // LL subband
                    acb0x = cb0x;
                    acb0y = cb0y;

                    p0x = acb0x+j*(int)twoppx;
                    p1x = p0x + (int)twoppx;
                    p0y = acb0y+i*(int)twoppy;
                    p1y = p0y + (int)twoppy;

                    sb = (SubbandAn)root.getSubbandByIdx(0,0);
                    s0x = (p0x<sb.ulcx) ? sb.ulcx : p0x;
                    s1x = (p1x>sb.ulcx+sb.w) ? sb.ulcx+sb.w : p1x;
                    s0y = (p0y<sb.ulcy) ? sb.ulcy : p0y;
                    s1y = (p1y>sb.ulcy+sb.h) ? sb.ulcy+sb.h : p1y;

                    // Code-blocks are located at (acb0x+k*cw,acb0y+l*ch)
                    cw = sb.nomCBlkW;
                    ch = sb.nomCBlkH;
                    k0 = (int)Math.floor((sb.ulcy-acb0y)/(double)ch);
                    kstart = (int)Math.floor((s0y-acb0y)/(double)ch);
                    kend = (int)Math.floor((s1y-1-acb0y)/(double)ch);
                    l0 = (int)Math.floor((sb.ulcx-acb0x)/(double)cw);
                    lstart = (int)Math.floor((s0x-acb0x)/(double)cw);
                    lend = (int)Math.floor((s1x-1-acb0x)/(double)cw);

                    if(s1x-s0x<=0 || s1y-s0y<=0) {
                        ppinfo[t][c][r][nPrec].nblk[0] = 0;
                        ttIncl[t][c][r][nPrec][0] = new TagTreeEncoder(0,0);
                        ttMaxBP[t][c][r][nPrec][0] = new TagTreeEncoder(0,0);
                    } else {
                        ttIncl[t][c][r][nPrec][0] =
                            new TagTreeEncoder(kend-kstart+1,lend-lstart+1);
                        ttMaxBP[t][c][r][nPrec][0] =
                            new TagTreeEncoder(kend-kstart+1,lend-lstart+1);
                        ppinfo[t][c][r][nPrec].cblk[0] =
                            new CBlkCoordInfo[kend-kstart+1][lend-lstart+1];
                        ppinfo[t][c][r][nPrec].
                            nblk[0] = (kend-kstart+1)*(lend-lstart+1);

                        for(int k=kstart; k<=kend; k++) { // Vertical cblks
                            for(int l=lstart; l<=lend; l++) { // Horiz. cblks

                               cb = new CBlkCoordInfo(k-k0,l-l0);
                                ppinfo[t][c][r][nPrec].
                                    cblk[0][k-kstart][l-lstart] = cb;
                            } // Horizontal code-blocks
                        } // Vertical code-blocks
                    }
                } else { // HL, LH and HH subbands
                    // HL subband
                    acb0x = 0;
                    acb0y = cb0y;

                    p0x = acb0x+j*twoppx2;
                    p1x = p0x + twoppx2;
                    p0y = acb0y+i*twoppy2;
                    p1y = p0y + twoppy2;

                    sb = (SubbandAn)root.getSubbandByIdx(r,1);
                    s0x = (p0x<sb.ulcx) ? sb.ulcx : p0x;
                    s1x = (p1x>sb.ulcx+sb.w) ? sb.ulcx+sb.w : p1x;
                    s0y = (p0y<sb.ulcy) ? sb.ulcy : p0y;
                    s1y = (p1y>sb.ulcy+sb.h) ? sb.ulcy+sb.h : p1y;

                    // Code-blocks are located at (acb0x+k*cw,acb0y+l*ch)
                    cw = sb.nomCBlkW;
                    ch = sb.nomCBlkH;
                    k0 = (int)Math.floor((sb.ulcy-acb0y)/(double)ch);
                    kstart = (int)Math.floor((s0y-acb0y)/(double)ch);
                    kend = (int)Math.floor((s1y-1-acb0y)/(double)ch);
                    l0 = (int)Math.floor((sb.ulcx-acb0x)/(double)cw);
                    lstart = (int)Math.floor((s0x-acb0x)/(double)cw);
                    lend = (int)Math.floor((s1x-1-acb0x)/(double)cw);

                    if(s1x-s0x<=0 || s1y-s0y<=0) {
                        ppinfo[t][c][r][nPrec].nblk[1] = 0;
                        ttIncl[t][c][r][nPrec][1] = new TagTreeEncoder(0,0);
                        ttMaxBP[t][c][r][nPrec][1] = new TagTreeEncoder(0,0);
                    } else {
                        ttIncl[t][c][r][nPrec][1] =
                            new TagTreeEncoder(kend-kstart+1,lend-lstart+1);
                        ttMaxBP[t][c][r][nPrec][1] =
                            new TagTreeEncoder(kend-kstart+1,lend-lstart+1);
                        ppinfo[t][c][r][nPrec].cblk[1] =
                            new CBlkCoordInfo[kend-kstart+1][lend-lstart+1];
                        ppinfo[t][c][r][nPrec].
                            nblk[1] = (kend-kstart+1)*(lend-lstart+1);

                        for(int k=kstart; k<=kend; k++) { // Vertical cblks
                            for(int l=lstart; l<=lend; l++) { // Horiz. cblks
                                cb = new CBlkCoordInfo(k-k0,l-l0);
                                ppinfo[t][c][r][nPrec].
                                    cblk[1][k-kstart][l-lstart] = cb;
                            } // Horizontal code-blocks
                        } // Vertical code-blocks
                    }

                    // LH subband
                    acb0x = cb0x;
                    acb0y = 0;

                    p0x = acb0x+j*twoppx2;
                    p1x = p0x + twoppx2;
                    p0y = acb0y+i*twoppy2;
                    p1y = p0y + twoppy2;

                    sb = (SubbandAn)root.getSubbandByIdx(r,2);
                    s0x = (p0x<sb.ulcx) ? sb.ulcx : p0x;
                    s1x = (p1x>sb.ulcx+sb.w) ? sb.ulcx+sb.w : p1x;
                    s0y = (p0y<sb.ulcy) ? sb.ulcy : p0y;
                    s1y = (p1y>sb.ulcy+sb.h) ? sb.ulcy+sb.h : p1y;

                    // Code-blocks are located at (acb0x+k*cw,acb0y+l*ch)
                    cw = sb.nomCBlkW;
                    ch = sb.nomCBlkH;
                    k0 = (int)Math.floor((sb.ulcy-acb0y)/(double)ch);
                    kstart = (int)Math.floor((s0y-acb0y)/(double)ch);
                    kend = (int)Math.floor((s1y-1-acb0y)/(double)ch);
                    l0 = (int)Math.floor((sb.ulcx-acb0x)/(double)cw);
                    lstart = (int)Math.floor((s0x-acb0x)/(double)cw);
                    lend = (int)Math.floor((s1x-1-acb0x)/(double)cw);

                    if(s1x-s0x<=0 || s1y-s0y<=0) {
                        ppinfo[t][c][r][nPrec].nblk[2] = 0;
                        ttIncl[t][c][r][nPrec][2] = new TagTreeEncoder(0,0);
                        ttMaxBP[t][c][r][nPrec][2] = new TagTreeEncoder(0,0);
                    } else {
                        ttIncl[t][c][r][nPrec][2] =
                            new TagTreeEncoder(kend-kstart+1,lend-lstart+1);
                        ttMaxBP[t][c][r][nPrec][2] =
                            new TagTreeEncoder(kend-kstart+1,lend-lstart+1);
                        ppinfo[t][c][r][nPrec].cblk[2] =
                            new CBlkCoordInfo[kend-kstart+1][lend-lstart+1];
                        ppinfo[t][c][r][nPrec].
                            nblk[2] = (kend-kstart+1)*(lend-lstart+1);

                        for(int k=kstart; k<=kend; k++) { // Vertical cblks
                            for(int l=lstart; l<=lend; l++) { // Horiz cblks
                                cb = new CBlkCoordInfo(k-k0,l-l0);
                                ppinfo[t][c][r][nPrec].
                                    cblk[2][k-kstart][l-lstart] = cb;
                            } // Horizontal code-blocks
                        } // Vertical code-blocks
                    }

                    // HH subband
                    acb0x = 0;
                    acb0y = 0;

                    p0x = acb0x+j*twoppx2;
                    p1x = p0x + twoppx2;
                    p0y = acb0y+i*twoppy2;
                    p1y = p0y + twoppy2;

                    sb = (SubbandAn)root.getSubbandByIdx(r,3);
                    s0x = (p0x<sb.ulcx) ? sb.ulcx : p0x;
                    s1x = (p1x>sb.ulcx+sb.w) ? sb.ulcx+sb.w : p1x;
                    s0y = (p0y<sb.ulcy) ? sb.ulcy : p0y;
                    s1y = (p1y>sb.ulcy+sb.h) ? sb.ulcy+sb.h : p1y;

                    // Code-blocks are located at (acb0x+k*cw,acb0y+l*ch)
                    cw = sb.nomCBlkW;
                    ch = sb.nomCBlkH;
                    k0 = (int)Math.floor((sb.ulcy-acb0y)/(double)ch);
                    kstart = (int)Math.floor((s0y-acb0y)/(double)ch);
                    kend = (int)Math.floor((s1y-1-acb0y)/(double)ch);
                    l0 = (int)Math.floor((sb.ulcx-acb0x)/(double)cw);
                    lstart = (int)Math.floor((s0x-acb0x)/(double)cw);
                    lend = (int)Math.floor((s1x-1-acb0x)/(double)cw);

                    if(s1x-s0x<=0 || s1y-s0y<=0) {
                        ppinfo[t][c][r][nPrec].nblk[3] = 0;
                        ttIncl[t][c][r][nPrec][3] = new TagTreeEncoder(0,0);
                        ttMaxBP[t][c][r][nPrec][3] = new TagTreeEncoder(0,0);
                    } else {
                        ttIncl[t][c][r][nPrec][3] =
                            new TagTreeEncoder(kend-kstart+1,lend-lstart+1);
                        ttMaxBP[t][c][r][nPrec][3] =
                            new TagTreeEncoder(kend-kstart+1,lend-lstart+1);
                        ppinfo[t][c][r][nPrec].cblk[3] =
                            new CBlkCoordInfo[kend-kstart+1][lend-lstart+1];
                        ppinfo[t][c][r][nPrec].
                            nblk[3] = (kend-kstart+1)*(lend-lstart+1);

                        for(int k=kstart; k<=kend; k++) { // Vertical cblks
                            for(int l=lstart; l<=lend; l++) { // Horiz cblks
                                cb = new CBlkCoordInfo(k-k0,l-l0);
                                ppinfo[t][c][r][nPrec].
                                    cblk[3][k-kstart][l-lstart] = cb;
                            } // Horizontal code-blocks
                        } // Vertical code-blocks
                    }

                }
            } // Horizontal precincts
        } // Vertical precincts
   }


    /**
     * Encodes a packet and returns the buffer containing the encoded packet
     * header. The code-blocks appear in a 3D array of CBlkRateDistStats,
     * 'cbs'. The first index is the tile index in lexicographical order, the
     * second index is the subband index (as defined in the Subband class),
     * and the third index is the code-block index (whithin the subband tile)
     * in lexicographical order as well. The indexes of the new truncation
     * points for each code-block are specified by the 3D array of int
     * 'tIndx'. The indices of this array are the same as for cbs. The
     * truncation point indices in 'tIndx' are the indices of the elements of
     * the 'truncIdxs' array, of the CBlkRateDistStats class, that give the
     * real truncation points. If a truncation point index is negative it
     * means that the code-block has not been included in any layer yet. If
     * the truncation point is less than or equal to the highest truncation
     * point used in previous layers then the code-block is not included in
     * the packet. Otherwise, if larger, the code-block is included in the
     * packet. The body of the packet can be obtained with the
     * getLastBodyBuf() and getLastBodyLen() methods.
     *
     * <p>Layers must be coded in increasing order, in consecutive manner, for
     * each tile, component and resolution level (e.g., layer 1, then layer 2,
     * etc.). For different tile, component and/or resolution level no
     * particular order must be followed.</p>
     *
     * @param ly The layer index (starts at 1).
     *
     * @param c The component index.
     *
     * @param r The resolution level
     *
     * @param t Index of the current tile
     *
     * @param cbs The 3D array of coded code-blocks.
     *
     * @param tIndx The truncation point indices for each code-block.
     *
     * @param hbuf The header buffer. If null a new BitOutputBuffer is created
     * and returned. This buffer is reset before anything is written to it.
     *
     * @param bbuf The body buffer. If null a new one is created. If not large
     * enough a new one is created.
     *
     * @param pIdx The precinct index.
     *
     * @return The buffer containing the packet header.
     * */
    public BitOutputBuffer encodePacket(int ly,int c,int r,int t,
                                        CBlkRateDistStats cbs[][],
                                        int tIndx[][],BitOutputBuffer hbuf,
                                        byte bbuf[],int pIdx) {
        int b,i,maxi;
        int ncb;
        int thmax;
        int newtp;
        int cblen;
        int prednbits,nbits,deltabits;
        TagTreeEncoder cur_ttIncl,cur_ttMaxBP; // inclusion and bit-depth tag
        // trees 
        int cur_prevtIdxs[]; // last encoded truncation points
        CBlkRateDistStats cur_cbs[];
        int cur_tIndx[]; // truncation points to encode
        int minsb = (r==0) ? 0 : 1;
        int maxsb = (r==0) ? 1 : 4;
        Point cbCoord = null;
        SubbandAn root = infoSrc.getAnSubbandTree(t,c);
        SubbandAn sb;
        roiInPkt = false;
        roiLen = 0;
        int mend,nend;

        // Checks if a precinct with such an index exists in this resolution
        // level
        if(pIdx>=ppinfo[t][c][r].length) {
            packetWritable = false;
            return hbuf;
        }
        PrecInfo prec = ppinfo[t][c][r][pIdx];

        // First, we check if packet is empty (i.e precinct 'pIdx' has no
        // code-block in any of the subbands)
        boolean isPrecVoid = true;

        for(int s=minsb; s<maxsb; s++) {
            if(prec.nblk[s]==0) {
                // The precinct has no code-block in this subband.
                continue;
            } else {
                // The precinct is not empty in at least one subband ->
                // stop
                isPrecVoid = false;
                break;
            }
        }

        if(isPrecVoid) {
            packetWritable = true;

            if(hbuf == null) {
                hbuf = new BitOutputBuffer();
            } else {
                hbuf.reset();
            }
            if (bbuf == null) {
                lbbuf = bbuf = new byte[1];
            }
            hbuf.writeBit(0);
            lblen = 0;

            return hbuf;
        }

        if(hbuf == null) {
            hbuf = new BitOutputBuffer();
        } else {
            hbuf.reset();
        }

        // Invalidate last body buffer
        lbbuf = null;
        lblen = 0;

        // Signal that packet is present
        hbuf.writeBit(1);

        for(int s=minsb; s<maxsb; s++) { // Loop on subbands
            sb = (SubbandAn)root.getSubbandByIdx(r,s);

            // Go directly to next subband if the precinct has no code-block
            // in the current one.
            if(prec.nblk[s]==0) {
                continue;
            }

            cur_ttIncl = ttIncl[t][c][r][pIdx][s];
            cur_ttMaxBP = ttMaxBP[t][c][r][pIdx][s];
            cur_prevtIdxs = prevtIdxs[t][c][r][s];
            cur_cbs = cbs[s];
            cur_tIndx = tIndx[s];

            // Set tag tree values for code-blocks in this precinct
            mend = (prec.cblk[s]==null) ? 0 : prec.cblk[s].length;
            for(int m=0; m<mend; m++) {
                nend = (prec.cblk[s][m]==null) ? 0 : prec.cblk[s][m].length;
                for (int n=0; n<nend; n++) {
                    cbCoord = prec.cblk[s][m][n].idx;
                    b = cbCoord.x+cbCoord.y*sb.numCb.x;

                    if (cur_tIndx[b]>cur_prevtIdxs[b] && cur_prevtIdxs[b]<0) {
                        // First inclusion
                        cur_ttIncl.setValue(m,n,ly-1);
                    }
                    if (ly==1) { // First layer, need to set the skip of MSBP
                        cur_ttMaxBP.setValue(m,n,cur_cbs[b].skipMSBP);
                    }
                }
            }

            // Now encode the information
            for(int m=0; m<prec.cblk[s].length; m++) { // Vertical code-blocks
                for (int n=0; n<prec.cblk[s][m].length; n++) { // Horiz. cblks
                    cbCoord = prec.cblk[s][m][n].idx;
                    b = cbCoord.x+cbCoord.y*sb.numCb.x;

                    // 1) Inclusion information
                    if (cur_tIndx[b]>cur_prevtIdxs[b]) {
                        // Code-block included in this layer
                        if (cur_prevtIdxs[b]<0) { // First inclusion
                            // Encode layer info
                            cur_ttIncl.encode(m,n,ly,hbuf);

                            // 2) Max bitdepth info. Encode value
                            thmax = cur_cbs[b].skipMSBP+1;
                            for (i=1; i<=thmax; i++) {
                                cur_ttMaxBP.encode(m,n,i,hbuf);
                            }

                            // Count body size for packet
                            lblen += cur_cbs[b].
                                truncRates[cur_cbs[b].truncIdxs[cur_tIndx[b]]];
                        } else { // Already in previous layer
                            // Send "1" bit
                            hbuf.writeBit(1);
                            // Count body size for packet
                            lblen +=
                                cur_cbs[b].
                                truncRates[cur_cbs[b].
                                          truncIdxs[cur_tIndx[b]]] -
                                cur_cbs[b].
                                truncRates[cur_cbs[b].
                                          truncIdxs[cur_prevtIdxs[b]]];
                        }

                        // 3) Truncation point information
                        if (cur_prevtIdxs[b]<0) {
                            newtp = cur_cbs[b].truncIdxs[cur_tIndx[b]];
                        } else {
                            newtp = cur_cbs[b].truncIdxs[cur_tIndx[b]]-
                                cur_cbs[b].truncIdxs[cur_prevtIdxs[b]]-1;
                        }

                        // Mix of switch and if is faster
                        switch (newtp) {
                        case 0:
                            hbuf.writeBit(0); // Send one "0" bit
                            break;
                        case 1:
                            hbuf.writeBits(2,2); // Send one "1" and one "0"
                            break;
                        case 2:
                        case 3:
                        case 4:
                            // Send two "1" bits followed by 2 bits
                            // representation of newtp-2
                            hbuf.writeBits((3<<2)|(newtp-2),4);
                            break;
                        default:
                            if (newtp <= 35) {
                                // Send four "1" bits followed by a five bits
                                // representation of newtp-5
                                hbuf.writeBits((15<<5)|(newtp-5),9);
                            } else if (newtp <= 163) {
                                // Send nine "1" bits followed by a seven bits
                                // representation of newtp-36
                                hbuf.writeBits((511<<7)|(newtp-36),16);
                            } else {
                                throw new
                                    ArithmeticException("Maximum number "+
                                                        "of truncation "+
                                                        "points exceeded");
                            }
                        }
                    } else { // Block not included in this layer
                        if (cur_prevtIdxs[b]>=0) {
                            // Already in previous layer. Send "0" bit
                            hbuf.writeBit(0);
                        } else { // Not in any previous layers
                            cur_ttIncl.encode(m,n,ly,hbuf);
                        }
                        // Go to the next one.
                        continue;
                    }

                    // Code-block length

                    // We need to compute the maximum number of bits needed to
                    // signal the length of each terminated segment and the
                    // final truncation point.
                    newtp = 1;
                    maxi = cur_cbs[b].truncIdxs[cur_tIndx[b]];
                    cblen = (cur_prevtIdxs[b]<0) ? 0 :
                        cur_cbs[b].truncRates[cur_cbs[b].
                                             truncIdxs[cur_prevtIdxs[b]]];

                    // Loop on truncation points
                    i = (cur_prevtIdxs[b]<0) ? 0 :
                        cur_cbs[b].truncIdxs[cur_prevtIdxs[b]]+1;
                    int minbits = 0;
                    for (; i<maxi; i++, newtp++) {
                        // If terminated truncation point calculate length
                        if (cur_cbs[b].isTermPass != null &&
                            cur_cbs[b].isTermPass[i]) {

                            // Calculate length
                            cblen = cur_cbs[b].truncRates[i] - cblen;

                            // Calculate number of needed bits
                            prednbits = lblock[t][c][r][s][b] +
                                MathUtil.log2(newtp);
                            minbits = ((cblen>0) ? MathUtil.log2(cblen) : 0)+1;

                            // Update Lblock increment if needed
                            for(int j=prednbits; j<minbits; j++) {
                                lblock[t][c][r][s][b]++;
                                hbuf.writeBit(1);
                            }
                            // Initialize for next length
                            newtp = 0;
                            cblen = cur_cbs[b].truncRates[i];
                        }
                    }
                    // Last truncation point length always sent

                    // Calculate length
                    cblen = cur_cbs[b].truncRates[i] - cblen;

                    // Calculate number of bits
                    prednbits = lblock[t][c][r][s][b] + MathUtil.log2(newtp);
                    minbits = ((cblen>0) ? MathUtil.log2(cblen) : 0)+1;
                    // Update Lblock increment if needed
                    for(int j=prednbits; j<minbits; j++) {
                        lblock[t][c][r][s][b]++;
                        hbuf.writeBit(1);
                    }

                    // End of comma-code increment
                    hbuf.writeBit(0);

                    // There can be terminated several segments, send length
                    // info for all terminated truncation points in addition
                    // to final one
                    newtp = 1;
                    maxi = cur_cbs[b].truncIdxs[cur_tIndx[b]];
                    cblen = (cur_prevtIdxs[b]<0) ? 0 :
                        cur_cbs[b].truncRates[cur_cbs[b].
                                             truncIdxs[cur_prevtIdxs[b]]];
                    // Loop on truncation points and count the groups
                    i = (cur_prevtIdxs[b]<0) ? 0 :
                        cur_cbs[b].truncIdxs[cur_prevtIdxs[b]]+1;
                    for (; i<maxi; i++, newtp++) {
                        // If terminated truncation point, send length
                        if (cur_cbs[b].isTermPass != null &&
                            cur_cbs[b].isTermPass[i]) {

                            cblen = cur_cbs[b].truncRates[i] - cblen;
                            nbits = MathUtil.log2(newtp)+lblock[t][c][r][s][b];
                            hbuf.writeBits(cblen,nbits);

                            // Initialize for next length
                            newtp = 0;
                            cblen = cur_cbs[b].truncRates[i];
                        }
                    }
                    // Last truncation point length is always signalled
                    // First calculate number of bits needed to signal
                    // Calculate length
                    cblen = cur_cbs[b].truncRates[i] - cblen;
                    nbits = MathUtil.log2(newtp) + lblock[t][c][r][s][b];
                    hbuf.writeBits(cblen,nbits);

                } // End loop on horizontal code-blocks
            } // End loop on vertical code-blocks
        } // End loop on subband

        // -> Copy the data to the body buffer

        // Ensure size for body data
        if (bbuf==null || bbuf.length<lblen){
            bbuf = new byte[lblen];
        }
        lbbuf = bbuf;
        lblen = 0;

        for (int s=minsb; s<maxsb; s++) { // Loop on subbands
            sb = (SubbandAn)root.getSubbandByIdx(r,s);

            cur_prevtIdxs = prevtIdxs[t][c][r][s];
            cur_cbs = cbs[s];
            cur_tIndx = tIndx[s];
            ncb = cur_prevtIdxs.length;

            mend = (prec.cblk[s]==null) ? 0 : prec.cblk[s].length;
            for(int m=0; m<mend; m++) { // Vertical code-blocks
                nend = (prec.cblk[s][m]==null) ? 0 : prec.cblk[s][m].length;
                for (int n=0; n<nend; n++) { // Horiz. cblks
                    cbCoord = prec.cblk[s][m][n].idx;
                    b = cbCoord.x+cbCoord.y*sb.numCb.x;

                    if (cur_tIndx[b]>cur_prevtIdxs[b]) {

                        // Block included in this precinct -> Copy data to
                        // body buffer and get code-size
                        if (cur_prevtIdxs[b]<0) {
                            cblen = cur_cbs[b].
                                truncRates[cur_cbs[b].truncIdxs[cur_tIndx[b]]];
                            System.arraycopy(cur_cbs[b].data,0,
                                             lbbuf,lblen,cblen);
                        } else {
                            cblen = cur_cbs[b].
                                truncRates[cur_cbs[b].
                                          truncIdxs[cur_tIndx[b]]] -
                                cur_cbs[b].
                                truncRates[cur_cbs[b].
                                          truncIdxs[cur_prevtIdxs[b]]];
                            System.
                                arraycopy(cur_cbs[b].data,
                                          cur_cbs[b].
                                          truncRates[cur_cbs[b].
                                                    truncIdxs[cur_prevtIdxs
                                                             [b]]],
                                          lbbuf,lblen,cblen);
                        }
                        lblen += cblen;

                        // Verifies if this code-block contains new ROI
                        // information
                        if(cur_cbs[b].nROIcoeff!=0 &&
                           (cur_prevtIdxs[b]==-1 ||
                            cur_cbs[b].truncIdxs[cur_prevtIdxs[b]] <=
                            cur_cbs[b].nROIcp-1) ) {
                            roiInPkt = true;
                            roiLen = lblen;
                        }

                        // Update truncation point
                        cur_prevtIdxs[b] = cur_tIndx[b];
                    }
                } // End loop on horizontal code-blocks
            } // End loop on vertical code-blocks
        } // End loop on subbands

        packetWritable = true;

        // Must never happen
        if(hbuf.getLength()==0) {
            throw new Error("You have found a bug in PktEncoder, method:"+
                            " encodePacket");
        }

        return hbuf;
    }

    /**
     * Returns the buffer of the body of the last encoded packet. The length
     * of the body can be retrieved with the getLastBodyLen() method. The
     * length of the array returned by this method may be larger than the
     * actual body length.
     *
     * @return The buffer of body of the last encoded packet.
     *
     * @exception IllegalArgumentException If no packet has been coded since
     * last reset(), last restore(), or object creation.
     *
     * @see #getLastBodyLen
     * */
    public byte[] getLastBodyBuf() {
        if (lbbuf == null) {
            throw new IllegalArgumentException();
        }
        return lbbuf;
    }

    /**
     * Returns the length of the body of the last encoded packet, in
     * bytes. The body itself can be retrieved with the getLastBodyBuf()
     * method.
     *
     * @return The length of the body of last encoded packet, in bytes.
     *
     * @see #getLastBodyBuf
     * */
    public int getLastBodyLen() {
        return lblen;
    }

    /**
     * Saves the current state of this object. The last saved state
     * can be restored with the restore() method.
     *
     * @see #restore
     * */
    public void save() {
        int maxsbi,minsbi;

        // Have we done any save yet?
        if (bak_lblock==null) {
            // Allocate backup buffers
            bak_lblock = new int[ttIncl.length][][][][];
            bak_prevtIdxs = new int[ttIncl.length][][][][];
            for (int t=ttIncl.length-1; t>=0; t--) {
                bak_lblock[t] = new int[ttIncl[t].length][][][];
                bak_prevtIdxs[t] = new int[ttIncl[t].length][][][];
                for (int c=ttIncl[t].length-1; c>=0; c--) {
                    bak_lblock[t][c] = new int[lblock[t][c].length][][];
                    bak_prevtIdxs[t][c] = new int[ttIncl[t][c].length][][];
                    for (int r=lblock[t][c].length-1; r>=0; r--) {
                        bak_lblock[t][c][r] =
                            new int[lblock[t][c][r].length][];
                        bak_prevtIdxs[t][c][r] =
                            new int[prevtIdxs[t][c][r].length][];
                        minsbi = (r==0) ? 0 : 1;
                        maxsbi = (r==0) ? 1 : 4;
                        for (int s=minsbi; s<maxsbi; s++) {
                            bak_lblock[t][c][r][s] =
                                new int[lblock[t][c][r][s].length];
                            bak_prevtIdxs[t][c][r][s] =
                                new int[prevtIdxs[t][c][r][s].length];
                        }
                    }
                }
            }
        }

        //-- Save the data

        // Use reference caches to minimize array access overhead
        TagTreeEncoder
            ttIncl_t_c[][][],
            ttMaxBP_t_c[][][],
            ttIncl_t_c_r[][],
            ttMaxBP_t_c_r[][];
        int
            lblock_t_c[][][],
            bak_lblock_t_c[][][],
            prevtIdxs_t_c_r[][],
            bak_prevtIdxs_t_c_r[][];

        // Loop on tiles
        for (int t=ttIncl.length-1; t>=0; t--) {
            // Loop on components
            for (int c=ttIncl[t].length-1; c>=0; c--) {
                // Initialize reference caches
                lblock_t_c = lblock[t][c];
                bak_lblock_t_c = bak_lblock[t][c];
                ttIncl_t_c = ttIncl[t][c];
                ttMaxBP_t_c = ttMaxBP[t][c];
                // Loop on resolution levels
                for (int r=lblock_t_c.length-1; r>=0; r--) {
                    // Initialize reference caches
                    ttIncl_t_c_r = ttIncl_t_c[r];
                    ttMaxBP_t_c_r = ttMaxBP_t_c[r];
                    prevtIdxs_t_c_r = prevtIdxs[t][c][r];
                    bak_prevtIdxs_t_c_r = bak_prevtIdxs[t][c][r];

                    // Loop on subbands
                    minsbi = (r==0) ? 0 : 1;
                    maxsbi = (r==0) ? 1 : 4;
                    for (int s=minsbi; s<maxsbi; s++) {
                        // Save 'lblock'
                        System.arraycopy(lblock_t_c[r][s],0,
                                         bak_lblock_t_c[r][s],0,
                                         lblock_t_c[r][s].length);
                        // Save 'prevtIdxs'
                        System.arraycopy(prevtIdxs_t_c_r[s],0,
                                         bak_prevtIdxs_t_c_r[s],0,
                                         prevtIdxs_t_c_r[s].length);
                    } // End loop on subbands

                    // Loop on precincts
                    for(int p=ppinfo[t][c][r].length-1; p>=0; p--) {
                        if(p<ttIncl_t_c_r.length) {
                            // Loop on subbands
                            for(int s=minsbi; s<maxsbi; s++) {
                                ttIncl_t_c_r[p][s].save();
                                ttMaxBP_t_c_r[p][s].save();
                            } // End loop on subbands
                        }
                    } // End loop on precincts
                } // End loop on resolutions
            } // End loop on components
        } // End loop on tiles

        // Set the saved state
        saved = true;
    }


    /**
     * Restores the last saved state of this object. An
     * IllegalArgumentException is thrown if no state has been saved.
     *
     * @see #save
     * */
    public void restore() {
        int maxsbi,minsbi;

        if (!saved) {
            throw new IllegalArgumentException();
        }

        // Invalidate last encoded body buffer
        lbbuf = null;

        //-- Restore tha data

        // Use reference caches to minimize array access overhead
        TagTreeEncoder ttIncl_t_c[][][],ttMaxBP_t_c[][][],ttIncl_t_c_r[][],
            ttMaxBP_t_c_r[][];
        int lblock_t_c[][][],bak_lblock_t_c[][][],prevtIdxs_t_c_r[][],
            bak_prevtIdxs_t_c_r[][];

        // Loop on tiles
        for (int t=ttIncl.length-1; t>=0; t--) {
            // Loop on components
            for (int c=ttIncl[t].length-1; c>=0; c--) {
                // Initialize reference caches
                lblock_t_c = lblock[t][c];
                bak_lblock_t_c = bak_lblock[t][c];
                ttIncl_t_c = ttIncl[t][c];
                ttMaxBP_t_c = ttMaxBP[t][c];
                // Loop on resolution levels
                for (int r=lblock_t_c.length-1; r>=0; r--) {
                    // Initialize reference caches
                    ttIncl_t_c_r = ttIncl_t_c[r];
                    ttMaxBP_t_c_r = ttMaxBP_t_c[r];
                    prevtIdxs_t_c_r = prevtIdxs[t][c][r];
                    bak_prevtIdxs_t_c_r = bak_prevtIdxs[t][c][r];

                    // Loop on subbands
                    minsbi = (r==0) ? 0 : 1;
                    maxsbi = (r==0) ? 1 : 4;
                    for (int s=minsbi; s<maxsbi; s++) {
                        // Restore 'lblock'
                        System.arraycopy(bak_lblock_t_c[r][s],0,
                                         lblock_t_c[r][s],0,
                                         lblock_t_c[r][s].length);
                        // Restore 'prevtIdxs'
                        System.arraycopy(bak_prevtIdxs_t_c_r[s],0,
                                         prevtIdxs_t_c_r[s],0,
                                         prevtIdxs_t_c_r[s].length);
                    } // End loop on subbands

                    // Loop on precincts
                    for(int p=ppinfo[t][c][r].length-1; p>=0; p--) {
                        if(p<ttIncl_t_c_r.length) {
                            // Loop on subbands
                            for(int s=minsbi; s<maxsbi; s++) {
                                ttIncl_t_c_r[p][s].restore();
                                ttMaxBP_t_c_r[p][s].restore();
                            } // End loop on subbands
                        }
                    } // End loop on precincts
                } // End loop on resolution levels
            } // End loop on components
        } // End loop on tiles
    }

    /**
     * Resets the state of the object to the initial state, as if the object
     * was just created.
     * */
    public void reset() {
        int maxsbi,minsbi;

        // Invalidate save
        saved = false;
        // Invalidate last encoded body buffer
        lbbuf = null;

        // Reinitialize each element in the arrays

        // Use reference caches to minimize array access overhead
        TagTreeEncoder ttIncl_t_c[][][],ttMaxBP_t_c[][][],ttIncl_t_c_r[][],
            ttMaxBP_t_c_r[][];
        int lblock_t_c[][][],prevtIdxs_t_c_r[][];

        // Loop on tiles
        for (int t=ttIncl.length-1; t>=0; t--) {
            // Loop on components
            for (int c=ttIncl[t].length-1; c>=0; c--) {
                // Initialize reference caches
                lblock_t_c = lblock[t][c];
                ttIncl_t_c = ttIncl[t][c];
                ttMaxBP_t_c = ttMaxBP[t][c];
                // Loop on resolution levels
                for (int r=lblock_t_c.length-1; r>=0; r--) {
                    // Initialize reference caches
                    ttIncl_t_c_r = ttIncl_t_c[r];
                    ttMaxBP_t_c_r = ttMaxBP_t_c[r];
                    prevtIdxs_t_c_r = prevtIdxs[t][c][r];

                    // Loop on subbands
                    minsbi = (r==0) ? 0 : 1;
                    maxsbi = (r==0) ? 1 : 4;
                    for (int s=minsbi; s<maxsbi; s++) {
                        // Reset 'prevtIdxs'
                        ArrayUtil.intArraySet(prevtIdxs_t_c_r[s],-1);
                        // Reset 'lblock'
                        ArrayUtil.intArraySet(lblock_t_c[r][s],INIT_LBLOCK);
                    } // End loop on subbands

                    // Loop on precincts
                    for(int p=ppinfo[t][c][r].length-1; p>=0; p--) {
                        if(p<ttIncl_t_c_r.length) {
                            // Loop on subbands
                            for(int s=minsbi; s<maxsbi; s++) {
                                ttIncl_t_c_r[p][s].reset();
                                ttMaxBP_t_c_r[p][s].reset();
                            } // End loop on subbands
                        }
                    } // End loop on precincts
                } // End loop on resolution levels
            } // End loop on components
        } // End loop on tiles
    }

    /**
     * Returns true if the current packet is writable i.e. should be written.
     * Returns false otherwise.
     * */
    public boolean isPacketWritable() {
        return packetWritable;
    }

    /**
     * Tells if there was ROI information in the last written packet
     * */
    public boolean isROIinPkt(){
        return roiInPkt;
    }

    /** Gives the length to read in current packet body to get all ROI
     * information */
    public int getROILen(){
        return roiLen;
    }

    /**
     * Returns the parameters that are used in this class and implementing
     * classes. It returns a 2D String array. Each of the 1D arrays is for a
     * different option, and they have 3 elements. The first element is the
     * option name, the second one is the synopsis, the third one is a long
     * description of what the parameter is and the fourth is its default
     * value. The synopsis or description may be 'null', in which case it is
     * assumed that there is no synopsis or description of the option,
     * respectively. Null may be returned if no options are supported.
     *
     * @return the options name, their synopsis and their explanation,
     * or null if no options are supported.
     * */
    public static String[][] getParameterInfo() {
        return pinfo;
    }

    /** 
     * Returns information about a given precinct
     * 
     * @param t Tile index.
     *
     * @param c Component index.
     *
     * @param r Resolution level index.
     *
     * @param p Precinct index
     * */
    public PrecInfo getPrecInfo(int t,int c,int r,int p) {
        return ppinfo[t][c][r][p];
    }
}
