/*   EXTRAITS DE LA LICENCE
	Copyright CEA, contributeurs : Luc BILLARD et Damien
	CALISTE, laboratoire L_Sim, (2001-2006)
  
	Adresse mèl :
	BILLARD, non joignable par mèl ;
	CALISTE, damien P caliste AT cea P fr.

	Ce logiciel est un programme informatique servant à visualiser des
	structures atomiques dans un rendu pseudo-3D. 

	Ce logiciel est régi par la licence CeCILL soumise au droit français et
	respectant les principes de diffusion des logiciels libres. Vous pouvez
	utiliser, modifier et/ou redistribuer ce programme sous les conditions
	de la licence CeCILL telle que diffusée par le CEA, le CNRS et l'INRIA 
	sur le site "http://www.cecill.info".

	Le fait que vous puissiez accéder à cet en-tête signifie que vous avez 
	pris connaissance de la licence CeCILL, et que vous en avez accepté les
	termes (cf. le fichier Documentation/licence.fr.txt fourni avec ce logiciel).
*/

/*   LICENCE SUM UP
	Copyright CEA, contributors : Luc BILLARD et Damien
	CALISTE, laboratoire L_Sim, (2001-2006)

	E-mail address:
	BILLARD, not reachable any more ;
	CALISTE, damien P caliste AT cea P fr.

	This software is a computer program whose purpose is to visualize atomic
	configurations in 3D.

	This software is governed by the CeCILL  license under French law and
	abiding by the rules of distribution of free software.  You can  use, 
	modify and/ or redistribute the software under the terms of the CeCILL
	license as circulated by CEA, CNRS and INRIA at the following URL
	"http://www.cecill.info". 

	The fact that you are presently reading this means that you have had
	knowledge of the CeCILL license and that you accept its terms. You can
	find a copy of this licence shipped with this software at Documentation/licence.en.txt.
*/
#include "dumpToTiff.h"

#include "stdlib.h"
#include "stdio.h"
#include "string.h"
#include <visu_tools.h>
#include <opengl.h>

#include <glib.h>

static FILE *output;
static unsigned char *image;
static int TIFFwidth, TIFFheight;

int writeViewInTiffFormat(FileFormat *format, GString *buffer,
			  char* filename, int width, int height,
			  VisuData *dataObj,
			  voidDataFunc functionWait, gpointer data);
static gpointer waitData;
static voidDataFunc waitFunc;

DumpType* initDumpToTiff()
{
  DumpType *tiff;
  char *typeTIF[] = {"*.tif", "*.tiff", (char*)0};
#define descrTIF _("Tiff file")
  FileFormat* fmt;

  tiff = malloc(sizeof(DumpType));
  if (!tiff)
    {
      allocationProblems();
      exit(1);
    }
  fmt = fileFormatNew(descrTIF, typeTIF);
  if (!fmt)
    g_error("Can't initialize the TIF dump module, aborting.\n");

  tiff->fileType = fmt;
  tiff->writeFunc = writeViewInTiffFormat;
  
  waitData = (gpointer)0;
  waitFunc = (voidDataFunc)0;

  return tiff;
}

/******************************************************************************/
/******************************************************************************/

static void encode();

/******************************************************************************/

static void OutputData() {
   
  encode();
      
}

/******************************************************************************/

static void OutHeader(unsigned int value, unsigned int bytes) {    

    unsigned char str[4];
    register unsigned int k;
    register int is = 4 - bytes;
    
    for (k=0; k<bytes; k++) 
       str[k] = (value << 8*(is + k)) >> 24;
           
    (void)fwrite(str, sizeof(unsigned char), bytes, output);
    
}

/******************************************************************************/

