/********************************COPYRIGHT*************************************

    Copyright (C) 2003-2007 Brendt Wohlberg  <software@wohlberg.net>

    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License as
    published by the Free Software Foundation; either version 2 of the
    License, or (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Library General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this library; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

********************************COPYRIGHT*************************************/


#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <popt.h>
#include <magick/api.h>
#if defined(HAVE_LCMS_H)
#include <lcms.h>
#else 
#if defined(HAVE_LCMS_LCMS_H)
#include <lcms/lcms.h>
#endif
#endif

#define POPT_OPTARGS_BROKEN 1

/* MD5 headers */
#include "md5.h"

/* MD5 functions */
static void signature(md5_state_t *pms, Image *image);
static void printdigest(FILE* fp, md5_byte_t* digest);

/* Output formatting */
void printspace(int n);

/* Information extraction and display functions */
static void pformat(Image *image);
static void pfmtdscr(Image *image);
static void pdepth(Image *image);
static void psize(Image *image);
static void pgeom(Image *image);
static void pwidth(Image *image);
static void pheight(Image *image);
#ifndef POPT_OPTARGS_BROKEN
static void porient(Image *image, float orientswitch);
#endif
static void piccname(Image *image);
static void piccmd5(Image *image);
static void pmd5hash(Image *image);
static void psha256hash(Image *image);
static int wiccfile(Image *image, const char* iccfile);


