/*
 *  options.c:		FIASCO options handling
 *
 *  Written by:		Ullrich Hafner
 *		
 *  This file is part of FIASCO (Fractal Image And Sequence COdec)
 *  Copyright (C) 1994-2000 Ullrich Hafner
 */

/*
 *  $Date: 2000/10/28 17:39:31 $
 *  $Author: hafner $
 *  $Revision: 5.5 $
 *  $State: Exp $
 */

#define _DEFAULT_SOURCE 1 /* New name for SVID & BSD source defines */
#define _BSD_SOURCE 1   /* Make sure strdup() is in string.h */
#define _XOPEN_SOURCE 500  /* Make sure strdup() is in string.h */

#include "config.h"

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

#include "nstring.h"

#include "types.h"
#include "macros.h"
#include "error.h"

#include "wfa.h"
#include "misc.h"
#include "bit-io.h"
#include "fiasco.h"
#include "options.h"

fiasco_c_options_t *
fiasco_c_options_new (void)
/*
 *  FIASCO options constructor.
 *  Allocate memory for the FIASCO coder options structure and
 *  fill in default values.
 *
 *  Return value:
 *	pointer to the new option structure
 */
{
   c_options_t 	      *options = calloc (1, sizeof (c_options_t));
   fiasco_c_options_t *public  = calloc (1, sizeof (fiasco_c_options_t));

   if (!options || !public)
   {
      set_error (_("Out of memory."));
      return NULL;
   }
   public->private 	      = options;
   public->delete 	      = fiasco_c_options_delete;
   public->set_tiling 	      = fiasco_c_options_set_tiling;
   public->set_frame_pattern  = fiasco_c_options_set_frame_pattern;
   public->set_basisfile      = fiasco_c_options_set_basisfile;
   public->set_chroma_quality = fiasco_c_options_set_chroma_quality;
   public->set_optimizations  = fiasco_c_options_set_optimizations;
   public->set_video_param    = fiasco_c_options_set_video_param;
   public->set_quantization   = fiasco_c_options_set_quantization;
   public->set_progress_meter = fiasco_c_options_set_progress_meter;
   public->set_smoothing      = fiasco_c_options_set_smoothing;
   public->set_title   	      = fiasco_c_options_set_title;
   public->set_comment        = fiasco_c_options_set_comment;
   
   strcpy (options->id, "COFIASCO");

   /*
    *  Set default value of fiasco options
    */
   options->basis_name 		  = strdup ("small.fco");
   options->lc_min_level 	  = 4;
   options->lc_max_level 	  = 12;
   options->p_min_level 	  = 8;
   options->p_max_level 	  = 10;
   options->images_level 	  = 5;
   options->max_states 		  = MAXSTATES;
   options->chroma_max_states 	  = 40;
   options->max_elements 	  = MAXEDGES;
   options->tiling_exponent 	  = 4;
   options->tiling_method 	  = FIASCO_TILING_VARIANCE_DSC;
   options->id_domain_pool 	  = strdup ("rle");
   options->id_d_domain_pool 	  = strdup ("rle");
   options->id_rpf_model 	  = strdup ("adaptive");
   options->id_d_rpf_model 	  = strdup ("adaptive");
   options->rpf_mantissa 	  = 3;
   options->rpf_range 		  = FIASCO_RPF_RANGE_1_50;
   options->dc_rpf_mantissa 	  = 5;
   options->dc_rpf_range 	  = FIASCO_RPF_RANGE_1_00;
   options->d_rpf_mantissa 	  = 3;
   options->d_rpf_range 	  = FIASCO_RPF_RANGE_1_50;
   options->d_dc_rpf_mantissa 	  = 5;
   options->d_dc_rpf_range 	  = FIASCO_RPF_RANGE_1_00;
   options->chroma_decrease 	  = 2.0;
   options->prediction 		  = NO;
   options->delta_domains 	  = YES;
   options->normal_domains 	  = YES;
   options->search_range 	  = 16;
   options->fps 		  = 25;
   options->pattern 		  = strdup ("IPPPPPPPPP");
   options->reference_filename 	  = NULL;
   options->half_pixel_prediction = NO;
   options->cross_B_search 	  = YES;
   options->B_as_past_ref 	  = YES;
   options->check_for_underflow   = NO;
   options->check_for_overflow 	  = NO;
   options->second_domain_block   = NO;
   options->full_search 	  = NO;
   options->progress_meter 	  = FIASCO_PROGRESS_NONE;
   options->smoothing 	 	  = 70;
   options->comment 		  = strdup ("");
   options->title 		  = strdup ("");
   
   return public;
}