static void WriteTif() {

    char ImageDescription[1024] = "Image dump from V_Sim";
    char Software[] = "V_Sim (L. BILLARD)";    
    /*  RGB FullColor */   
    unsigned int PhotometricInterpretation = 2;
    unsigned int BitsPerSample[3] = {8, 8, 8};
    unsigned int SamplesPerPixel = 3;
    /* LZW compression */
    unsigned int Compression = 5;

    unsigned char str[4];
    unsigned int offset;
    unsigned int nb_entries;
    unsigned int tag, type, count;
    
    unsigned int offset_bits;
    
    unsigned int ImageDescription_length;
    unsigned int offset_imagedescription;
    
    unsigned int StripOffsets_nbr;
    unsigned int offset_posi;
    
    unsigned int RowsPerStrip;
    
    unsigned int StripByteCounts_nbr;
    unsigned int data_written;
          
    unsigned int XResolution[2];
    unsigned int x_posi;  
    unsigned int YResolution[2];
    unsigned int y_posi;
    
    unsigned int ResolutionUnit;    
    
    unsigned int Software_length;
    unsigned int offset_software;
    

    /********************* 12 octets en tête ****************************/    

    /* big_endian */
    str[0] = 77;
    str[1] = 77;    
    (void)fwrite(str, sizeof(unsigned char), 2, output);
    
    /* caractéristique TIFF 42 */
    str[0] =  0;
    str[1] = 42;    
    (void)fwrite(str, sizeof(unsigned char), 2, output);
    
    /* offset = 8 pour IFD (temporaire) */
    offset = 8;
    OutHeader(offset, 4);
    
    
    /******************** Champs 'trop longs ' *************************/
    
    StripOffsets_nbr = 1;
    XResolution[0] = 72;
    XResolution[1] = 1;
    YResolution[0] = 72;
    YResolution[1] = 1;    
    ResolutionUnit = 2;    

    
    offset_bits = ftell(output);
    OutHeader(BitsPerSample[0], 2);
    OutHeader(BitsPerSample[1], 2);
    OutHeader(BitsPerSample[2], 2);
          
    ImageDescription_length = 1 + strlen(ImageDescription);
    switch (ImageDescription_length) {
       case 1: 
          offset_imagedescription = 0; 
          break;
       case 2: 
          offset_imagedescription = 65536*ImageDescription[0]; 
          break;
       default: 
          offset_imagedescription = ftell(output);               
          (void)fwrite(ImageDescription, sizeof(char), 
                ImageDescription_length, output);
          break;
    }           
    
    offset_posi = ftell(output);
    OutputData(); 
    data_written = ftell(output) - offset_posi; 
    
    RowsPerStrip = TIFFheight;
     
    StripByteCounts_nbr = StripOffsets_nbr;
    
    x_posi = ftell(output);
    OutHeader(XResolution[0], 4);
    OutHeader(XResolution[1], 4);  
    y_posi = ftell(output);
    OutHeader(YResolution[0], 4);
    OutHeader(YResolution[1], 4);         
    
    Software_length = 1 + strlen(Software);
    switch (Software_length) {
       case 1: 
          offset_software = 0; 
          break;
       case 2: 
          offset_software = 65536*Software[0]; 
          break;
       default: 
          offset_software = ftell(output);               
          (void)fwrite(Software, sizeof(char), Software_length, output);
          break;
    }
    
    
    /******************** IFD ******************************************/
    
    /* je stocke la position */
    offset = ftell(output);
    
    /* provisoire */
    nb_entries = 0;
    OutHeader(nb_entries, 2);
    
    
    tag = 256;
    OutHeader(tag, 2);
    type = 4;
    OutHeader(type, 2); 
    count = 1;
    OutHeader(count, 4);    
    OutHeader(TIFFwidth, 4);
    nb_entries++;
    
      
    tag = 257;
    OutHeader(tag, 2);
    type = 4;
    OutHeader(type, 2); 
    count = 1;
    OutHeader(count, 4);    
    OutHeader(TIFFheight, 4);
    nb_entries++;
    
         
    tag = 258;
    OutHeader(tag, 2);
    type = 3;
    OutHeader(type, 2); 
    count = SamplesPerPixel;
    OutHeader(count, 4);    
    OutHeader(offset_bits, 4); 
    nb_entries++;
    
         
    tag = 259;
    OutHeader(tag, 2);
    type = 3;
    OutHeader(type, 2); 
    count = 1;
    OutHeader(count, 4);    
    OutHeader(Compression, 2);    
    OutHeader(0, 2);
    nb_entries++;
    
          
    tag = 262;
    OutHeader(tag, 2);
    type = 3;
    OutHeader(type, 2); 
    count = 1;
    OutHeader(count, 4);
    OutHeader(PhotometricInterpretation, 2);    
    OutHeader(0, 2);
    nb_entries++;    
    
          
    tag = 270;
    OutHeader(tag, 2);
    type = 2;
    OutHeader(type, 2); 
    count = ImageDescription_length;
    OutHeader(count, 4);
    OutHeader(offset_imagedescription, 4);
    nb_entries++;
    
          
    tag = 273;
    OutHeader(tag, 2);
    type = 4;
    OutHeader(type, 2); 
    count = StripOffsets_nbr;
    OutHeader(count, 4);
    OutHeader(offset_posi, 4);    
    nb_entries++;

          
    tag = 277;
    OutHeader(tag, 2);
    type = 3;
    OutHeader(type, 2); 
    count = 1;
    OutHeader(count, 4);    
    OutHeader(SamplesPerPixel, 2);    
    OutHeader(0, 2); 
    nb_entries++;
    
             
    tag = 278;
    OutHeader(tag, 2);
    type = 4;
    OutHeader(type, 2); 
    count = 1;
    OutHeader(count, 4);    
    OutHeader(RowsPerStrip, 4);
    nb_entries++;

        
    tag = 279;
    OutHeader(tag, 2);
    type = 4;
    OutHeader(type, 2); 
    count = StripByteCounts_nbr;
    OutHeader(count, 4);    
    OutHeader(data_written, 4);    
    nb_entries++;
    
        
    tag = 282;
    OutHeader(tag, 2);
    type = 5;
    OutHeader(type, 2); 
    count = 1;
    OutHeader(count, 4);
    OutHeader(x_posi, 4);
    nb_entries++;
    
            
    tag = 283;
    OutHeader(tag, 2);
    type = 5;
    OutHeader(type, 2); 
    count = 1;
    OutHeader(count, 4);
    OutHeader(y_posi, 4);
    nb_entries++;

          
    tag = 296;
    OutHeader(tag, 2);
    type = 3;
    OutHeader(type, 2); 
    count = 1;
    OutHeader(count, 4);
    OutHeader(ResolutionUnit, 2);    
    OutHeader(0, 2);
    nb_entries++;        
    
          
    tag = 305;
    OutHeader(tag, 2);
    type = 2;
    OutHeader(type, 2); 
    count = Software_length;
    OutHeader(count, 4);
    OutHeader(offset_software, 4);
    nb_entries++;
    
    
    /* pas d'autre IFD */
    OutHeader(0, 4);
   
    
    /* je ré-écris proprement nb_entries */ 
    if (fseek(output, offset, SEEK_SET))
      g_error("INTERNAL ERROR! I cannot go at requested position\n");
    OutHeader(nb_entries, 2);
      
    /* je ré-écris proprement la position de l'IFD */ 
    if (fseek(output, 4, SEEK_SET))
      g_error("INTERNAL ERROR! I cannot go at requested position\n");
    OutHeader(offset, 4);

 

}