int main(int argc, const char *argv[])
{
  char* iccfile = 0;
  poptContext poptctxt;
  struct poptOption poptopt[] = {
    {"format", '\0', POPT_ARG_NONE, 0, 1, "Display image format", 0},
    {"fmtdscr", '\0', POPT_ARG_NONE, 0, 2, 
     "Display image format description", 0},
    {"size", '\0', POPT_ARG_NONE, 0, 3, "Display image size", 0},
    {"depth", '\0', POPT_ARG_NONE, 0, 4, "Display bit depth", 0},
    {"geom", '\0', POPT_ARG_NONE, 0, 5, "Display image geometry", 0},
    {"width", '\0', POPT_ARG_NONE, 0, 6, "Display image width", 0},
    {"height", '\0', POPT_ARG_NONE, 0, 7, "Display image height", 0},
#ifndef POPT_OPTARGS_BROKEN
    {"orient", '\0', POPT_ARG_FLOAT | POPT_ARGFLAG_OPTIONAL, 0, 8, 
     "Display image orientation; 'P' for portrait, 'L' for landscape, "
     "and 'S' for square (when width and height differ by less than "
     "offsquare\%)", "offsquare"},
#endif
    {"iccname", '\0', POPT_ARG_NONE, 0, 9, "Display ICC profile name", 0},
    {"iccfile", '\0', POPT_ARG_STRING, &iccfile, 0,
     "Save ICC profile to file", "file"},
    {"iccmd5", '\0', POPT_ARG_NONE, 0, 10, "Display MD5 hash of ICC profile",
     0},
    {"md5hash", '\0', POPT_ARG_NONE, 0, 11, "Display MD5 hash of image", 0},
    {"sha256hash",'\0', POPT_ARG_NONE, 0, 12, "Display SHA-256 hash of image",
     0},
    {"version", '\0', POPT_ARG_NONE, 0, 13, "Display imageinfo version", 0},
    POPT_AUTOHELP
    POPT_TABLEEND
  };
  const char* filename = 0;
  const char* optarg = 0;
  float orientswitch = 5.0;
  int optorder[16] = {0}, k, l;
  int na = 0;
  int readimage = 0;
  ExceptionInfo exception;
  Image *image = 0;
  ImageInfo *image_info = 0;

  /* Parse command line options */
  poptctxt = poptGetContext("imageinfo", argc, argv, poptopt, 0);
  poptSetOtherOptionHelp(poptctxt, "<image file>");
  k = 0;
  while ((l = poptGetNextOpt(poptctxt)) > 0) {
    optorder[k] = l;
    switch (l) {
    case 8: optarg = poptGetOptArg(poptctxt);
      if (optarg)
	orientswitch = atof(optarg);
      break;
    case 13: printf("imageinfo %s\n", IMINVERSION);
      poptFreeContext(poptctxt);
      exit(0);
    }
    k++;
  }
  if (l < -1) {
    fprintf(stderr, "%s: %s\n",poptBadOption(poptctxt, POPT_BADOPTION_NOALIAS),
	    poptStrerror(l));
    poptFreeContext(poptctxt);
    exit(1);
  }
  filename = poptGetArg(poptctxt);
  if (poptGetArg(poptctxt) != NULL) {
    fprintf(stderr, "imageinfo: must specify a single filename\n");
    poptFreeContext(poptctxt);
    exit(2);
  }
  poptFreeContext(poptctxt);
  if (!filename) {
    fprintf(stderr, "imageinfo: must specify a filename\n");
    exit(3);
  }
  if (optorder[0] == 0) {
    fprintf(stderr, "imageinfo: must specify at least one output flag\n");
    exit(4);
  }

  /* Determine whether image needs to be read or "pinged" */
  k = 0;
  while(!readimage && optorder[k] && k < 16) {
    switch (optorder[k]) {
    case 11:
    case 12:
      readimage = 1;
    }
    k++;
  }

  /* Initialise ImageMagick */
  InitializeMagick(argv[0]);

  /* Read or "ping" image */
  GetExceptionInfo(&exception);
  image_info=CloneImageInfo((ImageInfo *) NULL);
  strcpy(image_info->filename, filename);
  if (readimage)
    image = ReadImage(image_info, &exception);
  else
    image = PingImage(image_info, &exception);
  if (!image) {
    /* Use MagickWarning rather than MagickError beacuse the latter
       behaves incorrectly when an empty file is encountered */
    MagickWarning(exception.severity,exception.reason,exception.description);
    DestroyImageInfo(image_info);
    DestroyExceptionInfo(&exception);
    DestroyMagick();
    exit(5);
  }

  /* Call information display functions in command line switch order */
  k = 0;
  while(optorder[k] && k < 16) {
    switch (optorder[k]) {
    case  1: printspace(na); pformat(image); na++; break;
    case  2: printspace(na); pfmtdscr(image); na++; break;
    case  3: printspace(na); psize(image); na++; break;
    case  4: printspace(na); pdepth(image); na++; break;
    case  5: printspace(na); pgeom(image); na++; break;
    case  6: printspace(na); pwidth(image); na++; break;
    case  7: printspace(na); pheight(image); na++; break;
#ifndef POPT_OPTARGS_BROKEN
    case  8: printspace(na); porient(image, orientswitch); na++; break;
#endif
    case  9: printspace(na); piccname(image); na++; break;
    case 10: printspace(na); piccmd5(image); na++; break;
    case 11: printspace(na); pmd5hash(image); na++; break;
    case 12: printspace(na); psha256hash(image); na++; break;
    }
    k++;
  }

  /* Write ICC profile file if requested */
  if (iccfile && !wiccfile(image, iccfile)) {
    fprintf(stderr, "imageinfo: error writing ICC profile to %s\n", iccfile);
    DestroyImageInfo(image_info);
    DestroyExceptionInfo(&exception);
    DestroyImage(image);
    DestroyMagick();
    exit(6);
  }

 
  /* Clean up */
  DestroyImageInfo(image_info);
  DestroyExceptionInfo(&exception);
  DestroyImage(image);
  DestroyMagick();

  return(0);
}



void signature(md5_state_t *pms, Image *image) {
  PixelPacket *pp = NULL;
  IndexPacket *ip = NULL;
  PixelPacket *buffer = NULL;
  unsigned int k, npixels, r;

  /* Work through image row by row */
  for (r = 0; r < image->rows; r++) {
    pp = NULL;
    ip = NULL;
    npixels = image->columns;
    pp = GetImagePixels(image, 0, r, image->columns, 1);
    if (pp == NULL) {
      if (image->storage_class == DirectClass) {
	fprintf(stderr,"signature: error getting DirectClass image pixels\n");
	exit(1);
      } else if (image->storage_class == PseudoClass) {
	ip = GetIndexes(image);
	if (ip == NULL) {
	  fprintf(stderr,"signature: error getting PsuedoClass image pixel "
		  "indices\n");
	  exit(1);
	} else {
	  /* This represents a best guess at the correct approach for
	     handling "PseudoClass" images, but has not been tested
	     since this type doesn't seem to occur very often. 
	     Corrections or error reports would be appreciated if anyone
	     notices anything wrong with this code. */
	  if (buffer == NULL)
	    if ((buffer = malloc(npixels*sizeof(PixelPacket))) == NULL) {
	      fprintf(stderr,"signature: error allocating indexed pixel  "
		      "buffer\n");
	      exit(1);
	    }
	  pp = buffer;
	  for (k = 0; k < npixels; k++)
	    pp[k] = image->colormap[ip[k]];
	}
      } else {
	fprintf(stderr,"signature: error getting unknown class image pixels\n");
	exit(1);
      }
    }
    /* Pass image row to MD5 */
    md5_append(pms, (unsigned char*)pp, npixels*sizeof(PixelPacket));
  }
  if (buffer != NULL)
    free(buffer);
}


