//----------------------------------------------------------------------------
//
// License:  LGPL
// 
// See LICENSE.txt file in the top level directory for more details.
//
// Author:  David Burken
//
// Description: JP2 Info object.
// 
//----------------------------------------------------------------------------
// $Id: ossimJp2Info.cpp 23222 2015-04-05 15:44:57Z dburken $

#include <ossim/support_data/ossimJp2Info.h>
#include <ossim/base/ossimCommon.h>
#include <ossim/base/ossimEndian.h>
#include <ossim/base/ossimException.h>
#include <ossim/base/ossimKeywordlist.h>
#include <ossim/base/ossimTrace.h>
// #include <ossim/support_data/ossimJ2kCommon.h>
#include <ossim/support_data/ossimTiffInfo.h>
#include <fstream>
#include <istream>
#include <iostream>

static ossimTrace traceDebug("ossimJp2Info:debug");

ossimJp2Info::ossimJp2Info()
   : ossimJ2kInfo()
{
}

ossimJp2Info::~ossimJp2Info()
{
}

bool ossimJp2Info::open(const ossimFilename& file)
{
   bool result = false;

   std::ifstream str;
   str.open( file.c_str(), std::ios_base::in | std::ios_base::binary);

   if ( str.is_open() )
   {
      const ossim_uint8 J2K_SIGNATURE_BOX[SIGNATURE_BOX_SIZE] = 
         {0x00,0x00,0x00,0x0c,0x6a,0x50,0x20,0x20,0x0d,0x0a,0x87,0x0a};
      
      ossim_uint8 box[SIGNATURE_BOX_SIZE];
      
      // Read in the box.
      str.read((char*)box, SIGNATURE_BOX_SIZE);
      
      result = true;
      for (ossim_uint32 i = 0; i < SIGNATURE_BOX_SIZE; ++i)
      {
         if (box[i] != J2K_SIGNATURE_BOX[i])
         {
            result = false;
            break;
         }
      }
      
      str.close();
   }

   if ( result )
   {
      m_file = file; // Capture file name.
   }
   
   return result;
}

