/* TGA.C release 0.9 06/18/97
 *
 * TrueVision Targa loading and saving file filter for the Gimp
 *  
 * -Raphael FRANCOIS
 *
 * This code is based on TrueVision TGA file format specification Version 2.0.
 * All the features written in the specificarion are not available but you can : 
 *
 * - Read 24 and 32 bit Targa images compressed or not.
 * - Write 24 and 32 bit Targa images in non compressed format for the moment.
 *
 *  NB: these images are compatible with Photoshop 32 targa images with Alphachanel.
 *
 * your can send me any comment on fraph@ibm.net
 */


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "gtk/gtk.h"
#include "libgimp/gimp.h"

#define FALSE 0
#define TRUE 1

#define COMPRESSION_NONE 0
#define COMPRESSION_RLE 1

#define IMAGE_24_BIT 0
#define IMAGE_32_BIT_ALPHA 1

typedef struct _TgaSaveVals
{
  gint compression;
  gint type;
} TgaSaveVals;

typedef struct _TgaSaveInterface
{
  unsigned char run;
} TgaSaveInterface;

static TgaSaveVals tsvals =
{
  COMPRESSION_RLE,    /*  compression  */
  IMAGE_32_BIT_ALPHA,  /*  type  */
};

static TgaSaveInterface tsint =
{
  FALSE                /*  run  */
};

enum TGA_VERTICAL { BOTTOM2TOP, TOP2BOTTOM };
enum TGA_HORIZONTAL { LEFT2RIGHT, RIGHT2LEFT };

static struct
{

  unsigned char idLenght;
  unsigned char colorMaptype;
  unsigned char field;

  short int colorMapIndex;

/*
  Well, with the original specification, I've got on offset in the date. So from I've comment them and
created a dummy to respect the alignement. Must be ficed in few days...

  short int colorMapLenght;
  unsigned char colorMapSize;
*/

  short int dummy;

  short int xOrigin;
  short int yOrigin;
 
  short int width;
  short int height;

  unsigned char bpp;

  unsigned char descriptor;

} tga_header;


/* Declare some local functions.
 */
static void   query      (void);
static void   run        (char    *name,
                          int      nparams,
                          GParam  *param,
                          int     *nreturn_vals,
                          GParam **return_vals);
static gint32 load_image (char   *filename);
static gint   save_image (char   *filename,
			  gint32  image_ID,
			  gint32  drawable_ID);

static gint   save_dialog ();

static void   save_close_callback  (GtkWidget *widget,
				    gpointer   data);
static void   save_ok_callback     (GtkWidget *widget,
				    gpointer   data);
static void   save_toggle_update   (GtkWidget *widget,
				    gpointer   data);

GPlugInInfo PLUG_IN_INFO =
{
  NULL,    /* init_proc */
  NULL,    /* quit_proc */
  query,   /* query_proc */
  run,     /* run_proc */
};


MAIN ();


static void
query ()
{
  static GParamDef load_args[] =
  {
    { PARAM_INT32, "run_mode", "Interactive, non-interactive" },
    { PARAM_STRING, "filename", "The name of the file to load" },
    { PARAM_STRING, "raw_filename", "The name entered" },
  };
  static GParamDef load_return_vals[] =
  {
    { PARAM_IMAGE, "image", "Output image" },
  };
  static int nload_args = sizeof (load_args) / sizeof (load_args[0]);
  static int nload_return_vals = sizeof (load_return_vals) / sizeof (load_return_vals[0]);


  static GParamDef save_args[] =
  {
    { PARAM_INT32, "run_mode", "Interactive, non-interactive" },
    { PARAM_IMAGE, "image", "Input image" },
    { PARAM_DRAWABLE, "drawable", "Drawable to save" },
    { PARAM_STRING, "filename", "The name of the file to save the image in" },
    { PARAM_STRING, "raw_filename", "The name of the file to save the image in" },
    { PARAM_INT32, "compression", "Compression type: { NONE (0), RLE (1)" },
    { PARAM_INT32, "Alpha ", "Alpha Type: { 32 bit Alpha (0), 24 bit  (1)" }
  } ;
  static int nsave_args = sizeof (save_args) / sizeof (save_args[0]);
  
  gimp_install_procedure ("file_tga_load",
                          "Loads files of Targa file format",
                          "FIXME: write help for tga_load",
                          "Raphael FRANCOIS",
                          "Raphael FRANCOIS",
                          "1997",
                          "<Load>/TGA",
			  NULL,
                          PROC_PLUG_IN,
                          nload_args, nload_return_vals,
                          load_args, load_return_vals);

			   
 


  gimp_install_procedure ("file_tga_save",
                          "saves files in the Targa file format",
                          "FIXME: write help for tga_save",
			  "Raphael FRANCOIS",
                          "Raphael FRANCOIS",
                          "1997",
                          "<Save>/TGA",
			  "RGB*, GRAY*",
                          PROC_PLUG_IN,
                          nsave_args, 0,
                          save_args, NULL);

  gimp_register_magic_load_handler ("file_tga_load", "tga", "",
				    "0&,byte,10,2&,byte,1,3&,byte,>0,3,byte,<9");
  gimp_register_save_handler ("file_tga_save", "tga", "");

}

