//
// Copyright (c) 1997,1998 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: PNG Dump Application
//
//  Author:  John M. Miano miano@colosseumbuilders.com
//
//  Description:
//
//    This is a simple application that prints the structure of a PNG file
//    to cout.
//
//    pngdump filename.png
//

#include <iostream>
#include <fstream>
#include <errno.h>
#include <string>
#include <stdexcept>

#include "datatype.h"
#include "pngpvt.h"

using namespace std ;

UBYTE4 CrcTable [256] ;
UBYTE4 Register ;

UBYTE1 CurrentChunkType [5] ;
UBYTE1 *ChunkData ;
UBYTE4 ChunkDataLength ;
UBYTE4 ChunkBufferSize ;
unsigned int ChunkCount ;
bool IENDReached ;
bool PLTEReached ;
unsigned int LastIDATChunk ;

PngImageHeader ImageHeader ;

unsigned long CrcRegister ;
void CrcByte (unsigned char data)
{
  unsigned int index = (CrcRegister ^ data) & 0xFF ;
  CrcRegister = CrcTable [index] ^ ((CrcRegister >> 8) & 0x00FFFFFF) ;
  return ;
}

unsigned long Crc (UBYTE1 type [5], UBYTE1 buffer [], unsigned int length)
{
  CrcRegister = 0xFFFFFFFFL ;
  for (unsigned int ii = 0 ; ii < 4 ; ++ ii)
    CrcByte (type [ii]) ;

  for (unsigned int jj = 0 ; jj < length ; ++ jj)
    CrcByte (buffer [jj]) ;

  return ~CrcRegister ;
}

void MakeCrcTable ()
{
  for (unsigned int ii = 0 ; ii < 256 ; ++ ii)
  {
    CrcTable [ii] = ii ;
    for (unsigned int jj = 0 ; jj < 8 ; ++ jj)
    {
      if ((CrcTable [ii] & 0x1) == 0)
        CrcTable [ii] >>= 1 ;
      else
        CrcTable [ii] = 0xEDB88320L ^ (CrcTable [ii] >> 1) ;
    }
  }
  return ;
}

UBYTE4 ReadLong (istream &strm)
{
  UBYTE4 data ;
  strm.read ((char *) &data, sizeof (data)) ;
  return BigEndianToSystem (data) ;
}

void ReadSignature (istream &strm)
{
  static const int sigsize = 8 ;
  static const unsigned char signature [sigsize] = { 137, 80, 78, 71, 13,
                                                     10, 26, 10, } ;
  char sigbuf [sigsize] ;

  strm.read (sigbuf, sigsize) ;
  if (memcmp (sigbuf, signature, sigsize) != 0)
    cout << "*** PNG Signature Invalid ***" << endl ;
  return ;
}

void ProcessHeader (istream &strm)
{
  PngImageHeader *header = (PngImageHeader *) ChunkData ;
  ImageHeader = *header ;

  if (ChunkCount != 0)
    cout << "*** IHDR must be the first chunk ***" << endl ;
  if (ChunkDataLength != sizeof (PngImageHeader))
    cout << "*** Invalid IHDR Chunk Length ***" << endl ;

  cout << "  Image Size: " << dec << BigEndianToSystem (header->height)
       << " x " << dec << BigEndianToSystem (header->width) << endl ;
  cout << "  Bit Depth: " << (int) header->bitdepth << endl ;
  switch (header->colortype)
  {
  case 0:
    cout << "  Color Type: Grayscale" << endl ;
    switch (header->bitdepth)
    {
    case 1: case 2: case 4: case 8: case 16: break ;
    default:
      cout << "Bad Bitdepth (" << header->bitdepth << ") for Color Type" << endl ;
    }
    break ;
  case 2:
    cout << "  Color Type: RGB Triple" << endl ;
    if (header->bitdepth != 8 && header->bitdepth != 16)
      cout << "*** Bad Bitdepth for Color Type ***" << endl ;
    break ;
  case 3:
    cout << "  Color Type: Palette Index" << endl ;
    switch (header->bitdepth)
    {
    case 1: case 2: case 4: case 8: break ;
    default:
      cout << "*** Bad Bitdepth (" << header->bitdepth << ") for Color Type ***"
           << endl ;
    }
    break ;
  case 4:
    cout << "  Color Type: Grayscale/Alpha" << endl ;
    if (header->bitdepth != 8 && header->bitdepth != 16)
      cout << "*** Bad Bitdepth (" << header->bitdepth << ") for Color Type ***"
           << endl ;
    break ;
  case 6:
    cout << " Color Type: RGB/Alpha" << endl ;
    if (header->bitdepth != 8 && header->bitdepth != 16)
      cout << "*** Bad Bitdepth (" << header->bitdepth << ") for Color Type ***"
           << endl ;
    break ;
  default:
    cout << "*** Invalid Color Type (" << header->colortype << ") ***" << endl ;
  }
  if (header->compressionmethod == 0)
  {
    cout << "  Compression Method: deflate/inflate"
         << endl ;
  }
  else
  {
    cerr << "Invalid Compression Method (" << header->compressionmethod << ")" << endl ;
  }

  if (header->filtermethod == 0)
  {
    cout << "  Filter Method: adaptive" << endl ;
  }
  else
  {
    cout << "*** Invalid Filter Method (" << header->filtermethod << ") ***"
         << endl ;
  }

  switch (header->interlacemethod)
  {
  case 0:
    cout << "  Interlace Method:  none" << endl ;
    break ;
  case 1:
    cout << "  Interlace Method:  Adam7" << endl ;
    break ;
  default:
    cout << "*** Invalid Interlace Method ("
         << header->interlacemethod << ") ***" << endl ;
  }
  return ;
}

