//
// Copyright (c) 1997,1998,2004 Colosseum Builders, Inc.
// All rights reserved.
//
// Colosseum Builders, Inc. makes no warranty, expressed or implied
// with regards to this software. It is provided as is.
//
// See the README.TXT file that came with this software for restrictions
// on the use and redistribution of this file or send E-mail to
// info@colosseumbuilders.com
//

//
//  Title: JPEGDump
//
//  Author:  John M. Miano
//
//  Description:
//
//    This is a simple application for displaying the block structure
//    of a JFIF or Exif (JPEG) file.
//
//    jpegdump filename.jpg
//
//
//  Changes
//
//    April 2004 - Added limited support for Exif. This code does not
//                 work on BigEndian Systems.
//
#include <fstream>
#include <iostream>
#include <iomanip>
#include <cerrno>
#include <string>

using namespace std ;

#include "systemspecific.h"
#include "jfif.h"
#include "jpgpvt.h"
#include "databuffer.h"
#include "exif.h"

using namespace ColosseumPrivate ;

typedef Colosseum::DataBuffer<char> CHARBUFFER ;

struct
{
  int marker ;
  char *name ;
} Markers [] =
{
#define JPEGMARKER(name,code,string) {code, string}
#include "jpgmkdef.h"
} ;

string ExifFieldName (UBYTE2 code)
{
 using namespace Exif ;
 for (int ii = 0 ; TagNames [ii].field_name != 0 ; ++ ii)
 {
   if (TagNames [ii].tag_id == code)
     return TagNames [ii].field_name ;
 }
 return "" ;
}

void PrintMarkerName (int marker)
{
  for (unsigned int ii = 0 ; ii < sizeof (Markers) / sizeof (Markers [0]) ; ++ ii)
  {
    if (Markers [ii].marker == marker)
    {
      cout << "{ " << Markers [ii].name << " " ;
      return ;
    }
  }
  cout << setfill ('0') << "{ Unknown Marker ff" << setw (2) << hex << marker << dec << " " << setfill ('\000') ;
  return ;
}

// This function reads a single byte from the input stream.
UBYTE1 ReadByte (istream &strm)
{
  UBYTE1 data ;
  strm.read ((char*) &data, sizeof (data)) ;
  return data ;
}

// This function returns the next two byte integer in the input stream in
// the correct format for the processor.
UBYTE2 ReadWord (istream &strm)
{
  UBYTE1 hi = ReadByte (strm) ;
  UBYTE1 lo = ReadByte (strm) ;
  return (hi << 8 | lo) ;
}

// This function dumps the contents of a start of frame or DHP marker to the
// output stream. The start of frame marker defines the size of the image,
// the number of components, and the attributes of the components.
void ReadStartOfFrame (istream &strm, unsigned int type)
{
  cout << endl ;
  // Section B.2.2 (SOF)
  // Section B.3.2 (DHP - which says is identical to B.2.2)

  cout << "  Length: " << dec << ReadWord (strm) << endl ;
  cout << "  Precision: " << dec << (unsigned int)ReadByte (strm) << endl ; // P in standard
  cout << "  Height: " << dec << ReadWord (strm) << endl ;            // Y in standard
  cout << "  Width: " << dec << ReadWord (strm) << endl ;             // X in standard
  int component_count = ReadByte (strm) ;     // Nf in standard
  cout << "  Component Count: " << component_count << endl ;

  for (int ii = 0 ; ii < component_count ; ++ ii)
  {
    cout << "   Component " << (unsigned int) ReadByte (strm) << endl ; // Ci in standard
    UBYTE1 data = ReadByte (strm) ;
    cout << "    Horizontal Frequency: " << (data >> 4) << endl ; // Hi in standard
    cout << "    Vertical Frequency: " << (data & 0xF) << endl ; // Vi in standard
    cout << "    Quantization Table: " << (unsigned int) ReadByte (strm) << endl ;  // Tqi in standard
  }
  return ;
}