static void
run (char    *name,
     int      nparams,
     GParam  *param,
     int     *nreturn_vals,
     GParam **return_vals)
{
  static GParam values[2];
  GStatusType status = STATUS_SUCCESS;
  GRunModeType run_mode;
  gint32 image_ID;

  run_mode = param[0].data.d_int32;

  *nreturn_vals = 1;
  *return_vals = values;
  values[0].type = PARAM_STATUS;
  values[0].data.d_status = STATUS_CALLING_ERROR;

  if (strcmp (name, "file_tga_load") == 0)
    {
      image_ID = load_image (param[1].data.d_string);

      if (image_ID != -1)
        {
          *nreturn_vals = 2;
          values[0].data.d_status = STATUS_SUCCESS;
          values[1].type = PARAM_IMAGE;
          values[1].data.d_image = image_ID;
        }
      else
        {
          values[0].data.d_status = STATUS_EXECUTION_ERROR;
        }
    }
  else if (strcmp (name, "file_tga_save") == 0)
    {
      switch (run_mode)
	{
	case RUN_INTERACTIVE:
	  /*  Possibly retrieve data  */
	  gimp_get_data ("file_tga_save", &tsvals);

	  /*  First acquire information with a dialog  */
	  if (! save_dialog ())
	    return;
	  
	  break;

	case RUN_NONINTERACTIVE:
	  /*  Make sure all the arguments are there!  */
	  if (nparams != 7)
	    status = STATUS_CALLING_ERROR;
	  if (status == STATUS_SUCCESS)
	    {
	      switch (param[5].data.d_int32)
		{
		case 0: tsvals.compression = COMPRESSION_NONE;     break;
		case 1: tsvals.compression = COMPRESSION_RLE;      break;
		default: status = STATUS_CALLING_ERROR; break;
		}
	      switch (param[6].data.d_int32)
		{
		case 0: tsvals.type = IMAGE_24_BIT;
		case 1: tsvals.type = IMAGE_32_BIT_ALPHA; break;
		default: status = STATUS_CALLING_ERROR; break;
		}
	    }

	case RUN_WITH_LAST_VALS:
	  /*  Possibly retrieve data  */
	  
	  gimp_get_data ("file_tiff_save", &tsvals);
	  break;
	  
	default:
	  break;
	}

      *nreturn_vals = 1;
      if (save_image (param[3].data.d_string, param[1].data.d_int32, param[2].data.d_int32))
	{
	  /*  Store psvals data  */
	  gimp_set_data ("file_tga_save", &tsvals, sizeof (TgaSaveVals));
	  
	  values[0].data.d_status = STATUS_SUCCESS;
	}
      else
	values[0].data.d_status = STATUS_EXECUTION_ERROR;
    }
}

int verbose = TRUE;

static gint32 ReadImage (FILE *, char *, int, int, int);

