/******************************************************************************
 * $Id: gdaljp2box.cpp 27044 2014-03-16 23:41:27Z rouault $
 *
 * Project:  GDAL 
 * Purpose:  GDALJP2Box Implementation - Low level JP2 box reader.
 * Author:   Frank Warmerdam, warmerdam@pobox.com
 *
 ******************************************************************************
 * Copyright (c) 2005, Frank Warmerdam <warmerdam@pobox.com>
 * Copyright (c) 2010-2012, Even Rouault <even dot rouault at mines-paris dot org>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 ****************************************************************************/

#include "gdaljp2metadata.h"
#include "cpl_string.h"

CPL_CVSID("$Id: gdaljp2box.cpp 27044 2014-03-16 23:41:27Z rouault $");

/************************************************************************/
/*                             GDALJP2Box()                             */
/************************************************************************/

GDALJP2Box::GDALJP2Box( VSILFILE *fpIn )

{
    fpVSIL = fpIn;
    szBoxType[0] = '\0';
    nBoxOffset = -1;
    nDataOffset = -1;
    nBoxLength = 0;
    
    pabyData = NULL;
}

/************************************************************************/
/*                            ~GDALJP2Box()                             */
/************************************************************************/

GDALJP2Box::~GDALJP2Box()

{
    CPLFree( pabyData );
}

/************************************************************************/
/*                             SetOffset()                              */
/************************************************************************/

int  GDALJP2Box::SetOffset( GIntBig nNewOffset )

{
    szBoxType[0] = '\0';
    return VSIFSeekL( fpVSIL, nNewOffset, SEEK_SET ) == 0;
}

/************************************************************************/
/*                             ReadFirst()                              */
/************************************************************************/

int GDALJP2Box::ReadFirst()

{
    return SetOffset(0) && ReadBox();
}

/************************************************************************/
/*                              ReadNext()                              */
/************************************************************************/

int GDALJP2Box::ReadNext()

{
    return SetOffset( nBoxOffset + nBoxLength ) && ReadBox();
}

/************************************************************************/
/*                           ReadFirstChild()                           */
/************************************************************************/

int GDALJP2Box::ReadFirstChild( GDALJP2Box *poSuperBox )

{
    szBoxType[0] = '\0';
    if( !poSuperBox->IsSuperBox() )
        return FALSE;

    return SetOffset( poSuperBox->nDataOffset ) && ReadBox();
}

/************************************************************************/
/*                           ReadNextChild()                            */
/************************************************************************/

int GDALJP2Box::ReadNextChild( GDALJP2Box *poSuperBox )

{
    if( !ReadNext() )
        return FALSE;

    if( nBoxOffset >= poSuperBox->nBoxOffset + poSuperBox->nBoxLength )
    {
        szBoxType[0] = '\0';
        return FALSE;
    }

    return TRUE;
}

/************************************************************************/
/*                              ReadBox()                               */
/************************************************************************/

int GDALJP2Box::ReadBox()

{
    GUInt32   nLBox;
    GUInt32   nTBox;

    nBoxOffset = VSIFTellL( fpVSIL );

    if( VSIFReadL( &nLBox, 4, 1, fpVSIL ) != 1
        || VSIFReadL( &nTBox, 4, 1, fpVSIL ) != 1 )
    {
        return FALSE;
    }

    memcpy( szBoxType, &nTBox, 4 );
    szBoxType[4] = '\0';

    nLBox = CPL_MSBWORD32( nLBox );

    if( nLBox != 1 )
    {
        nBoxLength = nLBox;
        nDataOffset = nBoxOffset + 8;
    }
    else
    {
        GByte abyXLBox[8];
        if( VSIFReadL( abyXLBox, 8, 1, fpVSIL ) != 1 )
            return FALSE;

        
        if( sizeof(nBoxLength) == 8 )
        {
            CPL_MSBPTR64( abyXLBox );
            memcpy( &nBoxLength, abyXLBox, 8 );
        }
        else
        {
            CPL_MSBPTR32( abyXLBox+4 );
            memcpy( &nBoxLength, abyXLBox+4, 4 );
        }

        nDataOffset = nBoxOffset + 16;
    }

    if( nBoxLength == 0 )
    {
        VSIFSeekL( fpVSIL, 0, SEEK_END );
        nBoxLength = VSIFTellL( fpVSIL ) - nBoxOffset;
    }

    if( EQUAL(szBoxType,"uuid") )
    {
        VSIFReadL( abyUUID, 16, 1, fpVSIL );
        nDataOffset += 16;
    }

    if( GetDataLength() < 0 )
    {
        CPLDebug("GDALJP2", "Invalid length for box %s", szBoxType);
        return FALSE;
    }

    return TRUE;
}