/******************************************************************************/
/******************************************************************************/

/* Encodage LZW pp. 57 et suivantes */

#define CLEAR 256
#define EOI 257

static struct {
   int previous;
   unsigned char value;
} table[4096];
static unsigned int table_next;

static unsigned int length_bit;

static unsigned int stock;
static unsigned int remain;

/******************************************************************************/

static struct {
   unsigned int nbr;
   unsigned int *table_index;
   unsigned int *table_previous;
} related[256];

/* related[i] donne la liste de toutes les tables
   dont table[].value = i;
   
   à savoir:
      pour k = 0; k < related[i].nbr
      on a:
      (related[i].table_index)[k]
      (related[i].table_previous)[k]
      
      table[(related[i].table_index)[k]].previous =
         (related[i].table_previous)[k];
      table[(related[i].table_index)[k]].value =
         i;
*/

static int search(unsigned char value, unsigned int previous) {
/* chercher la table telle que:
   table[which].previous = previous;
   table[which].value = value;
   
   retourne -1, si pas trouvée
*/

   register unsigned int k;
   
   int which = -1;
   
   for (k=0; k<related[value].nbr; k++) {
      if ( (related[value].table_previous)[k] == previous ) {
            which = (related[value].table_index)[k];
            break;
      }
   }
   
   return which;
   
}


/******************************************************************************/
   
static void free_related(void) {
   int k;   
   for (k=0; k<256; k++) {
      (void)free(related[k].table_index);
      (void)free(related[k].table_previous);
   }
}

/******************************************************************************/

