/*
 * $RCSfile: CodestreamManipulator.java,v $
 * $Revision: 1.1 $
 * $Date: 2005-02-11 05:02:24 $
 * $State: Exp $
 *
 * Class:                   CodestreamManipulator
 *
 * Description:             Manipulates codestream to create tile-parts etc
 *
 *
 *
 * 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.util;

import jj2000.j2k.codestream.*;
import jj2000.j2k.io.*;

import java.util.*;
import java.io.*;

/**
 * This class takes a legal JPEG 2000 codestream and performs some
 * manipulation on it. Currently the manipulations supported are: Tile-parts
 * */
public class CodestreamManipulator{

    /** Flag indicating whether packed packet headers in main header is used
     *  */
    private boolean ppmUsed;

    /** Flag indicating whether packed packet headers in tile headers is used
     *  */
    private boolean pptUsed;

    /** Flag indicating whether SOP marker was only intended for parsing in
     * This class and should be removed */
    private boolean tempSop;

    /** Flag indicating whether EPH marker was only intended for parsing in
     * This class and should be removed */
    private boolean tempEph;

    /** The number of tiles in the image */
    private int nt;

    /** The number of packets per tile-part */
    private int pptp;

    /** The name of the outfile */
    private File file;

    /** The length of a SOT plus a SOD marker */
    private static int TP_HEAD_LEN = 14;

    /** The maximum number of a tile part index (TPsot) */
    private static int MAX_TPSOT = 16;

    /** The maximum number of tile parts in any tile */
    private int maxtp;

    /** The number of packets per tile */
    private int[] ppt = new int[nt];

    /** The positions of the SOT, SOP and EPH markers */
    private Integer[] positions;

    /** The main header */
    private byte[] mainHeader;

    /** Buffers containing the tile parts */
    private byte[][][] tileParts;

    /** Buffers containing the original tile headers */
    private byte[][]   tileHeaders;

    /** Buffers contaning the packet headers */
    private byte[][][] packetHeaders;

    /** Buffers containing the packet data */
    private byte[][][] packetData;

    /** Buffers containing the SOP marker segments */
    private byte[][][] sopMarkSeg;

    /**
     * Instantiates a codestream manipulator..
     *
     * @param outname The name of the original outfile
     *
     * @param nt The number of tiles in the image
     *
     * @param pptp Packets per tile-part. If zero, no division into tileparts
     * is performed
     *
     * @param ppm Flag indicating that PPM marker is used
     *
     * @param ppt Flag indicating that PPT marker is used
     *
     * @param tempSop Flag indicating whether SOP merker should be removed
     *
     * @param tempEph Flag indicating whether EPH merker should be removed
     * */
    public CodestreamManipulator(File file, int nt, int pptp, boolean ppm,
                                 boolean ppt, boolean tempSop,
                                 boolean tempEph) {
	this.file = file;
        this.nt=nt;
        this.pptp = pptp;
        this.ppmUsed = ppm;
        this.pptUsed = ppt;
        this.tempSop = tempSop;
        this.tempEph = tempEph;
    }

    /**
     * This method performs the actual manipulation of the codestream which is
     * the reparsing for tile parts and packed packet headers
     *
     * @return The number of bytes that the file has increased by
     *
     * @exception java.io.IOException If an I/O error ocurred.
     * */
    public int doCodestreamManipulation() throws IOException{
        int addedHeaderBytes=0;
        ppt = new int[nt];
        tileParts     = new byte[nt][][];
        tileHeaders   = new byte[nt][];
        packetHeaders = new byte[nt][][];
        packetData    = new byte[nt][][];
        sopMarkSeg    = new byte[nt][][];

        // If neither packed packet header nor tile parts are used, return 0
        if( ppmUsed == false && pptUsed == false && pptp == 0)
            return 0;

        // Open file for reading and writing
        BEBufferedRandomAccessFile fi =
            new BEBufferedRandomAccessFile(file, "rw+");
        addedHeaderBytes -= fi.length();

        // Parse the codestream for SOT, SOP and EPH markers
        parseAndFind(fi);

        // Read and buffer the tile headers, packet headers and packet data
        readAndBuffer(fi);

        // Close file and overwrite with new file
        fi.close();
        fi = new  BEBufferedRandomAccessFile(file, "rw");

        // Create tile-parts
        createTileParts();

        // Write new codestream
        writeNewCodestream(fi);

        // Close file
        fi.flush();
        addedHeaderBytes += fi.length();
        fi.close();

        return addedHeaderBytes;
    }