/************************************************************************/
/*                             IsSuperBox()                             */
/************************************************************************/

int GDALJP2Box::IsSuperBox()

{
    if( EQUAL(GetType(),"asoc") || EQUAL(GetType(),"jp2h")
        || EQUAL(GetType(),"res ") )
        return TRUE;
    else
        return FALSE;
}

/************************************************************************/
/*                            ReadBoxData()                             */
/************************************************************************/

GByte *GDALJP2Box::ReadBoxData()

{
    char *pszData = (char *) CPLMalloc((int)GetDataLength() + 1);

    if( (GIntBig) VSIFReadL( pszData, 1, (int)GetDataLength(), fpVSIL ) 
        != GetDataLength() )
    {
        CPLFree( pszData );
        return NULL;
    }

    pszData[GetDataLength()] = '\0';

    return (GByte *) pszData;
}

/************************************************************************/
/*                           GetDataLength()                            */
/************************************************************************/

GIntBig GDALJP2Box::GetDataLength()
{
    return nBoxLength - (nDataOffset - nBoxOffset);
}

/************************************************************************/
/*                            DumpReadable()                            */
/************************************************************************/

int GDALJP2Box::DumpReadable( FILE *fpOut, int nIndentLevel )

{
    if( fpOut == NULL )
        fpOut = stdout;

    int i;
    for(i=0;i<nIndentLevel;i++)
        fprintf( fpOut, "  " );

    fprintf( fpOut, "  Type=%s, Offset=%d/%d, Data Size=%d",
             szBoxType, (int) nBoxOffset, (int) nDataOffset, 
             (int)(nBoxLength - (nDataOffset - nBoxOffset)) );

    if( IsSuperBox() )
    {
        fprintf( fpOut, " (super)" );
    }

    fprintf( fpOut, "\n" );
    
    if( IsSuperBox() ) 
    {
        GDALJP2Box oSubBox( GetFILE() );

        for( oSubBox.ReadFirstChild( this );
             strlen(oSubBox.GetType()) > 0;
             oSubBox.ReadNextChild( this ) )
        {
            oSubBox.DumpReadable( fpOut, nIndentLevel + 1 );
        }
    }

    if( EQUAL(GetType(),"uuid") )
    {
        char *pszHex = CPLBinaryToHex( 16, GetUUID() );
        for(i=0;i<nIndentLevel;i++)
            fprintf( fpOut, "  " );

        fprintf( fpOut, "    UUID=%s", pszHex );

        if( EQUAL(pszHex,"B14BF8BD083D4B43A5AE8CD7D5A6CE03") )
            fprintf( fpOut, " (GeoTIFF)" );
        if( EQUAL(pszHex,"96A9F1F1DC98402DA7AED68E34451809") )
            fprintf( fpOut, " (MSI Worldfile)" );
        CPLFree( pszHex );

        fprintf( fpOut, "\n" );
    }

    return 0;
}

/************************************************************************/
/*                              SetType()                               */
/************************************************************************/

void GDALJP2Box::SetType( const char *pszType )

{
    CPLAssert( strlen(pszType) == 4 );

    memcpy(szBoxType, pszType, 4);
    szBoxType[4] = '\0';
}

/************************************************************************/
/*                          SetWritableData()                           */
/************************************************************************/

void GDALJP2Box::SetWritableData( int nLength, const GByte *pabyDataIn )