static void InitializeStringTable(void) {
   /* pour 256 et 257 bidon */
   
   register unsigned int k;

   
   /* PAS UTILE POUR L'ENCODAGE */
   /*
   
   register unsigned int i;
   

 
   
   for (i=0; i<256; i++) {
      table[i].previous = -1;
      table[i].value = (unsigned char)i;
   }
     
   table[256].previous = -2;
   table[256].value = 0;
   table[257].previous = -2;
   table[257].value = 0;

   */
   
   table_next = 258;
   length_bit = 9;
   
   
   for (k=0; k<256; k++) {
      related[k].nbr = 0;
      related[k].table_index = NULL;
      related[k].table_previous = NULL;
      related[k].table_index = realloc(related[k].table_index,
           1 * sizeof(unsigned int));
      related[k].table_previous = realloc(related[k].table_previous,
           1 * sizeof(unsigned int));
   }
   
}

/******************************************************************************/

static void WriteCode(int code) {

   /* stock est sur 32 bits 
      et contient remain bits utiles à partir de la gauche
   */

   /* je viens y coller par la droite, code écrit sur length_bit */
   stock += code << (32 - length_bit - remain);
   remain += length_bit;
   
   /* j'écris les 8 bits utiles de gauche (s'il y en a) */
   while (remain >= 8) {
      unsigned int k = (stock >> 24);
      fputc(k, output);
      stock <<= 8;
      remain -= 8;
   }
   
}

/******************************************************************************/

static void Flush() {

   /* je sors ce qui reste (< 8 bits) */
   if (remain) {
      unsigned int k = (stock >> 24);   
      fputc(k, output);
   } 
}

/******************************************************************************/

static void AddTableEntry(unsigned int last, unsigned char c) {

   unsigned int nbr;
   
   table[table_next].previous = last;
   table[table_next].value = c;
   
   nbr = related[c].nbr + 1;
   related[c].table_index = realloc(related[c].table_index,
           nbr * sizeof(unsigned int));
   related[c].table_previous = realloc(related[c].table_previous,
           nbr * sizeof(unsigned int));
   (related[c].table_index)[related[c].nbr] = table_next;
   (related[c].table_previous)[related[c].nbr] = last;
   related[c].nbr = nbr;
   
   table_next++;
   
   if (table_next == 512)
      length_bit = 10;
   else if (table_next == 1024)
      length_bit = 11;
   else if (table_next == 2048)
      length_bit = 12;
   
}

/******************************************************************************/

static void encode() {

   register unsigned int n;
   int it_is;
   unsigned char k;
   int which;
   unsigned int image_length;
   
   /*(void)printf("Be patient: LZW encoding!...\n");*/
   image_length = 3*TIFFwidth*TIFFheight;
   
   stock = 0;
   remain = 0;

   InitializeStringTable();
   
   WriteCode(CLEAR);
   
   k = image[0];      
   it_is = k;
   
   
   for (n=1; n<image_length; n++)
     {
       if (waitFunc && n % (image_length / 100) == 0)
	 waitFunc(waitData);
   
       k = image[n];
      
       if ( (which = search(k, it_is)) != -1 )
	 {
	   it_is = which;
	   goto next_data;
	 }

       WriteCode(it_is);
       AddTableEntry(it_is, k);

       if (table_next != 4094) 
         it_is = k;
       else
	 {
	   WriteCode(k);
	   WriteCode(CLEAR);
	   InitializeStringTable();
	   n++;
	   if ( n < image_length)
	     {
	       k = image[n];      
	       it_is = k;
	     }
	 }
      
      next_data:;
      
   } /* loop over data */

       
   WriteCode(it_is); 
      
   WriteCode(EOI);

   
   Flush();
   
   free_related();

   
}
 
/******************************************************************************/

int writeViewInTiffFormat(FileFormat *format, GString *buffer,
			  char* filename, int width, int height,
			  VisuData *dataObj,
			  voidDataFunc functionWait, gpointer data)
{
  waitData = data;
  waitFunc = functionWait;

  image = dumpViewToImage(dataObj, buffer, &width, &height);
  if (!image)
    return 1;

  DBG_fprintf(stderr, "Dump TIFF : begin export in %dx%d...\n", width, height);

  TIFFwidth = width;
  TIFFheight = height;

  output = fopen(filename, "wb");
  if(!output)
    {
      g_string_append(buffer, _("Cannot open file (to write in)\n"));
      free(image);
      return 1;
    }

  DBG_fprintf(stderr, "Dump Tif : begin export...\n");

  WriteTif();
   
  (void)fclose(output);
  free(image);
  return 0;
}