// This function dumps the contents of a DHT marker to the output stream.
// Huffman tables are stored in two parts. First there is an array containing
// 16 values where each value is the count of codes for with a given length.
// The next is a variable array that contains the Huffman values. The number
// of values is the sum of all the counts.
void ReadDefineHuffmanTables (istream &strm)
{
  unsigned int jj ;

  cout << endl ;

  // Section B.2.4.2

   UBYTE2 length = ReadWord (strm) ;
   cout << "  Length: " << dec << length << endl ;
   int remaining = length - sizeof (length) ;
   while (remaining > 0 && ! strm.eof ())
   {
      UBYTE1 data = ReadByte (strm) ; -- remaining ;
      unsigned int tableclass = data >> 4 ;    // Tc in standard 0=>DC, 1=>AC
      unsigned int id = data & 0x0F ;          // Th in standard

      cout << "   Table Index " << (int) id << endl ;
      if (tableclass == 0)
        cout << "    Table Class: DC" << endl ;
      else
        cout << "    Table Class: AC" << endl ;

      cout << "    Code Counts: " ;
      int count = 0 ;
      for (jj = 0 ; jj < 16 ; ++ jj)
      {
        UBYTE1 codecount = ReadByte (strm) ;
        // These values are called Li in the standard.
        cout << hex << (unsigned int) codecount << " " ; -- remaining ;
        count += codecount ;
      }
      cout << endl ;
      cout << "    Code Values: " ;
      for (jj = 0 ; jj < count ; ++ jj)
      {
        // These values are called Vi in the standard.
        if (jj % 16 == 0 && jj != 0)
          cout << endl << "                 " ;
        cout << setw (2) << hex << (unsigned int) ReadByte (strm)
             << " "  ; -- remaining ;
      }
      cout << endl ;
   }
   if (remaining < 0)
    cout << "-- Error in block size -- " << - remaining << endl ;

   return ;
}

// This function reads a DAC block from the input stream. Since Arithmetic
// coding is covered by patents we ignore the contents and just dump them
// to the output stream.
void ReadDefineArithmeticCoding (istream &strm)
{
  // The actual format is in B.2.4.3

  cout << endl ;
  unsigned int length = ReadWord (strm) ;
  cout << "  Length: " << dec << length << endl ;

  int count = 0 ;
  for (length -= sizeof (length) ; length > 0 ; -- length)
  {
    cout << " " << hex << (int) ReadByte (strm) ;
    if (count >= 15)
    {
      count = 0 ;
      cout << endl ;
    }
    else
    {
      ++ count ;
    }
  }
  return ;
}

// This function reads an SOS marker and dumps its contents to the output
// stream. The scan data immediately follows the header but the data is not
// dumped here.
void ReadStartOfScan (istream &strm)
{
  cout << endl ;
  // Section B.2.3

  cout << "  Length:  " << dec << ReadWord (strm) << endl ;

  int scan_component_count = ReadByte (strm) ;  // Ns in standard
  cout << "  Scan Count: " << dec << scan_component_count << endl ;
  for (int ii = 0 ; ii < scan_component_count ; ++ ii)
  {
    cout << "   Component ID: " << dec << (unsigned int) ReadByte (strm) << endl ;  // Csi in standard
    UBYTE1 rb = ReadByte (strm) ;
    cout << "    AC Entropy Table: " << dec << (rb & 0x0F) << endl  ;  // Tai in standard
    cout << "    DC Entropy Table: " << dec << (rb >> 4) << endl  ;    // Tdi in standard
  }

  cout << "  Spectral Selection Start: " << dec << (unsigned int) ReadByte (strm) << endl ; // Ss in standard
  cout << "  Spectral Selection End: " << dec << (unsigned int) ReadByte (strm) << endl ;  // Se in standard
  UBYTE1 ssa = ReadByte (strm) ;
  cout << "  Sucessive Approximation High: " << dec <<(ssa >> 4) << endl ;  // Ah in standard
  cout << "  Sucessive Approximation Low: " <<  dec << (ssa & 0x0F) << endl  ; // Al in standard

  return ;
}