std::ostream& ossimJp2Info::print(std::ostream& out) const
{
   if ( m_file.size() )
   {
      // Open the file.
      std::ifstream str( m_file.c_str(), std::ios_base::in | std::ios_base::binary );
      if (str.good())
      {
         //---
         // ISO/IEC 15444-1:2004 (E)
         // LBox, Box Length. This field specifies the length of the box, stored
         // as a 4-byte big endian unsigned integer. This value includes all of
         // the fields of the box, including the length and type. If the value
         // of this field is 1, then the XLBox field shall exist and the value
         // of that field shall be the actual length of the box. If the value
         // of this field is 0, then the length of the box was not known when
         // the LBox field was written. In this case, this box contains all
         // bytes up to the end of the file. If a box of length 0 is contained
         // within another box (its superbox), then the length of that superbox
         // shall also be 0.  This means that this box is the last box in the
         // file. The values 2-7 are reserved for ISO use.
         //---
         ossim_uint32 lbox  = 0;
         ossim_uint32 tbox  = 0;
         ossim_uint64 xlbox = 0;

         const ossim_uint32 UUID_TYPE = 0x75756964;
         const ossim_uint8 GEOTIFF_UUID[GEOTIFF_UUID_SIZE] = 
            {
               0xb1, 0x4b, 0xf8, 0xbd,
               0x08, 0x3d, 0x4b, 0x43,
               0xa5, 0xae, 0x8c, 0xd7,
               0xd5, 0xa6, 0xce, 0x03
            };
         
         while ( str.good() )
         {
            std::streamoff boxPos = str.tellg();
            
            readUInt32( lbox, str );
            readUInt32( tbox, str );
            
            if ( 1 == lbox )
            {
               readUInt64( xlbox, str );
            }

            if ( traceDebug() )
            {
               ossimNotify(ossimNotifyLevel_DEBUG)
                  << "jp2.lbox: " << std::dec << lbox << "\n"
                  << "jp2.tbox: " << std::hex << tbox << std::dec << "\n";
               printTboxType( tbox, ossimNotify(ossimNotifyLevel_DEBUG)  );

               if ( 1 == lbox )
               {
                  ossimNotify(ossimNotifyLevel_DEBUG) << "jp2.xlbox: " << xlbox << std::endl;
               }
            }            
            
            if (tbox == UUID_TYPE)
            {
               // Check for GeoTIFF box.
               
               ossim_uint8 geotiffUuid[GEOTIFF_UUID_SIZE];
               
               // Read in the id.
               str.read((char*)geotiffUuid, GEOTIFF_UUID_SIZE);

#if 0
               bool foundIt = true;
               for (ossim_uint32 i = 0; i < GEOTIFF_UUID_SIZE; ++i)
               {
                  if (geotiffUuid[i] != GEOTIFF_UUID[i])
                  {
                     foundIt = false;
                     break;
                  }
               }
#endif
               
               if( memcmp( (char*)geotiffUuid, GEOTIFF_UUID, GEOTIFF_UUID_SIZE) == 0)
               {
                  //---
                  // Feed the stream to the tiff info object to get a keyword
                  // list with geometry/projection info.
                  //---
                  ossimTiffInfo info;
                  
                  //---
                  // Have geotiff boxes with badly terminated geotiffs. So to keep
                  // the tag parser from walking past the first image file directory
                  // (IFD) into garbage we will set the process overview flag to false.
                  //
                  // Note if we ever get multiple entries we will need to take this out.
                  //---
                  info.setProcessOverviewFlag(false);

                  // Do a print to a memory stream.
                  std::ostringstream os;
                  info.print(str, os);

                  // Open an input stream to pass to the keyword list.
                  std::istringstream in( os.str() );
                  
                  ossimKeywordlist kwl;
                  if ( kwl.parseStream(in) )      
                  {
                     kwl.addPrefixToAll( ossimString("jp2.") );

                     // Print it:
                     out << kwl << std::endl;
                  }
                  
                  // Get out of while loop.
                  // break;
               }
            }

            if (lbox == 0) // last box?
            {
               break;
            }
            
            // Seek to the next box.  If xlbox is 1
            if (lbox == 1)
            {
               boxPos += (std::streamoff)xlbox;
            }
            else
            {
               boxPos += (std::streamoff)lbox;
            }

            // Go to next box:
            str.seekg(boxPos, std::ios_base::beg);
            
         } // matches: while ( str.good() )
         
         // Close the stream.
         str.close();
         
      } // matches: if ( str.is_open() )
      
   } // matches: if ( m_file.size() )
   
   return out; 
}

void ossimJp2Info::readUInt32(ossim_uint32& i, std::ifstream& str) const
{
   str.read((char*)&i, 4);
   if (m_endian)
   {
      m_endian->swap(i);
   }
}

void ossimJp2Info::readUInt64(ossim_uint64& i, std::ifstream& str) const
{
   str.read((char*)&i, 8);
   if (m_endian)
   {
      m_endian->swap(i);
   }
}