void
fiasco_c_options_delete (fiasco_c_options_t *options)
/*
 *  FIASCO options destructor.
 *  Free memory of FIASCO options struct.
 *
 *  No return value.
 *
 *  Side effects:
 *	structure 'options' is discarded.
 */
{
   c_options_t *this = cast_c_options (options);

   if (!this)
      return;
   
   Free (this->id_domain_pool);
   Free (this->id_d_domain_pool);
   Free (this->id_rpf_model);
   Free (this->id_d_rpf_model);
   Free (this->pattern);
   Free (this->comment);
   Free (this->title);
   
   Free (this);

   return;
}

int
fiasco_c_options_set_tiling (fiasco_c_options_t *options,
			     fiasco_tiling_e method, unsigned exponent)
/*
 *  Set tiling `method' and `exponent'.
 *  See type `fiasco_tiling_e' for a list of valid  tiling `methods'.
 *  The image is subdivied into 2^`exponent' tiles
 *
 *  Return value:
 *	1 on success
 *	0 otherwise
 */
{
   c_options_t *this = (c_options_t *) cast_c_options (options);

   if (!this)
   {
      return 0;
   }
   switch (method)
   {
      case FIASCO_TILING_SPIRAL_ASC:
      case FIASCO_TILING_SPIRAL_DSC:
      case FIASCO_TILING_VARIANCE_ASC:
      case FIASCO_TILING_VARIANCE_DSC:
	 this->tiling_method = method;
	 break;
      default:
	 set_error (_("Invalid tiling method `%d' specified "
		      "(valid methods are 0, 1, 2, or 3)."), method);
	 return 0;
   }
   this->tiling_exponent = exponent;
   
   return 1;
}

int
fiasco_c_options_set_frame_pattern (fiasco_c_options_t *options,
				    const char *pattern)
/*
 *  Set `pattern' of input frames.
 *  `pattern' has to be a sequence of the following
 *  characters (case insensitive):
 *  'i' intra frame
 *  'p' predicted frame
 *  'b' bidirectional predicted frame
 *  E.g. pattern = 'IBBPBBPBB'
 *
 *  When coding video frames the prediction type of input frame N is determined
 *  by reading `pattern' [N] (`pattern' is periodically extended).
 *  
 *  Return value:
 *	1 on success
 *	0 otherwise
 */
{
   c_options_t *this = (c_options_t *) cast_c_options (options);

   if (!this)
   {
      return 0;
   }
   else if (!pattern)
   {
      set_error (_("Parameter `%s' not defined (NULL)."), "pattern");
      return 0;
   }
   else if (strlen (pattern) < 1)
   {
      set_error (_("Frame type pattern doesn't contain any character."));
      return 0;
   }
   else
   {
      const char *str;
      bool_t 	  parse_error = NO;
      int	  c 	      = 0;
      
      for (str = pattern; *str && !parse_error; str++)
	 switch (*str)
	 {
	    case 'i':
	    case 'I':
	    case 'b':
	    case 'B':
	    case 'p':
	    case 'P':
	       break;
	    default:
	       c = *str;
	       parse_error = YES;
	 }

      if (parse_error)
      {
	 set_error (_("Frame type pattern contains invalid character `%c' "
		      "(choose I, B or P)."), c);
	 return 0;
      }
      else
      {
	 Free (this->pattern);
	 this->pattern = strdup (pattern);

	 return 1;
      }
   }
}