    /**
     * This method parses the codestream for SOT, SOP and EPH markers and
     * removes header header bits signalling SOP and EPH markers if packed
     * packet headers are used
     *
     * @param fi The file to parse the markers from
     *
     * @exception java.io.IOException If an I/O error ocurred.
     * */
    private void parseAndFind(BufferedRandomAccessFile fi) throws IOException{
        int length,pos,i,t,sop=0,eph=0;
        short marker;
        int halfMarker;
        int tileEnd;
        Vector markPos = new Vector();

        // Find position of first SOT marker
        marker = (short)fi.readUnsignedShort(); // read SOC marker
        marker = (short)fi.readUnsignedShort();
        while(marker != Markers.SOT){
            pos = fi.getPos();
            length = fi.readUnsignedShort();

            // If SOP and EPH markers were only used for parsing in this
            // class remove SOP and EPH markers from Scod field
            if(marker == Markers.COD){
                int scod = fi.readUnsignedByte();
                if(tempSop)
                    scod &= 0xfd; // Remove bits indicating SOP
                if(tempEph)
                    scod &= 0xfb; // Remove bits indicating SOP
                fi.seek(pos +2);
                fi.write(scod);
            }

            fi.seek(pos + length);
            marker = (short)fi.readUnsignedShort();
        }
        pos = fi.getPos();
        fi.seek(pos-2);

        // Find all packet headers, packed data and tile headers
        for(t=0;t<nt;t++){
            // Read SOT marker
            fi.readUnsignedShort(); // Skip SOT
            pos = fi.getPos();
            markPos.addElement(new Integer(fi.getPos()));
            fi.readInt();           // Skip Lsot and Isot
            length = fi.readInt();  // Read Psot
            fi.readUnsignedShort(); // Skip TPsot & TNsot
            tileEnd = pos + length-2; // Last byte of tile

            // Find position of SOD marker
            marker = (short)fi.readUnsignedShort();
            while(marker != Markers.SOD){
                pos = fi.getPos();
                length = fi.readUnsignedShort();

                // If SOP and EPH markers were only used for parsing in this
                // class remove SOP and EPH markers from Scod field
                if(marker == Markers.COD){
                    int scod = fi.readUnsignedByte();
                    if(tempSop)
                        scod &= 0xfd; // Remove bits indicating SOP
                    if(tempEph)
                        scod &= 0xfb; // Remove bits indicating SOP
                    fi.seek(pos +2);
                    fi.write(scod);
                }
                fi.seek(pos + length);
                marker = (short)fi.readUnsignedShort();
            }

            // Find all SOP and EPH markers in tile
            sop = 0;
            eph = 0;

            i = fi.getPos();
            while(i<tileEnd){
                halfMarker = (short) fi.readUnsignedByte();
                if(halfMarker == (short)0xff){
                    marker = (short)((halfMarker<<8) +
                                        fi.readUnsignedByte());
                    i++;
                    if(marker == Markers.SOP){
                        markPos.addElement(new Integer(fi.getPos()));
                        ppt[t]++;
                        sop++;
			fi.skipBytes(4);
			i+=4;
                    }

                    if(marker == Markers.EPH){
                        markPos.addElement(new Integer(fi.getPos()));
                        eph++;
                    }
                }
                i++;
            }
        }
        markPos.addElement(new Integer(fi.getPos()+2));
        positions = new Integer[markPos.size()];
        markPos.copyInto(positions);
    }

