/*************************
 * libdicom by Tony Voet *
 *************************/
/*
 *
 */

#include <stdlib.h>
#include <string.h>
#include "dicom.h"

#define MDC_ENCAP_OFFSET_TABLE 0               /* 0/1 using offset table */

static S64 dicom_pixel(const ELEMENT *);

static SINGLE single;

/**********
 * single *
 **********/

SINGLE *dicom_single(void)
{
  ELEMENT *e;
  CLUT *clut;
  S64 length, bytes;
  U32 i, f;
  size_t size;
  char *interpretation[]=
  {
    "MONOCHROME2",
    "MONOCHROME1",
    "PALETTE COLOR",
    "RGB",
    "HSV",
    "ARGB",
    "CMYK",
    "UNKNOWN"
  };

  dicom_log(DEBUG,"dicom_single()");

  memset(&single,0,sizeof(SINGLE));
  single.frames=1;
  single.samples=1;

  for (;;)
  {
    e=dicom_element();
    if (!e)
      break;

    if (mdc_dicom_skip_sequence(e)) {
      if (dicom_skip()) break;
      continue;
    }

    if (e->group==0x0028)
    {
      if (e->element==0x0002)
      {
        if (dicom_load(US))
          break;
        single.samples=*e->value.US;
        eNlfSafeFree(e->value.US);
        continue;
      }

      if (e->element==0x0004)
      {
        if (dicom_load(CS))
          break;

        if ((e->value.CS == NULL) || (*e->value.CS[0] == '\0')) {
          dicom_log(WARNING,"Empty Photometric Interpretation");
          break;
        }

        dicom_clean();

        for (single.photometric=0; single.photometric<UNKNOWN;
          single.photometric++)
          if ( !strncmp(*e->value.CS,interpretation[single.photometric],
            strlen(interpretation[single.photometric])) )
            break;

        if (single.photometric==UNKNOWN)
          dicom_log(WARNING,"Unknown PhotometricInterpretation");

        eNlfSafeFree(e->value.CS);
        continue;
      }

      if (e->element==0x0006)
      {
        if (dicom_load(US))
          break;
        single.planar=*e->value.US;
        eNlfSafeFree(e->value.US);
        continue;
      }

      if (e->element==0x0008)
      {
        if (dicom_load(IS))
          break;
        dicom_clean();
        single.frames=atoi(*e->value.IS);
        eNlfSafeFree(e->value.IS);
        continue;
      }

      if (e->element==0x0010)
      {
        if (dicom_load(US))
          break;
        single.h=*e->value.US;
        eNlfSafeFree(e->value.US);
        continue;
      }

      if (e->element==0x0011)
      {
        if (dicom_load(US))
          break;
        single.w=*e->value.US;
        eNlfSafeFree(e->value.US);
        continue;
      }

      if (e->element==0x0100)
      {
        if (dicom_load(US))
          break;
        single.alloc=*e->value.US;
        eNlfSafeFree(e->value.US);
        continue;
      }

      if (e->element==0x0101)
      {
        if (dicom_load(US))
          break;
        single.bit=*e->value.US;
        eNlfSafeFree(e->value.US);
        continue;
      }

      if (e->element==0x0102)
      {
        if (dicom_load(US))
          break;
        single.high=*e->value.US;
        eNlfSafeFree(e->value.US);
        if ((dicom_workaround & MDC_FIX_EZDICOM) && (single.high == 0)) {
          dicom_log(WARNING,"Wrong ezDICOM high bit value (fixed)");
          single.high = (single.bit > 0) ? single.bit - 1 : 15;
        }
        continue;
      }

      if (e->element==0x0103)
      {
        if (dicom_load(US))
          break;
        single.sign=*e->value.US;
        eNlfSafeFree(e->value.US);
        continue;
      }

      if (0x1101<=e->element && e->element<=0x1103)
      {
        if (dicom_load(US))
          break;

        if (e->vm!=3)
          dicom_log(WARNING,"Wrong VM for PaletteColorLookupTableDescriptor");
        else
        {
          clut = (CLUT *)&single.clut[e->element-0x1101];
  
          clut->size = (U32)e->value.US[0];
          clut->threshold.u16 = e->value.US[1];
          clut->bit= e->value.US[2];
  
          if (clut->size == 0) clut->size = 1<<16; /* 2^16 or 65356 */
        }

        eNlfSafeFree(e->value.US);
        continue;
      }

      if (0x1201<=e->element && e->element<=0x1203)
      {
        clut = (CLUT *)&single.clut[e->element-0x1201];

        /* eNlf: BEGIN - read lookup tables of a single byte */
        if (clut->size == e->length) {
          /* one byte case */
          U16 *wordclut;

          if (dicom_load(OB))
            break;

          wordclut = (U16 *)malloc(clut->size*2L);
          if (!wordclut) {
            dicom_log(ERROR,"Out of memory");
            break; 
          }

          /* transform into a two byte word */
          for (i=0; i<clut->size; i++) {
             wordclut[i] = (U16)e->value.OB[i];
          }
          clut->data.u16 = wordclut;

          eNlfSafeFree(e->value.OB);

        /* eNlf: END   - read lookup tables of a single byte */
        }else{

          /* should be two byte case */
          if (dicom_load(OW))
             break;

          clut->data.u16=e->value.OW;

        }
        continue;
      }
    }

    if (!(e->group&1)) 
    {
      if ((0x7F00<=e->group && e->group<0x7FFF) && (e->element==0x0010))
      {
        U32 frames, width, height, pixel;

        frames= (U32)single.frames;
        width = (U32)single.w; 
        height= (U32)single.h;
        pixel = (U32)single.samples*single.alloc>>3;

        if (e->length!=0xFFFFFFFF)
        { /* pixel data, not encapsulated */

          /* first fix bad VR values, confusing data endian swap */
          switch (e->vr)
          {
            case OB:
              if (single.alloc==16)
              {
                dicom_log(WARNING,"Incorrect OB value representation (fixed)");
                e->vr=OW;

                /* workaround for Amira 3.0 pixel data length bug     */
                /* http://www.amiravis.com/resources/Patch30-13-dicom */
                if (e->length == 2U * frames * width * height * pixel) {
                  dicom_log(WARNING,"Amira 3.0 pixel data length bug (fixed)");
                  e->length /= 2U;
                }
              }
              break;
            case OW:
              if (single.alloc==8)
              {
                dicom_log(WARNING,"Incorrect OW value representation (fixed)");
                e->vr=OB;
              }
              break;
            default:
              break;
          }

          length=dicom_pixel(e);
          if (length<0)
            break;

          if (length != (S64)frames * width * height * pixel) {
            dicom_log(WARNING,"Incorrect PixelData length");
            dicom_single_free();
            return 0L;
          }

          return &single;
        }
        else if (e->length == 0xFFFFFFFF)
        { /* encapsulated data */
          U8 *data;
#if MDC_ENCAP_OFFSET_TABLE
          U32 *offset=NULL;
          off_t begin=(off_t)0;
#endif
          /* skip offset table */
          e=dicom_element(); if (!e) break;
          if (e->vm && e->length != 0)
          { 
            /* a  value present */
            if (e->length != frames * 4U) break; /* get out, bad offset table */
#if MDC_ENCAP_OFFSET_TABLE
            dicom_load(UL);
            offset = (off_t)e->value.UL;
            begin = mdc_dicom_ftello();
#else
            dicom_skip();
#endif
          }

          /* allocate memory for all frames, memset for sure          */
          /* eNlf: - make sure to calculate in 64bit for sizes > 4GB  */
          /* eNlf: - allocate an extra 4 bytes, otherwise the bit.c   */
          /* eNlf: routines like source.u++ go beyond the boundaries  */
          /* eNlf: - memset the allocated buffer for sure             */
          bytes = (S64)width*height*pixel*frames + 4L;

          /* check for overflow */
          size = (size_t)bytes;
          if ((S64)size != bytes) {
            dicom_log(ERROR,"System size_t too small");
            return 0L;
          }

          /* allocate memory */
          data = (U8*)malloc(bytes);
          if (!data)
          {
            dicom_log(ERROR,"Out of memory");
            return 0L;
          }
          memset(data,0,bytes);

          single.data = (void *)data; 

          /* retrieve all frames and decompress */
          for (f=0; f<frames; f++)
          {
#if MDC_ENCAP_OFFSET_TABLE
             mdc_dicom_fseeko((off_t)offset[f] + begin,SEEK_SET);
#endif
             e=dicom_element(); if (!e) break;

             /* bytes to move in memory; 64bit for sizes > 4GB */
             bytes = (S64)f*width*height*pixel;

             /* move pointer */
             e->vr=OB;
             e->value.OB = data + bytes;

             if (mdc_dicom_decompress(&single,e))
             {
               dicom_log(ERROR,"Decompression failed");
               dicom_single_free();
               return 0L;
             }
          }

#if MDC_ENCAP_OFFSET_TABLE
          eNlfSafeFree(offset);
#endif

          return &single;

        }
      }
    }

    if (dicom_skip())
      break;
  }

  dicom_single_free();

  return 0L;
}

/***************
 * single free *
 ***************/

void dicom_single_free(void)
{
  int i;

  dicom_log(DEBUG,"dicom_single_free()");

  for (i=0; i<3; i++)
    eNlfSafeFree(single.clut[i].data.u16);

  eNlfSafeFree(single.data);

  memset(&single,0,sizeof(SINGLE));
}

/*********
 * pixel *
 *********/

static S64 dicom_pixel(const ELEMENT *e)
{
  U16 magic=0x1234;
  int error;

  dicom_log(DEBUG,"dicom_pixel()");

  if (e->length!=0xFFFFFFFF)
  {
    if (single.alloc==16) {
      error=dicom_load(OW);
    }else if (single.alloc==12) {
      if ( *((U8*)&magic)==0x12 ) mdc_dicom_switch_endian(); 
        error=dicom_load(OW);
      if ( *((U8*)&magic)==0x12 ) mdc_dicom_switch_endian();
    }else{
      error=dicom_load(OB);
    }

    if (error)
      return -1;

    single.data=e->value.OW;

    return ((S64)e->length);
  }

  if (dicom_skip())
    return -2;

  dicom_log(EMERGENCY,"Encapsulated PixelData is not implemented yet");

  return -3;
}