int
fiasco_c_options_set_basisfile (fiasco_c_options_t *options,
				const char *filename)
/*
 *  Set `filename' of FIASCO initial basis.
 *  
 *  Return value:
 *	1 on success (if the file is readable)
 *	0 otherwise
 */
{
   c_options_t *this = (c_options_t *) cast_c_options (options);

   if (!this)
   {
      return 0;
   }
   else if (!filename)
   {
      set_error (_("Parameter `%s' not defined (NULL)."), "filename");
      return 0;
   }
   else
   {
       /* Skip this because basis file may be linked with program, not
          in a separate file.  See get_linked_basis().  NETPBM
      FILE *file = open_file (filename, "FIASCO_DATA", READ_ACCESS);
      if (file)
      {
	 fclose (file);
	 return 1;
      }
      else
      {
	 set_error (_("Can't read basis file `%s'.\n%s."), filename,
		    get_system_error ());
	 return 0;
      }
      */ return 1;
   }
}

int
fiasco_c_options_set_chroma_quality (fiasco_c_options_t *options,
				     float quality_factor,
				     unsigned dictionary_size)
/*
 *  Set color compression parameters.
 *  When coding chroma channels (Cb and Cr)
 *  - approximation quality is given by `quality_factor' * `Y quality' and
 *  - `dictionary_size' gives the number of dictionary elements.
 *  
 *  If 'quality' <= 0 then the luminancy coding quality is also during
 *  chroma channel coding.
 *  
 *  Return value:
 *	1 on success
 *	0 otherwise
 */
{
   c_options_t *this = (c_options_t *) cast_c_options (options);

   if (!this)
   {
      return 0;
   }
   else if (!dictionary_size)
   {
      set_error (_("Size of chroma compression dictionary has to be "
		   "a positive number."));
      return 0;
   }
   else if (quality_factor <= 0)
   {
      set_error (_("Quality of chroma channel compression has to be "
		   "positive value."));
      return 0;
   }
   else
   {
      this->chroma_decrease   = quality_factor;
      this->chroma_max_states = dictionary_size;

      return 1;
   }
}

int
fiasco_c_options_set_optimizations (fiasco_c_options_t *options,
				    unsigned min_block_level,
				    unsigned max_block_level,
				    unsigned max_elements,
				    unsigned dictionary_size,
				    unsigned optimization_level)
/*
 *  Set various optimization parameters.
 *  - During compression only image blocks of size
 *    {`min_block_level', ... ,`max_block_level'} are considered.
 *    The smaller this set of blocks is the faster the coder runs
 *    and the worse the image quality will be.  
 *  - An individual approximation may use at most `max_elements'
 *    elements of the dictionary which itself contains at most
 *    `dictionary_size' elements. The smaller these values are
 *    the faster the coder runs and the worse the image quality will be. 
 *  - `optimization_level' enables some additional low level optimizations.
 *    0: standard approximation method
 *    1: significantly increases the approximation quality,
 *       running time is twice as high as with the standard method
 *    2: hardly increases the approximation quality of method 1, 
 *       running time is twice as high as with method 1
 *       (this method just remains for completeness)
 *
 *  Return value:
 *	1 on success
 *	0 otherwise
 */
{
   c_options_t *this = (c_options_t *) cast_c_options (options);

   if (!this)
   {
      return 0;
   }
   else if (!dictionary_size)
   {
      set_error (_("Size of dictionary has to be a positive number."));
      return 0;
   }
   else if (!max_elements)
   {
      set_error (_("At least one dictionary element has to be used "
		   "in an approximation."));
      return 0;
   }
   else if (max_block_level < 4)
   {
      set_error (_("Maximum image block size has to be at least level 4."));
      return 0;
   }
   else if (min_block_level < 4)
   {
      set_error (_("Minimum image block size has to be at least level 4."));
      return 0;
   }
   else if (max_block_level < min_block_level)
   {
      set_error (_("Maximum block size has to be larger or "
		   "equal minimum block size."));
      return 0;
   }
   else
   {
      this->lc_min_level 	= min_block_level;
      this->lc_max_level 	= max_block_level;
      this->max_states 	 	= dictionary_size;
      this->max_elements 	= max_elements;
      this->second_domain_block = optimization_level > 0 ? YES : NO;
      this->check_for_overflow  = optimization_level > 1 ? YES : NO;
      this->check_for_underflow = optimization_level > 1 ? YES : NO;
      this->full_search 	= optimization_level > 1 ? YES : NO;

      return 1;
   }
}