    /**
     * This method reads and buffers the tile headers, packet headers and
     * packet data.
     *
     * @param fi The file to read the headers and data from
     *
     * @exception java.io.IOException If an I/O error ocurred.
     * */
    private void readAndBuffer(BufferedRandomAccessFile fi)throws IOException{
        int p,prem,length,t,markIndex;

        // Buffer main header
        fi.seek(0);
        length = ((Integer)positions[0]).intValue()-2;
        mainHeader = new byte[length];
        fi.readFully(mainHeader,0,length);
        markIndex = 0;

        for(t=0; t<nt; t++){
            prem = ppt[t];

            packetHeaders[t] = new byte[prem][];
            packetData[t] = new byte[prem][];
            sopMarkSeg[t] = new byte[prem][];

            // Read tile header
            length = positions[ markIndex+1 ].intValue() -
                positions[ markIndex ].intValue();
            tileHeaders[t] = new byte[length];
            fi.readFully(tileHeaders[t],0,length);
            markIndex++;

            for(p=0; p<prem; p++){
                // Read packet header
                length = positions[ markIndex+1 ].intValue() -
                    positions[ markIndex ].intValue();

                if(tempSop){ // SOP marker is skipped
                    length -= Markers.SOP_LENGTH;
                    fi.skipBytes(Markers.SOP_LENGTH);
                }
                else{ // SOP marker is read and buffered
                    length -= Markers.SOP_LENGTH;
                    sopMarkSeg[t][p] = new byte[Markers.SOP_LENGTH];
                    fi.readFully(sopMarkSeg[t][p],0,Markers.SOP_LENGTH);
                }

                if(!tempEph){ // EPH marker is kept in header
                    length += Markers.EPH_LENGTH;
                }

                packetHeaders[t][p] = new byte[length];
                fi.readFully(packetHeaders[t][p],0,length);
                markIndex++;

                // Read packet data
                length = positions[ markIndex+1 ].intValue() -
                    positions[ markIndex ].intValue();

                length -= Markers.EPH_LENGTH;
                 if(tempEph){ // EPH marker is used and is skipped
                    fi.skipBytes(Markers.EPH_LENGTH);
                }

                packetData[t][p] = new byte[length];
                fi.readFully(packetData[t][p],0,length);
                markIndex++;
            }
        }
    }