static gint32
load_image (char *filename)
{
  FILE *fd;
  char * name_buf;
  gint32 image_ID = -1;
  
  name_buf = g_malloc (strlen (filename) + 11);

  sprintf (name_buf, "Loading %s:", filename);
  gimp_progress_init (name_buf);
  g_free (name_buf);

  fd = fopen (filename, "rb");
  if (!fd) {
      printf ("TGA: can't open %s\n", filename);
      return -1;
    }
 

  fseek( fd, 0, SEEK_SET ); 

  if (fread(&tga_header, 18, 1, fd)==0) {
     printf("TGA: Error in reading \"%s\"\n", filename); 
     return -1; 
  }   
  
  printf("idLenght %d \n", tga_header.idLenght);
  printf("ColorMapType %d \n", tga_header.colorMaptype);
  printf("field %d \n", tga_header.field);
  printf("xOrigin %d \n", tga_header.xOrigin);  
  printf("yOrigin %d \n", tga_header.yOrigin);  
  printf("width %d \n", tga_header.width);  
  printf("height %d \n", tga_header.height);  
  printf("bpp %d \n", tga_header.bpp);  
  printf("descriptor %d \n", tga_header.descriptor);   
  

  if (tga_header.bpp != 24 && tga_header.bpp != 32)
    {
      printf("Can only read 24 and 32 bit TGA images...\n");
      return -1;
    }

   image_ID = ReadImage (fd, 
			 filename,
			 tga_header.width,
			 tga_header.height,
			 tga_header.bpp);

  return image_ID;
 
}

static gint32
ReadImage (FILE *fd,
	   char *filename,
	   int   width,
	   int   height,
	   int   bpp)