std::streamoff ossimJp2Info::findBoxData( const ossim_uint32& type,
                                          std::ifstream& str,
                                          ossim_uint32& length ) const
{
   std::streamoff boxPos = 0;
   std::streamoff dataPosOfType = 0;

   if ( str.good() )
   {
      ossim_uint32   lbox   = 0;
      ossim_uint32   tbox   = 0;
      ossim_uint64   xlbox  = 0;
      std::streamoff offsetToDbox = 0;

      while ( str.good() )
      {
         boxPos = str.tellg();

         readUInt32( lbox, str );
         readUInt32( tbox, str );

         if ( lbox == 1 )
         {
            readUInt64( xlbox, str );
            offsetToDbox = 16;
         }
         else
         {
            offsetToDbox = 8;
         }

         if ( traceDebug() )
         {
            ossimNotify(ossimNotifyLevel_DEBUG)
               << "jp2.lbox: " << std::dec << lbox << "\n"
               << "jp2.tbox: " << std::hex << tbox << std::dec << "\n";
            printTboxType( tbox, ossimNotify(ossimNotifyLevel_DEBUG)  );

            if ( lbox == 1 )
            {
               ossimNotify(ossimNotifyLevel_DEBUG) << "jp2.xlbox: " << xlbox << std::endl;
            }
         }            

         if ( tbox == type )
         {
            dataPosOfType = boxPos + offsetToDbox; // set up return value
            length = lbox;
            break; // done...
         }

         if ( lbox == 0 ) // last box?
         {
            break;
         }

         // Seek to the next box.
         std::streamoff nextBoxPos = boxPos;
         if (lbox == 1)
         {
            nextBoxPos+= (std::streamoff)xlbox;
         }
         else
         {
            nextBoxPos+= (std::streamoff)lbox;
         }

         // Go to next box:
         str.seekg(nextBoxPos, std::ios_base::beg);

      } // matches: while ( str.good() )

   } // matches: if ( str.good() )

   return dataPosOfType;
}

std::streamoff ossimJp2Info::getBox( const ossim_uint32 type,
                                     bool includeAll,
                                     std::vector<ossim_uint8>& box ) const
{
   std::streamoff boxPos = 0;

   if ( m_file.size() )
   {
      // Open the file.
      std::ifstream str( m_file.c_str(), std::ios_base::in | std::ios_base::binary );
      if (str.good())
      {
         boxPos = getBox( type, includeAll, str, box ) ;

      } // matches: if ( str.is_open() )

   } // matches: if ( m_file.size() ) 

   return boxPos;

} // End: ossimJp2Info::getBox( type, includeAll, box ) const

std::streamoff ossimJp2Info::getBox( const ossim_uint32& type,
                                     bool includeAll,
                                     std::ifstream& str,
                                     std::vector<ossim_uint8>& box ) const
{
   std::streamoff boxPos = 0;
   std::streamoff boxPosOfType = 0;
   box.clear();
   
   if ( str.good() )
   {
      ossim_uint32   lbox   = 0;
      ossim_uint32   tbox   = 0;
      ossim_uint64   xlbox  = 0;
      std::streamoff offsetToDbox = 0;
      
      while ( str.good() )
      {
         boxPos = str.tellg();

         readUInt32( lbox, str );
         readUInt32( tbox, str );
            
         if ( lbox == 1 )
         {
            readUInt64( xlbox, str );
            offsetToDbox = 16;
         }

         if ( !includeAll )
         {
            if ( lbox == 1 )
            {
               offsetToDbox = 16;
            }
            else
            {
               offsetToDbox = 8;
            }
         }

         if ( traceDebug() )
         {
            ossimNotify(ossimNotifyLevel_DEBUG)
               << "jp2.lbox: " << std::dec << lbox << "\n"
               << "jp2.tbox: " << std::hex << tbox << std::dec << "\n";
            printTboxType( tbox, ossimNotify(ossimNotifyLevel_DEBUG)  );
            
            if ( 1 == lbox )
            {
               ossimNotify(ossimNotifyLevel_DEBUG) << "jp2.xlbox: " << xlbox << std::endl;
            }
         }            

            
         if ( tbox == type )
         {
            //---
            // This is our box, copy it to box.
            // Not copying the lbox,tbox or xlbox
            std::streamoff boxSize = 0;
            if ( lbox == 0 )
            {
               // Last box, goes to end of file.
               str.seekg( 0, std::ios_base::end );
               boxSize = str.tellg() - boxPos;
            }
            else if ( lbox == 1 )
            {
               boxSize = xlbox;
            }
            else
            {
               boxSize = lbox;
            }
            boxSize -= offsetToDbox;
               
            // Seek to box data start
            str.seekg( boxPos + offsetToDbox, std::ios_base::beg );
               
            // std::vector::resize can throw a std::bad_alloc so wrap it...
            try
            {
               box.resize( boxSize );
               str.read( (char*)&box.front(), boxSize );
            }
            catch( std::exception& e )
            {
               std::ostringstream errMsg;
               errMsg << "ossimJp2Info::getBox caught exception on resize:\n"
                      << "Buffer size in bytes: " << boxSize
                      << "\n" << e.what() << std::endl;
               throw ossimException( errMsg.str() );
            }

            boxPosOfType = boxPos; // set up return value 
            break; // done...
         }
            
         if (lbox == 0) // last box?
         {
            break;
         }
            
         // Seek to the next box.
         std::streamoff nextBoxPos = boxPos;
         if (lbox == 1)
         {
            nextBoxPos+= (std::streamoff)xlbox;
         }
         else
         {
            nextBoxPos+= (std::streamoff)lbox;
         }

         // Go to next box:
         str.seekg(nextBoxPos, std::ios_base::beg);
            
      } // matches: while ( str.good() )
         
   } // matches: if ( str.good() )
   
   return boxPosOfType;
   
} // End: ossimJp2Info::getBox( type, includeAll, str, box ) const