// This function reads the contents of a DQT marker and dumps it to the
// output stream. The quantization values can either be 8 or 16 bits. The
// values are stored in the file in zigzag order.
void ReadDefineQuantizationTable (istream &strm)
{
  cout << endl ;
  // Defined in Section B.2.4.1

  UBYTE2 length = ReadWord (strm) ;

  UBYTE1 data ;
  int remaining = length - sizeof (length) ;
  cout << "  Length:  " << dec << length << endl ;

  while (remaining > 0)
  {
    data = ReadByte (strm) ; -- remaining ;
    unsigned int precision = data >> 4 ;    // Pq in standard
    unsigned int index = data & 0x0F ;      // Tq in standard
    cout << "  Table Index: " << dec << index << endl ;
    cout << "  Table Precision: " << dec << precision << endl ;

    cout << "  Table Values: " ;
    if ((precision & 0xF0) != 0)
    {
      for (unsigned int ii = 0 ; ii < 64 ;)
      {
        cout << endl << "        " ;
        for (int jj = 0 ; jj < 8 && ii < 64 ; ++ ii, ++ jj)
        {
          cout << dec << ReadWord (strm) << " " ;
          remaining -= 2 ;
        }
      }
    }
    else
    {
      for (unsigned int ii = 0 ; ii < 64 ;)
      {
        cout << endl << "        "  ;
        for (int jj = 0 ; jj < 8 && ii < 64 ; ++ ii, ++ jj)
        {
          cout << dec << (unsigned int) ReadByte (strm) << " " ;
          -- remaining ;
        }
      }
    }
    cout << endl ;
  }

  return ;
}

// This function reads a DNL marker from the stream.
void ReadDefineNumberOfLines (istream &strm)
{
  // Defined in Section B.2.5
  cout << endl ;
  cout << "  Length: " << dec << ReadWord (strm) << endl ; // Should be 4
  cout << "  Number of Lines: " << dec << ReadWord (strm) << endl ;
  return ;
}

// This function reads a DRI marker from the input stream.
void ReadDefineRestartInterval (istream &strm)
{
  cout << endl ;
  // Defined in Section B.2.4.4
  cout << "  Length:  " << dec << ReadWord (strm) << endl ; // Should be 4
  cout << "  Interval: " << dec << ReadWord (strm) << endl ;
  return ;
}

// This function reads an EXP marker from the input stream.
void ReadExpandReferenceComponents (istream &strm)
{
  cout << endl ;
  cout << "  Length: " << ReadWord (strm) << endl ; // Should be 3 ;
  UBYTE1 exp = ReadByte (strm) ;
  if ((exp & 0xF0) == 0)
    cout << "  Expand Horizontally: False" << endl ;
  else
    cout << "  Expand Horizontally: True" << endl ;
  if ((exp & 0x0F) == 0)
    cout << "  Expand Vertically: False" << endl ;
  else
    cout << "  Expand Vertically: True" << endl ;
  return ;
}