{
  static gint32 image_ID;
  gint32 layer_ID;
  /*  gint32 mask_ID; */
  GPixelRgn pixel_rgn;
  GDrawable *drawable;  
  guchar *dest;
  guchar *buffer;

 
  gint cur_progress, max_progress;
  gint i;
   unsigned char withAlpha = tga_header.bpp==32?1:0;
  
  gchar horizontal = (tga_header.descriptor & 0x10)?RIGHT2LEFT:LEFT2RIGHT;
  gchar vertical = (tga_header.descriptor & 0x20)?TOP2BOTTOM:BOTTOM2TOP;
  
  image_ID = gimp_image_new (width, height, RGB_IMAGE);
  
  gimp_image_set_filename (image_ID, filename);
  
  layer_ID = gimp_layer_new (image_ID, 
			     "Background",
			     width, height,
			     withAlpha?RGBA_IMAGE: RGB_IMAGE, 
			     100, 
			     NORMAL_MODE);
  
  gimp_image_add_layer (image_ID, layer_ID, 0);
  
  drawable = gimp_drawable_get (layer_ID);
  
  cur_progress = 0;
  max_progress = height;
  
  
  
  dest = (guchar *) g_malloc ( width * height * (bpp/8));
  
  fseek(fd, 18, SEEK_SET);
  
  if(!(tga_header.field & 0x08))
    {
      long cpt=0L;
      int k;
      long npixel2read;
  
      npixel2read = tga_header.width * tga_header.height;
      

      if (verbose)
	if (!withAlpha)
	  printf ("%d bit TGA non compressed Image\n", bpp);
	else
	  printf ("%d bit TGA non compressed Image with Alpha Channel\n", bpp);
      
      
      
      
      for(k=0;k < npixel2read; k++)
      	{
	  static guchar readpixel[4];
	  
	  
	  readpixel[0] = fgetc( fd );		/* B */
	  readpixel[1] = fgetc( fd );		/* G */ 
	  readpixel[2] = fgetc( fd );		/* R */
	  
	  dest[cpt++] = readpixel[2];           /* R */
	  dest[cpt++] = readpixel[1];           /* G */
	  dest[cpt++] = readpixel[0];           /* B */
	  
	  if (withAlpha)
	    dest[cpt++] = fgetc( fd );          /* Alpha */
	  
	  
	  if ((k%tga_header.width)==0)
	    gimp_progress_update( (double) k / (double) npixel2read);
	}
    }
  else 
    {
      /* format compression RLE */
      
      int stat;
      unsigned int count;
      unsigned char readpixel[4];
      int k;
      unsigned long cpt = 0L;
      long npixel2read = tga_header.width * tga_header.height * bpp/3;
      
      if (verbose)
	if (!withAlpha)
	  printf ("%d bit TGA RLE  compressed Image\n", bpp);
	else
	  printf ("%d bit TGA RLE compressed Image with Alpha Channel\n", bpp);
      
      
      do
	{
	  stat = fgetc( fd  );

	  if ((cpt%tga_header.width)==0)
	    gimp_progress_update( (double) cpt / (double) npixel2read);
	  
	  if(stat==EOF)
	    {
	      return -1;
	    }
	  
	  if(! (stat & 0x80)) /* nombre de point consecutifs a lire*/
	    {
	      count = ++stat;
	      
	      for( k=0;k<count;k++)
		{
		  readpixel[0] = fgetc( fd );
		  readpixel[1] = fgetc( fd );
		  readpixel[2] = fgetc( fd );
		  
		  
		  dest[cpt++] = readpixel[2];
		  dest[cpt++] = readpixel[1];
		  dest[cpt++] = readpixel[0];
		  
		  if (withAlpha)
		    dest[cpt++] = fgetc( fd );;    
		}
	    }
	  else
	    {
	      stat &= ~0x80;
	      stat++;
	      
	      readpixel[0] = fgetc( fd );
	      readpixel[1] = fgetc( fd );
	      readpixel[2] = fgetc( fd );
	      if (withAlpha)
		      readpixel[3] = fgetc( fd );
	      
	      for( k=0;k<stat;k++)
		{
		  dest[cpt++] = readpixel[2];
		  dest[cpt++] = readpixel[1];
		  dest[cpt++] = readpixel[0];
		  if (withAlpha)
		    dest[cpt++]= readpixel[3];
		  
		}
	      
	      
	    }
	}while( !feof( fd ) );
    }
  
  gimp_pixel_rgn_init (
		       &pixel_rgn, 
		       drawable, 
		       0, 0, 
		       drawable->width, 
		       drawable->height, 
		       TRUE, 
		       FALSE);
  
  if ( vertical == BOTTOM2TOP )
    {
     
      /* Execute horizontal mirror */
  
      long lineLength = (bpp/8)*width;
      
      
      buffer = (guchar *) g_malloc ( width * height * (bpp/8));
      
      if (buffer == NULL){
	printf("g_malloc error on buffer for mirroring...\n");
	return -1;
      }
      
      for( i=0; i<height;i++)
	{
	  int j;

    	  int addressSrc = i*lineLength;
	  int addressDst = (height - i - 1) * lineLength;
	  
	  for( j=0; j< lineLength;j++)
	    buffer[ addressDst + j]  = dest[ addressSrc+j ] ;
	}
      
      
      
      gimp_pixel_rgn_set_rect (
			       &pixel_rgn, 
			       buffer, 
			       0, 0, 
			       drawable->width, 
			       drawable->height);
      g_free( buffer );
    }
  else
    {
      
      gimp_pixel_rgn_set_rect (
			       &pixel_rgn, 
			       dest, 
			       0, 0, 
			       drawable->width, 
			       drawable->height);
    }
  
  g_free (dest);
  
  gimp_drawable_flush (drawable);
  gimp_drawable_detach (drawable);
  
  return image_ID;
}  /*read_image*/





guchar *pixels;