    /**
     * This method creates the tileparts from the buffered tile headers,
     * packet headers and packet data
     *
     * @exception java.io.IOException If an I/O error ocurred.
     * */
    private void createTileParts() throws IOException{
        int i,prem,t,length;
        int pIndex,phIndex;
        int tppStart;
        int tilePart;
        int p,np, nomnp;
        int numTileParts;
        int numPackets;
        ByteArrayOutputStream temp = new ByteArrayOutputStream();
        byte[] tempByteArr;

        // Create tile parts
        tileParts = new byte[nt][][];
        maxtp = 0;

        for(t=0; t<nt; t++){
            // Calculate number of tile parts. If tileparts are not used,
            // put all packets in the first tilepart
            if(pptp == 0)
                pptp = ppt[t];
            prem = ppt[t];
            numTileParts = (int)Math.ceil(((double)prem)/pptp);
            numPackets = packetHeaders[t].length;
            maxtp = (numTileParts>maxtp) ? numTileParts:maxtp;
            tileParts[t] = new byte[numTileParts][];

            // Create all the tile parts for tile t
            tppStart = 0;
            pIndex = 0;
            p=0;
            phIndex = 0;
            for(tilePart=0;tilePart<numTileParts;tilePart++){

                // Calculate number of packets in this tilepart
                nomnp = (pptp > prem) ? prem:pptp;
		np = nomnp;

                // Write tile part header
                if(tilePart == 0){
                    // Write original tile part header up to SOD marker
                    temp.write(tileHeaders[t],0,
                               tileHeaders[t].length-2);
                }else{
                    // Write empty header of length TP_HEAD_LEN-2
                    temp.write(new byte[TP_HEAD_LEN-2],0,TP_HEAD_LEN-2);
                }

                // Write PPT marker segments if PPT used
                if(pptUsed){
                    int pptLength = 3; // Zppt and Lppt
                    int pptIndex = 0;
                    int phLength;

                    p = pIndex;
                    while(np > 0){
                        phLength = packetHeaders[t][p].length;

                        // If the total legth of the packet headers is greater
                        // than MAX_LPPT, several PPT markers are needed
                        if(pptLength + phLength > Markers.MAX_LPPT){
                            temp.write(Markers.PPT>>>8);
                            temp.write(Markers.PPT);
                            temp.write(pptLength>>>8);
                            temp.write(pptLength);
                            temp.write(pptIndex++);
                            for(i=pIndex; i < p ; i++){
                                temp.write(packetHeaders[t][i],0,
                                           packetHeaders[t][i].length);
                            }
                            pptLength = 3; // Zppt and Lppt
                            pIndex = p;
                        }
                        pptLength += phLength;
                        p++;
			np--;
                    }
                    // Write last PPT marker
                    temp.write(Markers.PPT>>>8);
                    temp.write(Markers.PPT);
                    temp.write(pptLength>>>8);
                    temp.write(pptLength);
                    temp.write(pptIndex);
                    for(i=pIndex; i < p ; i++){

                        temp.write(packetHeaders[t][i],0,
                                   packetHeaders[t][i].length);
                    }
                }
                pIndex = p;
		np = nomnp;

                // Write SOD marker
                temp.write(Markers.SOD>>>8);
                temp.write(Markers.SOD);

                // Write packet data and packet headers if PPT and PPM not used
                for(p=tppStart; p<tppStart+np ; p++){
                  if(!tempSop){
                        temp.write(sopMarkSeg[t][p],0,Markers.SOP_LENGTH);
                  }

                    if(!(ppmUsed || pptUsed)){
                        temp.write(packetHeaders[t][p],0,
                                   packetHeaders[t][p].length);
                    }

                    temp.write(packetData[t][p], 0, packetData[t][p].length);
                }
                tppStart += np;

                // Edit tile part header
                tempByteArr = temp.toByteArray();
                tileParts[t][tilePart] = tempByteArr;
                length = temp.size();

                if(tilePart == 0){
                    // Edit first tile part header
                    tempByteArr[6] = (byte)(length>>>24);         // Psot
                    tempByteArr[7] = (byte)(length>>>16);
                    tempByteArr[8] = (byte)(length>>>8);
                    tempByteArr[9] = (byte)(length);
                    tempByteArr[10] = (byte)(0);                  // TPsot
                    tempByteArr[11] = (byte)(numTileParts);       // TNsot
                }else{
                    // Edit tile part header
                    tempByteArr[0] = (byte)(Markers.SOT>>>8);     // SOT
                    tempByteArr[1] = (byte)(Markers.SOT);
                    tempByteArr[2] = (byte)(0);                   // Lsot
                    tempByteArr[3] = (byte)(10);
                    tempByteArr[4] = (byte)(t >> 8);                   // Lsot
                    tempByteArr[5] = (byte)(t);                   // Isot
                    tempByteArr[6] = (byte)(length>>>24);         // Psot
                    tempByteArr[7] = (byte)(length>>>16);
                    tempByteArr[8] = (byte)(length>>>8);
                    tempByteArr[9] = (byte)(length);
                    tempByteArr[10] = (byte)(tilePart);           //TPsot
                    tempByteArr[11] = (byte)(numTileParts);       // TNsot
                }
                temp.reset();
                prem -= np;
            }
        }
        temp.close();
    }