// This function reads a JFIF header.
void DumpJfif (CHARBUFFER &buffer)
{
  using namespace Jfif ;
  // Information for decoding the APP0 block comes from:
  // JPEG File Interchange Format
  // Version 1.02
  JfifHeader jfifHeader ; // Dummy 
  if (buffer.getSize () < jfifHeader.size)
  {
    cout << "%%% Invalid JFIF APP0 Marker: Too small %%%" << endl ;
    return ;
  }

  JfifHeader header (&buffer [0]) ;

  cout << "  Version: " << dec << (unsigned int) header.version [0] << "." << (unsigned int) header.version [0] << endl ;

  // density unit = 0 => Only the aspect ratio is specified.
  // density unit = 1 => Density in pixels per inch.
  // density unit = 2 => Density in pixels per centimeter.
  cout << "  Density Unit: " ;
  switch (*header.units)
  {
  case 0:
    cout << " (aspect ratio)" << endl ;
    break ;
  case 1:
    cout << " (pixels per inch)" << endl ;
    break ;
  case 2:
    cout << " (pixels/cm)" << endl ;
    break ;
  default:
    cout << " (????)" << endl ;
    break ;
  }
  cout << "  X Density: " << dec << BigEndianToSystem (*header.xdensity) << endl ;
  cout << "  Y Density: " << dec << BigEndianToSystem (*header.xdensity) << endl ;

  cout << "  Thumbnail Width: " << dec << (unsigned int) *header.xthumbnail << endl ;
  cout << "  Thumbnail Height: " << dec << (unsigned int) *header.xthumbnail << endl ;

  return ;
}
// This function reads an Exif header.
void DumpExif (CHARBUFFER &buffer)
{
  using namespace Exif ;
  const int START = 6 ;
  TiffHeader &th = *reinterpret_cast<TiffHeader *>(&buffer [START]) ;
  switch (th.byte_order)
  {
  case 'MM':
    cout << "  Byte Order: Big Endian" << endl ;
    break ;
  case 'II':
    cout << "  Byte Order: Little Endian" << endl ;
    break ;
  } ;

  unsigned int offset = BigEndianToSystem (th.ifd_offset) ;
  cout << "  Offset of IFD: " << offset << endl ;

  offset += START ;
  UBYTE2 &fieldcount =  *reinterpret_cast<UBYTE2*>(&buffer [offset]) ;
  fieldcount = BigEndianToSystem (fieldcount) ;
  offset += sizeof (UBYTE2) ;
  cout << "  Field Count: " << fieldcount << endl ;
  for (unsigned int ii = 0 ; ii < fieldcount ; ++ ii, offset += sizeof (Ifd))
  {
    cout << ii + 1 << endl ;
    Ifd &ifd1 = *reinterpret_cast<Ifd*>(&buffer [offset]) ;
    cout << ExifFieldName (BigEndianToSystem (ifd1.tag)) << endl ;
    cout << "           Tag: " << hex << BigEndianToSystem (ifd1.tag) << dec << endl ;
    cout << "          Type:  " << hex << BigEndianToSystem (ifd1.type) << dec << endl ;
    cout << "         Count: " << BigEndianToSystem (ifd1.count) << endl ;
    cout << "  Value Offset: " << hex << BigEndianToSystem (ifd1.value_offset) << dec << endl ;

    if (BigEndianToSystem (ifd1.type) == 2)
    {
      cout << &buffer [START + BigEndianToSystem (ifd1.value_offset)] << endl ;
    }
  }
}

// This function read an APPx or COM marker from the input stream and
// dumps its contents.
void ReadApp (istream &strm, unsigned int data)
{
  cout << endl ;
  // Defined in Section B.2.4.5 (COM)
  // Defined in Section B.2.4.6 (APPx)

  UBYTE2 length = ReadWord (strm) ;
  cout << "  Length: " << dec << length << endl ;

  if (length < sizeof (length))
  {
    cout << "%%% Marker length is invalid %%%" << endl ;
    return ;
  }
  else if (length == sizeof (length))
  {
    // The marker contains no data.
    return ;
  }
  CHARBUFFER buffer (length - sizeof (length)) ;
  strm.read (&buffer [0], buffer.getSize ()) ;

  // See if the APP marker has an ID string.
  for (unsigned int ii = 0 ; ii < buffer.getSize () ; ++ ii)
  {
    if (buffer [ii] == '\000')
    {
      string id = &buffer [0] ;
      cout << "  Marker ID: " << id << endl ;
      if (data == APP0 && id == "JFIF")
        DumpJfif (buffer) ;
      else if (data == APP1 && id == "Exif") 
        DumpExif (buffer) ;
      break ;
    }
    else if (! isprint (buffer [ii]))
    {
      // If we hit a nonprinting character before the null terminator
      // there cannot be an ID string.
      break ;
    }
  }

  return ;
}