void ProcessIDAT (istream &strm)
{
  if (LastIDATChunk != 0 && LastIDATChunk != ChunkCount - 1)
    cout << "*** IDAT blocks are not consecutive. ***" << endl ;
  if (ImageHeader.colortype == Palette && ! PLTEReached)
    cout << "*** IDAT requires PLTE chunk for this color type. ***" << endl ;
  LastIDATChunk = ChunkCount ;
  return ;
}

void ProcessPalette (istream &strm)
{
  if (PLTEReached)
    cout << "*** File contains multiple PLTE Chunks ***" << endl ;
  if (ChunkDataLength % 3 != 0)
    cout << "*** PLTE Block length not divisible by 3 ***" << endl ;
  if (ChunkDataLength > 3 * (1 << ImageHeader.bitdepth) ||
      ChunkDataLength > 256 * 3)
    cout << "*** PLTE Block length too large ***" << endl ;
  if (LastIDATChunk != 0)
    cout << "*** PLTE May not occur after the first IDAT chunk ***" << endl ;
  if (ImageHeader.colortype == Grayscale
      || ImageHeader.colortype == GrayscaleAlpha)
    cout << "*** PLTE May not occur with Grayscale or Grayscale"
         << " with Alpha Channel. ***" << endl ;

  PLTEReached = true ;
  struct PaletteEntry
  {
    UBYTE1 red ;
    UBYTE1 green ;
    UBYTE1 blue ;
  } palette [256] ;

  unsigned int colorcount = ChunkDataLength / 3 ;

  cout << "  Palette Color Count: " << colorcount << endl ;
  memcpy (palette, ChunkData, colorcount * sizeof (PaletteEntry));
  return ;
}

void ReadBlock (istream &strm)
{
  ChunkDataLength = ReadLong (strm) ;
  if (strm.eof ())
    return ;
  if (ChunkDataLength > ChunkBufferSize)
  {
    ChunkBufferSize = ChunkDataLength ;

    delete [] ChunkData ;
    ChunkData = new UBYTE1 [ChunkBufferSize] ;
  }
  strm.read ((char*)CurrentChunkType, 4) ;
  strm.read ((char*) ChunkData, ChunkDataLength) ;


  UBYTE4 datacrc = Crc (CurrentChunkType, ChunkData, ChunkDataLength) ;
  UBYTE4 filecrc = ReadLong (strm) ;

  cout << "{ " << CurrentChunkType << endl ;
  cout << "  Data Length: " << ChunkDataLength << endl ;
  cout << "  Data CRC:    " << hex << datacrc << endl ;
  cout << "  File CRC:    " << hex << filecrc << endl ;
  if (filecrc != datacrc)
    cout << "*** Chunk CRC is Incorrect ***" << endl ;

  UBYTE4 numericcode = ChunkType ((char*) CurrentChunkType) ;
  if (ChunkCount == 0 && numericcode != ChunkType ("IHDR"))
    cout << "*** First chunk is not IHDR ***" << endl ;
  if (IENDReached)
    cout << "*** Chunk follows IEND ***" << endl ;

  if (numericcode == ChunkType ("IHDR"))
  {
    ProcessHeader (strm) ;
  }
  else if (numericcode == ChunkType ("IDAT"))
  {
    ProcessIDAT (strm) ;
  }
  else if (numericcode == ChunkType ("PLTE"))
  {
    ProcessPalette (strm) ;
  }
  else if (numericcode == ChunkType ("IEND"))
  {
    IENDReached = true ;
    if (LastIDATChunk == 0)
      cout << "*** Image Contains No IDAT chunks ***" << endl ;
    if (ChunkDataLength != 0)
      cout << "*** IEND Chunk may not contain data ***" << endl ;
  }
  else
  {
  }
  cout << "}" << endl ;
  return ;
}

main (int argc, char *argv [])
{
  if (argc != 2)
  {
    cerr << "Usage: " << argv [0] << " filename" << endl ;
    return 1 ;
  }

  ifstream in (argv [1], ios::binary) ;
  if (! in)
  {
    cerr << "Can't open file " << argv [1] << endl ;
    cerr << strerror (errno) << endl ;
    return errno ;
  }

  MakeCrcTable () ;
  try
  {
    ReadSignature (in) ;
    for (ChunkCount = 0 ; ! in.eof () ; ++ ChunkCount)
      ReadBlock (in) ;
  }
  catch (exception &ee)
  {
    cerr << ee.what () << endl ;
  }
  return 0 ;
}
