/*   EXTRAITS DE LA LICENCE
	Copyright CEA, contributeurs : Luc BILLARD et Damien
	CALISTE, laboratoire L_Sim, (2001-2005)
  
	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-2005)

	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 <glib.h>

/**
 * SECTION:dumpToTiff
 * @short_description: add an export capability into TIFF files.
 *
 * This provides a write routine to export V_Sim views into TIFF
 * files.
 */

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

static gboolean writeViewInTiffFormat(ToolFileFormat *format, const char* filename,
				      VisuGlNodeScene *scene, guint width, guint height,
				      GError **error, ToolVoidDataFunc functionWait, gpointer data);
static gpointer waitData;
static ToolVoidDataFunc waitFunc;

static VisuDump *tiff;

const VisuDump* visu_dump_tiff_getStatic()
{
  const gchar *typeTIF[] = {"*.tif", "*.tiff", (char*)0};
#define descrTIF _("Tiff file")

  if (tiff)
    return tiff;

  tiff = VISU_DUMP(visu_dump_scene_new(descrTIF, typeTIF, writeViewInTiffFormat, FALSE));

  waitData = (gpointer)0;
  waitFunc = (ToolVoidDataFunc)0;

  return tiff;
}

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

static void encode();

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

static void OutputData() {
   
  encode();
      
}

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

static void OutHeader(guint value, guint bytes) {    

    unsigned char str[4];
    register guint 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 */   
    guint PhotometricInterpretation = 2;
    guint BitsPerSample[3] = {8, 8, 8};
    guint SamplesPerPixel = 3;
    /* LZW compression */
    guint Compression = 5;

    unsigned char str[4];
    guint offset;
    guint nb_entries;
    guint tag, type, count;
    
    guint offset_bits;
    
    guint ImageDescription_length;
    guint offset_imagedescription;
    
    guint StripOffsets_nbr;
    guint offset_posi;
    
    guint RowsPerStrip;
    
    guint StripByteCounts_nbr;
    guint data_written;
          
    guint XResolution[2];
    guint x_posi;  
    guint YResolution[2];
    guint y_posi;
    
    guint ResolutionUnit;    
    
    guint Software_length;
    guint 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 guint table_next;

static guint length_bit;

static guint stock;
static guint remain;

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

static struct {
   guint nbr;
   guint *table_index;
   guint *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, guint previous) {
/* chercher la table telle que:
   table[which].previous = previous;
   table[which].value = value;
   
   retourne -1, si pas trouvée
*/

   register guint 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++) {
      g_free(related[k].table_index);
      g_free(related[k].table_previous);
   }
}

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

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

   
   /* PAS UTILE POUR L'ENCODAGE */
   /*
   
   register guint 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 = g_realloc(related[k].table_index,
           1 * sizeof(guint));
      related[k].table_previous = g_realloc(related[k].table_previous,
           1 * sizeof(guint));
   }
   
}

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

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) {
      guint k = (stock >> 24);
      fputc(k, output);
      stock <<= 8;
      remain -= 8;
   }
   
}

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

static void Flush() {

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

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

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

   guint nbr;
   
   table[table_next].previous = last;
   table[table_next].value = c;
   
   nbr = related[c].nbr + 1;
   related[c].table_index = g_realloc(related[c].table_index,
           nbr * sizeof(guint));
   related[c].table_previous = g_realloc(related[c].table_previous,
           nbr * sizeof(guint));
   (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 guint n;
   int it_is;
   unsigned char k;
   int which;
   guint 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();

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

static gboolean writeViewInTiffFormat(ToolFileFormat *format _U_, const char* filename,
				      VisuGlNodeScene *scene, guint width, guint height,
				      GError **error, ToolVoidDataFunc functionWait,
                                      gpointer data)
{
  GArray *imageData;

  g_return_val_if_fail(error && !*error, FALSE);

  imageData = visu_gl_ext_set_getPixmapData(VISU_GL_EXT_SET(scene),
                                            width, height, FALSE);
  if (!imageData)
    {
      *error = g_error_new(VISU_DUMP_ERROR, DUMP_ERROR_OPENGL,
                           _("Can't dump OpenGL area to data.\n"));
      return FALSE;
    }

  waitData   = data;
  waitFunc   = functionWait;
  image      = (unsigned char*)imageData->data;
  TIFFwidth  = width;
  TIFFheight = height;

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

  output = fopen(filename, "wb");
  if(!output)
    {
      *error = g_error_new(VISU_DUMP_ERROR, DUMP_ERROR_FILE,
			   _("Cannot open file (to write in)."));
      return FALSE;
    }

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

  WriteTif();

  g_array_free(imageData, TRUE);
   
  (void)fclose(output);
  return TRUE;
}