gint
save_image (char   *filename,
	    gint32  image_ID,
	    gint32  drawable_ID)
{
	GPixelRgn pixel_rgn;
	GDrawable *drawable;
	GDrawableType drawable_type;
	int width, height;
	int bpp;
	FILE *outfile;
	guchar *name_buf;
	int i;

	drawable = gimp_drawable_get(drawable_ID);
	drawable_type = gimp_drawable_type(drawable_ID);
	gimp_pixel_rgn_init(&pixel_rgn, drawable, 0, 0, drawable->width, drawable->height, FALSE, FALSE);

	name_buf = (guchar *) g_malloc(strlen(filename) + 11);
	sprintf(name_buf, "Saving %s:", filename);
	gimp_progress_init(name_buf);
	g_free(name_buf);

	if (tsvals.compression == COMPRESSION_RLE)
	  {
	    printf("Save with RLE compression not available for the moment...");
	    return FALSE;
	  }
	
	switch(drawable_type) {
	  
	case RGBA_IMAGE:
	  bpp = 32;
	  break;
	case RGB_IMAGE :
	  bpp = 24;
	  break;
	  
	default :
	  fprintf(stderr, "TGA: you should not receive this error for any reason\n");
	  break;
	}
	
	if((outfile = fopen(filename, "wb")) == NULL) {
		fprintf(stderr, "TGA: Can't open \"%s\"\n", filename);
		return FALSE;
	}

	pixels = (guchar *) g_malloc(drawable->width * drawable->height * bpp/8);

	gimp_pixel_rgn_get_rect(&pixel_rgn, pixels, 0, 0, drawable->width, drawable->height);

	width = drawable->width;
	height = drawable->height;
	
	tga_header.idLenght=0;
	tga_header.colorMaptype=0;
	tga_header.field=2;        /* True Color image non compressed */
	tga_header.colorMapIndex=0;
	tga_header.dummy=0;
	tga_header.xOrigin=0;
	tga_header.yOrigin=0;
	tga_header.width=width;
	tga_header.height=height;
	tga_header.bpp=tsvals.type==IMAGE_24_BIT?24:32;
	tga_header.descriptor=0x20;  /* from top to bottom */
	
	
	fwrite(&tga_header, sizeof(tga_header), 1, outfile);
	
	for (i=0; i<height; i++)
	  {
	    int j; 

	    for (j=0; j<width; j++)
	      {
		guchar red, green, blue;

		red = *pixels++;
		green = *pixels++;
		blue = *pixels++;

		fputc( blue, outfile );
		fputc( green, outfile );
		fputc( red, outfile );

		if ( bpp == 32 && tsvals.type==IMAGE_32_BIT_ALPHA)
		  fputc(  *pixels++, outfile );
		else
		  pixels++;
	      }
	    gimp_progress_update( (double) i / (double) height);
	  }

	gimp_drawable_detach(drawable);
	g_free(pixels);

	fclose(outfile);
	return TRUE;
}