int
fiasco_c_options_set_prediction (fiasco_c_options_t *options,
				 int intra_prediction,
				 unsigned min_block_level,
				 unsigned max_block_level)
/*
 *  Set minimum and maximum size of image block prediction to
 *  `min_block_level' and `max_block_level'.
 *  (For either motion compensated prediction of inter frames
 *   or DC based prediction of intra frames)
 *  Prediction of intra frames is only used if `intra_prediction' != 0.
 *
 *  Return value:
 *	1 on success
 *	0 otherwise
 */
{
   c_options_t *this = (c_options_t *) cast_c_options (options);

   if (!this)
   {
      return 0;
   }
   else if (max_block_level < 6)
   {
      set_error (_("Maximum prediction block size has to be "
		   "at least level 6"));
      return 0;
   }
   else if (min_block_level < 6)
   {
      set_error (_("Minimum prediction block size has to be "
		   "at least level 6"));
      return 0;
   }
   else if (max_block_level < min_block_level)
   {
      set_error (_("Maximum prediction block size has to be larger or "
		   "equal minimum block size."));
      return 0;
   }
   else
   {
      this->p_min_level = min_block_level;
      this->p_max_level = max_block_level;
      this->prediction  = intra_prediction;
      
      return 1;
   }
}

int
fiasco_c_options_set_video_param (fiasco_c_options_t *options,
				  unsigned frames_per_second,
				  int half_pixel_prediction,
				  int cross_B_search,
				  int B_as_past_ref)
/*
 *  Set various parameters used for video compensation.
 *  'frames_per_second' defines the frame rate which should be
 *  used when the video is decoded. This value has no effect during coding,
 *  it is just passed to the FIASCO output file.
 *  If 'half_pixel_prediction' is not 0 then half pixel precise
 *  motion compensated prediction is used.
 *  If 'cross_B_search' is not 0 then the fast Cross-B-Search algorithm is
 *  used to determine the motion vectors of interpolated prediction. Otherwise
 *  exhaustive search (in the given search range) is used.
 *  If 'B_as_past_ref' is not 0 then B frames are allowed to be used
 *  for B frame predicion.
 *  
 *  Return value:
 *	1 on success
 *	0 otherwise
 */
{
   c_options_t *this = (c_options_t *) cast_c_options (options);

   if (!this)
   {
      return 0;
   }
   else
   {
      this->fps 	  	  = frames_per_second;
      this->half_pixel_prediction = half_pixel_prediction;
      this->cross_B_search 	  = cross_B_search;
      this->B_as_past_ref 	  = B_as_past_ref;

      return 1;
   }
}

int
fiasco_c_options_set_quantization (fiasco_c_options_t *options,
				   unsigned mantissa,
				   fiasco_rpf_range_e range,
				   unsigned dc_mantissa,
				   fiasco_rpf_range_e dc_range)