std::streamoff ossimJp2Info::getGeotiffBox(
   std::ifstream& str, std::vector<ossim_uint8>& box ) const
{
   std::streamoff boxPos = 0;
   if ( str.good() )
   {
      const ossim_uint32 UUID_TYPE = 0x75756964;
      
      boxPos = getBox( UUID_TYPE, false, str, box );
      if ( boxPos && ( box.size() >= GEOTIFF_UUID_SIZE) )
      {
         const ossim_uint8 GEOTIFF_UUID[GEOTIFF_UUID_SIZE] = 
            {
               0xb1, 0x4b, 0xf8, 0xbd,
               0x08, 0x3d, 0x4b, 0x43,
               0xa5, 0xae, 0x8c, 0xd7,
               0xd5, 0xa6, 0xce, 0x03
            };
         
         if( memcmp( (char*)&box.front(), GEOTIFF_UUID, GEOTIFF_UUID_SIZE) != 0)
         {
            boxPos = 0;
            box.clear();
         }
      }
   }
   return boxPos;
}

std::streamoff ossimJp2Info::getGmlBox(
   std::ifstream& str, std::vector<ossim_uint8>& box ) const
{
   std::streamoff boxPos = 0;
   if ( str.good() )
   {
      const ossim_uint32 ASOC_TBOX_ID = 0x61736F63;
      const ossim_uint32 LBL_TBOX_ID  = 0x6C626C20;
      const ossim_uint32 XML_TBOX_ID  = 0x786D6C20;
      
      ossim_uint32 asoc0_data_length = 0;
      std::streamoff dataPosAsoc0 = findBoxData( ASOC_TBOX_ID, str, asoc0_data_length );

      if ( dataPosAsoc0 > 0 && asoc0_data_length > 0 )
      {
         ossim_uint32 lbl0_data_length = 0;
         std::streamoff dataPosLbl0 = findBoxData( LBL_TBOX_ID, str, lbl0_data_length );

         if ( dataPosLbl0 > 0 && lbl0_data_length > 0 )
         {
            std::string gmlDataStr;
            gmlDataStr.resize( lbl0_data_length );

            // Removed c++ 11 call for portablility...
            // str.read( (char*)&gmlDataStr.front(), lbl0_data_length );
            str.read( (char*)&gmlDataStr[0], lbl0_data_length );            

            // Do we have a GML data asoc ?
            if ( gmlDataStr.compare( 0, 8, "gml.data" ) == 0 )
            {
               // Seek to asoc 0 start of data
               str.seekg( dataPosAsoc0, std::ios_base::beg );

               ossim_uint32 asoc1_data_length = 0;
               std::streamoff dataPosAsoc1 = findBoxData( ASOC_TBOX_ID, str, asoc1_data_length );

               if ( dataPosAsoc1 > 0 && asoc1_data_length > 0 )
               {
                  ossim_uint32 lbl1_data_length = 0;
                  std::streamoff dataPosLbl1 = findBoxData( LBL_TBOX_ID, str, lbl1_data_length );

                  if ( dataPosLbl1 > 0 && lbl1_data_length > 0 )
                  {
                     std::string gmlRootInstanceStr;
                     gmlRootInstanceStr.resize( lbl1_data_length );
                     // Removed c++ 11 call for portablility...
                     // str.read( (char*)&gmlRootInstanceStr.front(), lbl1_data_length );
                     str.read( (char*)&gmlRootInstanceStr[0], lbl1_data_length );

                     // Do we have a GML root-instance asoc ?
                     if ( gmlRootInstanceStr.compare( 0, 17, "gml.root-instance" ) == 0 )
                     {
                        // Seek to asoc 1 start of data
                        str.seekg( dataPosAsoc1, std::ios_base::beg );

                        boxPos = getBox( XML_TBOX_ID, false, str, box );
                     }
                  }
               }
            }
         }
      }
   }
   return boxPos;
}