    /**
     * This method writes the new codestream to the file.
     *
     * @param fi The file to write the new codestream to
     *
     * @exception java.io.IOException If an I/O error ocurred.
     * */
    private void writeNewCodestream(BufferedRandomAccessFile fi)
        throws IOException{
        int i,t,p,tp;
        int numTiles = tileParts.length;
        int[][] packetHeaderLengths = new int[numTiles][maxtp];
        byte[] temp;
        int length;

        // Write main header up to SOT marker
        fi.write(mainHeader,0,mainHeader.length);

        // If PPM used write all packet headers in PPM markers
        if(ppmUsed){
            ByteArrayOutputStream ppmMarkerSegment =
                new ByteArrayOutputStream();
            int numPackets;
            int totNumPackets;
            int ppmIndex=0;
            int ppmLength;
            int pStart,pStop;
            int prem[] = new int[numTiles];

            // Set number of remaining packets
            for(t=0 ; t<numTiles ; t++)
                prem[t] = packetHeaders[t].length;

            // Calculate Nppm values
            for(tp=0; tp<maxtp ; tp++){
                for(t=0 ; t<numTiles ; t++){

                    if(tileParts[t].length > tp){
                        totNumPackets = packetHeaders[t].length;
                        // Calculate number of packets in this tilepart
                        numPackets =
                            (tp == tileParts[t].length-1) ?
                            prem[t] : pptp;

                        pStart = totNumPackets - prem[t];
                        pStop = pStart + numPackets;

                        // Calculate number of packet header bytes for this
                        // tile part
                        for(p=pStart ; p < pStop ; p++)
                            packetHeaderLengths[t][tp] +=
                                packetHeaders[t][p].length;

                        prem[t] -= numPackets;
                    }
                }
            }

            // Write first PPM marker
            ppmMarkerSegment.write(Markers.PPM >>> 8);
            ppmMarkerSegment.write(Markers.PPM);
            ppmMarkerSegment.write(0); // Temporary Lppm value
            ppmMarkerSegment.write(0); // Temporary Lppm value
            ppmMarkerSegment.write(0); // zppm
            ppmLength = 3;
            ppmIndex++;

            // Set number of remaining packets
            for(t=0 ; t<numTiles ; t++)
                prem[t] = packetHeaders[t].length;

            // Write all PPM markers and information
            for(tp=0; tp<maxtp ; tp++){
                for(t=0 ; t<numTiles ; t++){

                    if(tileParts[t].length > tp){
                        totNumPackets = packetHeaders[t].length;

                        // Calculate number of packets in this tilepart
                        numPackets =
                            (tp == tileParts[t].length-1) ? prem[t] : pptp;

                        pStart = totNumPackets - prem[t];
                        pStop = pStart + numPackets;

                        // If Nppm value wont fit in current PPM marker segment
                        // write current PPM marker segment and start new
                        if(ppmLength + 4 > Markers.MAX_LPPM){
                            // Write current PPM marker
                            temp = ppmMarkerSegment.toByteArray();
                            length = temp.length-2;
                            temp[2] = (byte)(length >>> 8);
                            temp[3] = (byte)length;
                            fi.write(temp,0,length+2);

                            // Start new PPM marker segment
                            ppmMarkerSegment.reset();
                            ppmMarkerSegment.write(Markers.PPM >>> 8);
                            ppmMarkerSegment.write(Markers.PPM);
                            ppmMarkerSegment.write(0); // Temporary Lppm value
                            ppmMarkerSegment.write(0); // Temporary Lppm value
                            ppmMarkerSegment.write(ppmIndex++); // zppm
                            ppmLength = 3;
                        }

                        // Write Nppm value
                        length = packetHeaderLengths[t][tp];
                        ppmMarkerSegment.write(length>>>24);
                        ppmMarkerSegment.write(length>>>16);
                        ppmMarkerSegment.write(length>>>8);
                        ppmMarkerSegment.write(length);
                        ppmLength += 4;

                        // Write packet headers
                        for(p=pStart ; p < pStop ; p++){
                            length = packetHeaders[t][p].length;

                            // If next packet header value wont fit in
                            // current PPM marker segment write current PPM
                            // marker segment and start new
                            if(ppmLength + length > Markers.MAX_LPPM){
                                // Write current PPM marker
                                temp = ppmMarkerSegment.toByteArray();
                                length = temp.length-2;
                                temp[2] = (byte)(length >>> 8);
                                temp[3] = (byte)length;
                                fi.write(temp,0,length+2);

                                // Start new PPM marker segment
                                ppmMarkerSegment.reset();
                                ppmMarkerSegment.write(Markers.PPM >>> 8);
                                ppmMarkerSegment.write(Markers.PPM);
                                ppmMarkerSegment.write(0); // Temp Lppm value
                                ppmMarkerSegment.write(0); // Temp Lppm value
                                ppmMarkerSegment.write(ppmIndex++); // zppm
                                ppmLength = 3;
                            }

                            // write packet header
                            ppmMarkerSegment.write(packetHeaders[t][p],0,
                                                   packetHeaders[t][p].length);
                            ppmLength+=packetHeaders[t][p].length;
                        }
                        prem[t]-=numPackets;
                    }
                }
            }
            // Write last PPM marker segment
            temp = ppmMarkerSegment.toByteArray();
            length = temp.length-2;
            temp[2] = (byte)(length >>> 8);
            temp[3] = (byte)length;
            fi.write(temp,0,length+2);
        }

        // Write tile parts interleaved
        for(tp=0;tp<maxtp;tp++)
            for(t=0 ; t<nt ;t++){
                if(tileParts[t].length >= tp){
                    temp = tileParts[t][tp];
                    length = temp.length;
                    fi.write(temp,0,length);
                }
            }
        fi.writeShort(Markers.EOC);
    }
}





