void printdigest(FILE* fp, unsigned char* digest) {
  int k;

  for (k = 0; k < 16; k++)
    fprintf(fp, "%02x", digest[k]);
}



void printspace(int n) {
  if (n > 0)
    printf("   ");
}


void pformat(Image *image) {
  printf("%s", image->magick);
}


void pfmtdscr(Image *image) {
  const MagickInfo* magick_info = 
    GetMagickInfo(image->magick,&image->exception);
  /* See DescribeImage in image.c */
  printf("%s", magick_info->description);
}


void pdepth(Image *image) {
  int depth = image->depth; /*GetImageDepth(image,&exception);*/

  printf("%d  ", depth);
}


void psize(Image *image) {
  int size = GetBlobSize(image);

  printf("%d", size);
}


void pgeom(Image *image) {
  int width = image->columns, height = image->rows;
  
  printf("%dx%d", width, height);
}


void pwidth(Image *image) {
  int width = image->columns;
  
  printf("%d", width);
}


void pheight(Image *image) {
  int height = image->rows;
  
  printf("%d", height);
}


#ifndef POPT_OPTARGS_BROKEN
void porient(Image *image, float orientswitch) {
   int width = image->columns, height = image->rows;
   char ochar = ' ';
   float offsquare = 100.0 * fabs((float)width - (float)height)/
     ((width + height)/2.0);
   
   if (width == height || offsquare < orientswitch)
     ochar = 'S';
   else
     ochar = (width < height)?'P':'L';
   printf("%c", ochar);
}
#endif


void piccname(Image *image) {
#if defined(HAVE_LCMS_H) || defined(HAVE_LCMS_LCMS_H)
  if (image->color_profile.info) {
    cmsHPROFILE image_profile;
  
    image_profile = cmsOpenProfileFromMem(image->color_profile.info,
					  image->color_profile.length);
    if (image_profile)
      printf("%s", cmsTakeProductDesc(image_profile));
  }
#else
  fprintf(stderr, "imageinfo: installed imagemagick does not support lcms\n");
#endif
}


static void piccmd5(Image *image) {
  ProfileInfo *pi;
  md5_state_t pms;
  md5_byte_t digest[16];
   
  pi = &(image->color_profile);
  if (pi->length) {
    md5_init(&pms);
    md5_append(&pms, pi->info, pi->length);
    md5_finish(&pms, digest);
    printdigest(stdout, digest);
  }
}


static void psha256hash(Image *image) {
  if (SignatureImage(image))
    printf("%s", GetImageAttribute(image, "signature")->value);
}


static void pmd5hash(Image *image) {
  md5_state_t pms;
  md5_byte_t digest[16];
  md5_init(&pms);
  signature(&pms, image);
  md5_finish(&pms, digest);
  printdigest(stdout, digest);
}


int wiccfile(Image *image, const char* iccfile) {
#if defined(HAVE_LCMS_H) || defined(HAVE_LCMS_LCMS_H)
  if (image->color_profile.info) {
    FILE* fp;

    if (!(fp = fopen(iccfile, "w")))
      return 0;

    if (fwrite(image->color_profile.info, 1, image->color_profile.length, fp) 
	!= image->color_profile.length) {
      fclose(fp);
      return 0;
    }
    fclose(fp);
  }
  return 1;
#else
  fprintf(stderr, "imageinfo: installed imagemagick does not support lcms\n");
  return 0;
#endif
}