{
    CPLFree( pabyData );
    
    pabyData = (GByte *) CPLMalloc(nLength);
    memcpy( pabyData, pabyDataIn, nLength );

    nBoxOffset = -9; // virtual offsets for data length computation.
    nDataOffset = -1;
    
    nBoxLength = 8 + nLength;
}

/************************************************************************/
/*                           CreateUUIDBox()                            */
/************************************************************************/

GDALJP2Box *GDALJP2Box::CreateUUIDBox( 
    const GByte *pabyUUID, int nDataSize, GByte *pabyData )

{
    GDALJP2Box *poBox;

    poBox = new GDALJP2Box();
    poBox->SetType( "uuid" );
    memcpy( poBox->abyUUID, pabyUUID, 16 );

    GByte *pabyMergedData = (GByte *) CPLMalloc(16+nDataSize);
    memcpy( pabyMergedData, pabyUUID, 16 );
    memcpy( pabyMergedData+16, pabyData, nDataSize );
    
    poBox->SetWritableData( 16 + nDataSize, pabyMergedData );
    
    CPLFree( pabyMergedData );

    return poBox;
}

/************************************************************************/
/*                           CreateAsocBox()                            */
/************************************************************************/

GDALJP2Box *GDALJP2Box::CreateAsocBox( int nCount, GDALJP2Box **papoBoxes )


{
    int nDataSize=0, iBox;
    GByte *pabyCompositeData, *pabyNext;

/* -------------------------------------------------------------------- */
/*      Compute size of data area of asoc box.                          */
/* -------------------------------------------------------------------- */
    for( iBox = 0; iBox < nCount; iBox++ )
        nDataSize += 8 + (int) papoBoxes[iBox]->GetDataLength();

    pabyNext = pabyCompositeData = (GByte *) CPLMalloc(nDataSize);

/* -------------------------------------------------------------------- */
/*      Copy subboxes headers and data into buffer.                     */
/* -------------------------------------------------------------------- */
    for( iBox = 0; iBox < nCount; iBox++ )
    {
        GUInt32   nLBox;

        nLBox = CPL_MSBWORD32(papoBoxes[iBox]->nBoxLength);
        memcpy( pabyNext, &nLBox, 4 );
        pabyNext += 4;

        memcpy( pabyNext, papoBoxes[iBox]->szBoxType, 4 );
        pabyNext += 4;

        memcpy( pabyNext, papoBoxes[iBox]->pabyData, 
                (int) papoBoxes[iBox]->GetDataLength() );
        pabyNext += papoBoxes[iBox]->GetDataLength();
    }
    
/* -------------------------------------------------------------------- */
/*      Create asoc box.                                                */
/* -------------------------------------------------------------------- */
    GDALJP2Box *poAsoc = new GDALJP2Box();

    poAsoc->SetType( "asoc" );
    poAsoc->SetWritableData( nDataSize, pabyCompositeData );
    
    CPLFree( pabyCompositeData );

    return poAsoc;
}

/************************************************************************/
/*                            CreateLblBox()                            */
/************************************************************************/

GDALJP2Box *GDALJP2Box::CreateLblBox( const char *pszLabel )

{
    GDALJP2Box *poBox;

    poBox = new GDALJP2Box();
    poBox->SetType( "lbl " );
    poBox->SetWritableData( strlen(pszLabel)+1, (const GByte *) pszLabel );

    return poBox;
}

/************************************************************************/
/*                       CreateLabelledXMLAssoc()                       */
/************************************************************************/

GDALJP2Box *GDALJP2Box::CreateLabelledXMLAssoc( const char *pszLabel,
                                                const char *pszXML )

{
    GDALJP2Box oLabel, oXML;
    GDALJP2Box *aoList[2];

    oLabel.SetType( "lbl " );
    oLabel.SetWritableData( strlen(pszLabel)+1, (const GByte *) pszLabel );

    oXML.SetType( "xml " );
    oXML.SetWritableData( strlen(pszXML)+1, (const GByte *) pszXML );

    aoList[0] = &oLabel;
    aoList[1] = &oXML;
    
    return CreateAsocBox( 2, aoList );
}