static gint
save_dialog ()
{
  GtkWidget *dlg;
  GtkWidget *button;
  GtkWidget *toggle;
  GtkWidget *frame;
  GtkWidget *toggle_vbox;
  GSList *group;
  gchar **argv;
  gint argc;
  gint use_none = (tsvals.compression == COMPRESSION_NONE);
  gint use_rle = (tsvals.compression == COMPRESSION_RLE);
  gint use_alpha = (tsvals.type == IMAGE_32_BIT_ALPHA);
  gint use_no_alpha = (tsvals.type == IMAGE_24_BIT);

  argc = 1;
  argv = g_new (gchar *, 1);
  argv[0] = g_strdup ("save");

  gtk_init (&argc, &argv);
  gtk_rc_parse (gimp_gtkrc ());

  dlg = gtk_dialog_new ();
  gtk_window_set_title (GTK_WINDOW (dlg), "Save as Tga");
  gtk_window_position (GTK_WINDOW (dlg), GTK_WIN_POS_MOUSE);
  gtk_signal_connect (GTK_OBJECT (dlg), "destroy",
		      (GtkSignalFunc) save_close_callback,
		      NULL);

  /*  Action area  */
  button = gtk_button_new_with_label ("OK");
  GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
  gtk_signal_connect (GTK_OBJECT (button), "clicked",
                      (GtkSignalFunc) save_ok_callback,
                      dlg);
  gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dlg)->action_area), button, TRUE, TRUE, 0);
  gtk_widget_grab_default (button);
  gtk_widget_show (button);

  button = gtk_button_new_with_label ("Cancel");
  GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
  gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
			     (GtkSignalFunc) gtk_widget_destroy,
			     GTK_OBJECT (dlg));
  gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dlg)->action_area), button, TRUE, TRUE, 0);
  gtk_widget_show (button);

  /*  compression  */
  frame = gtk_frame_new ("Compression");
  gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_ETCHED_IN);
  gtk_container_border_width (GTK_CONTAINER (frame), 10);
  gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dlg)->vbox), frame, FALSE, TRUE, 0);
  toggle_vbox = gtk_vbox_new (FALSE, 5);
  gtk_container_border_width (GTK_CONTAINER (toggle_vbox), 5);
  gtk_container_add (GTK_CONTAINER (frame), toggle_vbox);

  group = NULL;
  toggle = gtk_radio_button_new_with_label (group, "None");
  group = gtk_radio_button_group (GTK_RADIO_BUTTON (toggle));
  gtk_box_pack_start (GTK_BOX (toggle_vbox), toggle, FALSE, FALSE, 0);
  gtk_signal_connect (GTK_OBJECT (toggle), "toggled",
		      (GtkSignalFunc) save_toggle_update,
		      &use_none);
  gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON (toggle), use_none);
  gtk_widget_show (toggle);

  toggle = gtk_radio_button_new_with_label (group, "RLE");
  group = gtk_radio_button_group (GTK_RADIO_BUTTON (toggle));
  gtk_box_pack_start (GTK_BOX (toggle_vbox), toggle, FALSE, FALSE, 0);
  gtk_signal_connect (GTK_OBJECT (toggle), "toggled",
		      (GtkSignalFunc) save_toggle_update,
		      &use_rle);
  gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON (toggle), use_rle);
  gtk_widget_show (toggle);

  gtk_widget_show (toggle_vbox);
  gtk_widget_show (frame);

  /*  fillorder  */
  frame = gtk_frame_new ("Image Type");
  gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_ETCHED_IN);
  gtk_container_border_width (GTK_CONTAINER (frame), 10);
  gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dlg)->vbox), frame, FALSE, TRUE, 0);
  toggle_vbox = gtk_vbox_new (FALSE, 5);
  gtk_container_border_width (GTK_CONTAINER (toggle_vbox), 5);
  gtk_container_add (GTK_CONTAINER (frame), toggle_vbox);

  group = NULL;
  toggle = gtk_radio_button_new_with_label (group, "24 bit");
  group = gtk_radio_button_group (GTK_RADIO_BUTTON (toggle));
  gtk_box_pack_start (GTK_BOX (toggle_vbox), toggle, FALSE, FALSE, 0);
  gtk_signal_connect (GTK_OBJECT (toggle), "toggled",
		      (GtkSignalFunc) save_toggle_update,
		      &use_no_alpha);
  gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON (toggle), use_no_alpha);
  gtk_widget_show (toggle);

  toggle = gtk_radio_button_new_with_label (group, "32 bit with Alpha");
  group = gtk_radio_button_group (GTK_RADIO_BUTTON (toggle));
  gtk_box_pack_start (GTK_BOX (toggle_vbox), toggle, FALSE, FALSE, 0);
  gtk_signal_connect (GTK_OBJECT (toggle), "toggled",
		      (GtkSignalFunc) save_toggle_update,
		      &use_alpha);
  gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON (toggle), use_alpha);
  gtk_widget_show (toggle);

  gtk_widget_show (toggle_vbox);
  gtk_widget_show (frame);

  gtk_widget_show (dlg);

  gtk_main ();
  gdk_flush ();

  if (use_none)
    tsvals.compression = COMPRESSION_NONE;
  else if (use_rle)
    tsvals.compression = COMPRESSION_RLE;


  if (use_no_alpha)
    tsvals.type = IMAGE_24_BIT;
  else if (use_alpha)
    tsvals.type = IMAGE_32_BIT_ALPHA;

  return tsint.run;
}

/*  Save interface functions  */

static void
save_close_callback (GtkWidget *widget,
		     gpointer   data)
{
  gtk_main_quit ();
}

static void
save_ok_callback (GtkWidget *widget,
		  gpointer   data)
{
  tsint.run = TRUE;
  gtk_widget_destroy (GTK_WIDGET (data));
}

static void
save_toggle_update (GtkWidget *widget,
		    gpointer   data)
{
  int *toggle_val;

  toggle_val = (int *) data;

  if (GTK_TOGGLE_BUTTON (widget)->active)
    *toggle_val = TRUE;
  else
    *toggle_val = FALSE;
}

/* The End */