void ossimJp2Info::printTboxType( ossim_uint32 tbox, std::ostream& out ) const
{
   std::string boxType;

   const ossim_uint32 JP2_ASSOC_TYPE = 0x61736f63;
   const ossim_uint32 JP2_LABEL_TYPE = 0x6c626c20;
   const ossim_uint32 JP2_SIG_TYPE   = 0x6A502020;
   const ossim_uint32 FILE_TYPE      = 0x66747970;
   const ossim_uint32 JP2_HDR_TYPE   = 0x6A703268;
   const ossim_uint32 IMG_HDR_TYPE   = 0x69686472;
   const ossim_uint32 BPCC_TYPE      = 0x62706363;
   const ossim_uint32 COLR_TYPE      = 0x636F6C72;
   const ossim_uint32 PCLR_TYPE      = 0x70636C72;
   const ossim_uint32 CMAP_TYPE      = 0x636D6170;
   const ossim_uint32 CDEF_TYPE      = 0x63646566;
   const ossim_uint32 RES_TYPE       = 0x72657320;
   const ossim_uint32 RESC_TYPE      = 0x72657363;
   const ossim_uint32 RESD_TYPE      = 0x72657364;
   const ossim_uint32 JP2C_TYPE      = 0x6A703263;
   const ossim_uint32 JP2I_TYPE      = 0x6A703269;
   const ossim_uint32 XML_TYPE       = 0x786D6C20;
   const ossim_uint32 UUID_TYPE      = 0x75756964;

   switch( tbox )
   {
      case JP2_ASSOC_TYPE:
      {
         boxType = "jp2_assoc";
         break;
      }
      case JP2_LABEL_TYPE:
      {
         boxType = "jp2_label";
         break;
      }
      case JP2_SIG_TYPE:
      {
         boxType = "jp2_sig";
         break;
      }
      case FILE_TYPE:
      {
         boxType = "ftyp";
         break;
      }
      case JP2_HDR_TYPE:
      {
         boxType = "jp2h";
         break;
      }
      case IMG_HDR_TYPE:
      {
         boxType = "ihdr";
         break;
      }
      case BPCC_TYPE:
      {
         boxType = "bpcc";
         break;
      }
      case COLR_TYPE:
      {
         boxType = "colr";
         break;
      }
      case PCLR_TYPE:
      {
         boxType = "pclr";
         break;
      }
      case CMAP_TYPE:
      {
         boxType = "cmap";
         break;
      }
      case CDEF_TYPE:
      {
         boxType = "cdef";
         break;
      }
      case RES_TYPE:
      {
         boxType = "res";
         break;
      }
      case RESC_TYPE:
      {
         boxType = "resc";
         break;
      }
      case RESD_TYPE:
      {
         boxType = "resd";
         break;
      }
      case JP2C_TYPE:
      {
         boxType = "jp2c";
         break;
      }
      case JP2I_TYPE:
      {
         boxType = "jp2i";
         break;
      }
      case XML_TYPE:
      {
         boxType = "xml";
         break;
      }
      case UUID_TYPE:
      {
         boxType = "uuid";
         break;
      }
      default:
      {
         boxType = "unhandled";
         break;
      }
   }

   out << "jp2.box_type: " << boxType << "\n";
}