/*
 *  Set accuracy of coefficients quantization.
 *  DC coefficients (of the constant dictionary vector f(x,y) = 1)
 *  are quantized to values of the interval [-`dc_range', `dc_range'] using
 *  #`dc_mantissa' bits. All other quantized coefficients are quantized in
 *  an analogous way using the parameters `range' and `mantissa'.
 *  
 *  Return value:
 *	1 on success
 *	0 otherwise
 */
{
   c_options_t *this = (c_options_t *) cast_c_options (options);

   if (!this)
   {
      return 0;
   }
   else if (mantissa < 2 || mantissa > 8 || dc_mantissa < 2 || dc_mantissa > 8)
   {
      set_error (_("Number of RPF mantissa bits `%d', `%d' have to be in "
		   "the interval [2,8]."), mantissa, dc_mantissa);
      return 0;
   }
   else
   {
      if ((range == FIASCO_RPF_RANGE_0_75
	  || range == FIASCO_RPF_RANGE_1_00
	  || range == FIASCO_RPF_RANGE_1_50
	   || range == FIASCO_RPF_RANGE_2_00)
	  &&
	  (dc_range == FIASCO_RPF_RANGE_0_75
	   || dc_range == FIASCO_RPF_RANGE_1_00
	   || dc_range == FIASCO_RPF_RANGE_1_50
	   || dc_range == FIASCO_RPF_RANGE_2_00))
      {
	 this->rpf_range       = range;
	 this->dc_rpf_range    = dc_range;
	 this->rpf_mantissa    = mantissa;
	 this->dc_rpf_mantissa = dc_mantissa;

	 return 1;
      }
      else
      {
	 set_error (_("Invalid RPF ranges `%d', `%d' specified."),
		    range, dc_range);
	 return 0;
      }
   }
}

int
fiasco_c_options_set_progress_meter (fiasco_c_options_t *options,
				     fiasco_progress_e type)
/*
 *  Set type of progress meter.
 *
 *  Return value:
 *	1 on success
 *	0 otherwise
 */
{
   c_options_t *this = (c_options_t *) cast_c_options (options);

   if (!this)
   {
      return 0;
   }
   switch (type)
   {
      case FIASCO_PROGRESS_BAR:
      case FIASCO_PROGRESS_PERCENT:
      case FIASCO_PROGRESS_NONE:
	 this->progress_meter = type;
	 break;
      default:
	 set_error (_("Invalid progress meter `%d' specified "
		      "(valid values are 0, 1, or 2)."), type);
	 return 0;
   }
   return 1;
}

int
fiasco_c_options_set_smoothing (fiasco_c_options_t *options, int smoothing)
/*
 *  Define `smoothing'-percentage along partitioning borders.
 *  
 *  Return value:
 *	1 on success
 *	0 otherwise
 */
{
   c_options_t *this = (c_options_t *) cast_c_options (options);

   if (!this)
   {
      return 0;
   }
   else if (smoothing < -1 || smoothing > 100)
   {
      set_error (_("Smoothing percentage must be in the range [-1, 100]."));
      return 0;
   }
   else
   {
      this->smoothing = smoothing;
      return 1;
   }
}

int
fiasco_c_options_set_comment (fiasco_c_options_t *options, const char *comment)
/*
 *  Define `comment' of FIASCO stream.
 *  
 *  Return value:
 *	1 on success
 *	0 otherwise
 */
{
   c_options_t *this = (c_options_t *) cast_c_options (options);

   if (!this)
   {
      return 0;
   }
   else if (!comment)
   {
      set_error (_("Parameter `%s' not defined (NULL)."), "title");
      return 0;
   }
   else
   {
      this->comment = strdup (comment);
      return 1;
   }
}

int
fiasco_c_options_set_title (fiasco_c_options_t *options, const char *title)
/*
 *  Define `title' of FIASCO stream.
 *  
 *  Return value:
 *	1 on success
 *	0 otherwise
 */
{
   c_options_t *this = (c_options_t *) cast_c_options (options);

   if (!this)
   {
      return 0;
   }
   else if (!title)
   {
      set_error (_("Parameter `%s' not defined (NULL)."), "title");
      return 0;
   }
   else
   {
      this->title = strdup (title);
      return 1;
   }
}

c_options_t *
cast_c_options (fiasco_c_options_t *options)
/*
 *  Cast generic pointer `options' to type c_options_t.
 *  Check whether `options' is a valid object of type c_options_t.
 *
 *  Return value:
 *	pointer to options struct on success
 *      NULL otherwise
 */
{
   c_options_t *this = (c_options_t *) options->private;
   if (this)
   {
      if (!streq (this->id, "COFIASCO"))
      {
	 set_error (_("Parameter `options' doesn't match required type."));
	 return NULL;
      }
   }
   else
   {
      set_error (_("Parameter `%s' not defined (NULL)."), "options");
   }

   return this;
}