void ReadMarker (istream &strm, bool verbose)
{
  // Find the next marker in the input stream. Dump data that is not a
  // marker. We expect to dump raw data following a SOS marker. We
  // also dump data when we have a bad marker.
  UBYTE1 data = ReadByte (strm) ;
  if (data != SOB)
  {
    unsigned int count = 0 ;
    while (data != SOB)
    {
ff00: // Special case of FF00 marker
      if (strm.eof ())
        return ;
      if (verbose)
      {
        cout << hex << (unsigned int) data << " " ;
        if (count >= 15)
        {
          cout << endl ;
          count = 0 ;
        }
        else
        {
          ++ count ;
        }
      }
      data = ReadByte (strm) ;
    }
    data = ReadByte (strm) ;
    if (data == 0)
    {
      // FF00 is used to represent the value FF in the output stream.
      data = 0xFF ;
      goto ff00 ;
    }
    cout << endl ;
  }
  else
  {
    data = ReadByte (strm) ;
    // According to E.1.2, 0xFF is allowed as fill when a marker is
    // expected.
    while (data == SOB && ! strm.eof ())
      data = ReadByte (strm) ;
  }
  PrintMarkerName (data) ;
  switch (data)
  {
  case SOF0:
  case SOF1:
  case SOF2:
  case SOF3:
  case SOF5:
  case SOF6:
  case SOF7:
  case SOF9:
  case SOFA:
  case SOFB:
  case SOFD:
  case SOFE:
  case SOFF:
  case DHP:
  case SOFLS:
    ReadStartOfFrame (strm, data) ;
    break ;
  case DHT:
    ReadDefineHuffmanTables (strm) ;
    break ;
  case DAC:
    ReadDefineArithmeticCoding (strm) ;
    break ;
  case SOS:
    ReadStartOfScan (strm) ;
    break ;
  case DQT:
    ReadDefineQuantizationTable (strm) ;
    break ;
  case DNL:
    ReadDefineNumberOfLines (strm) ;
    break ;
  case DRI:
    ReadDefineRestartInterval (strm) ;
    break ;
  case EXP:
    ReadExpandReferenceComponents (strm) ;
    break ;
  case APP0:
  case APP1:
  case APP2:
  case APP3:
  case APP4:
  case APP5:
  case APP6:
  case APP7:
  case APP8:
  case APP9:
  case APPA:
  case APPB:
  case APPC:
  case APPD:
  case APPE:
  case APPF:
  case COM:
    ReadApp (strm, data) ;
  }
  cout << "}" << endl ;
  return ;
}

void Usage (char *program)
{
  cerr << "Usage: " << program << " [options] file-name" << endl << flush ;
  cerr << " -V Verbose (Dump Raw Data)" << endl ;
  return ;
}

main (int argc, char *argv [])
{
  ifstream strm ;
  bool verbose = false ;

  int argn ;
  for (argn = 1 ; argn < argc && argv [argn][0] == '-' ; ++ argn)
  {
    if (strcmp (argv [argn], "-v") == 0)
      verbose = true ;
    else
    {
      Usage (argv [0]) ;
      return 1 ;
    }
  }
  if (argn == argc)
  {
    Usage (argv [0]) ;
    return 1 ;
  }

  for ( ; argn < argc ; ++ argn)
  {
    strm.open (argv [argn], ios::binary) ;
    if (! strm)
    {
      cerr << "Can't open file '" << argv [1] << "'" << endl
           << strerror (errno) << endl << flush ;
      return errno ;
    }

    while (! strm.eof ())
      ReadMarker (strm, verbose) ;

    strm.close () ;
  }

  return 0 ;
}