/**************************************************************************
***************************************************************************
			       DECODER
***************************************************************************
**************************************************************************/

fiasco_d_options_t *
fiasco_d_options_new (void)
/*
 *  FIASCO options constructor.
 *  Allocate memory for the FIASCO coder options structure and
 *  fill in default values.
 *
 *  Return value:
 *	pointer to the new option structure
 */
{
   d_options_t 	      *options = calloc (1, sizeof (d_options_t));
   fiasco_d_options_t *public  = calloc (1, sizeof (fiasco_d_options_t));

   if (!options || !public)
   {
      set_error (_("Out of memory."));
      return NULL;
   }
   public->private 	      = options;
   public->delete 	      = fiasco_d_options_delete;
   public->set_smoothing      = fiasco_d_options_set_smoothing;
   public->set_magnification  = fiasco_d_options_set_magnification;
   public->set_4_2_0_format   = fiasco_d_options_set_4_2_0_format;
   
   strcpy (options->id, "DOFIASCO");

   /*
    *  Set default value of fiasco decoder options
    */
   options->smoothing 	  = 70;
   options->magnification = 0;
   options->image_format  = FORMAT_4_4_4;
   
   return public;
}

void
fiasco_d_options_delete (fiasco_d_options_t *options)
/*
 *  FIASCO options destructor.
 *  Free memory of FIASCO options struct.
 *
 *  No return value.
 *
 *  Side effects:
 *	structure 'options' is discarded.
 */
{
   d_options_t *this = cast_d_options (options);

   if (!this)
      return;
   
   Free (this);

   return;
}

int
fiasco_d_options_set_smoothing (fiasco_d_options_t *options, int smoothing)
/*
 *  Define `smoothing'-percentage along partitioning borders.
 *  
 *  Return value:
 *	1 on success
 *	0 otherwise
 */
{
   d_options_t *this = (d_options_t *) cast_d_options (options);

   if (!this)
   {
      return 0;
   }
   else if (smoothing < -1 || smoothing > 100)
   {
      set_error (_("Smoothing percentage must be in the range [-1, 100]."));
      return 0;
   }
   else
   {
      this->smoothing = smoothing;
      return 1;
   }
}

int
fiasco_d_options_set_magnification (fiasco_d_options_t *options, int level)
/*
 *  Set magnification-'level' of decoded image.
 *  0: width x height of original image
 *  1: (2 * width) x (2 * height) of original image
 *  -1: (width / 2 ) x (height / 2) of original image
 *  etc.
 *
 *  Return value:
 *	1 on success
 *	0 otherwise
 */
{
   d_options_t *this = (d_options_t *) cast_d_options (options);

   if (!this)
   {
      return 0;
   }
   else
   {
      this->magnification = level;
      return 1;
   }
}

int
fiasco_d_options_set_4_2_0_format (fiasco_d_options_t *options, int format)
/*
 *  Set image format to 4:2:0 or 4:4:4.
 *  
 *  Return value:
 *	1 on success
 *	0 otherwise
 */
{
   d_options_t *this = (d_options_t *) cast_d_options (options);

   if (!this)
   {
      return 0;
   }
   else
   {
      this->image_format = format ? FORMAT_4_2_0 : FORMAT_4_4_4;
      return 1;
   }
}

d_options_t *
cast_d_options (fiasco_d_options_t *options)
/*
 *  Cast generic pointer `options' to type d_options_t.
 *  Check whether `options' is a valid object of type d_options_t.
 *
 *  Return value:
 *	pointer to options struct on success
 *      NULL otherwise
 */
{
   d_options_t *this = (d_options_t *) options->private;
   
   if (this)
   {
      if (!streq (this->id, "DOFIASCO"))
      {
	 set_error (_("Parameter `options' doesn't match required type."));
	 return NULL;
      }
   }
   else
   {
      set_error (_("Parameter `%s' not defined (NULL)."), "options");
   }

   return this;
}


