/*
 *  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 <stdbool.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_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 * const optionsP,
                            fiasco_tiling_e      const method,
                            unsigned int         const exponent) {
/*----------------------------------------------------------------------------
  Set tiling 'method' and 'exponent'.

  See type 'fiasco_tiling_e' for a list of valid tiling methods.

  The image is subdivided into 2^`exponent' tiles.

  Return value:
     1 on success
     0 otherwise
-----------------------------------------------------------------------------*/
    c_options_t * const this = (c_options_t *) cast_c_options(optionsP);

    int retval;

    if (!this) {
        retval = 0;
    } else {
        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;
        }
        this->tiling_exponent = exponent;

        retval = 1;
    }
    return retval;
}



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;
   }
}



void
fiasco_c_options_set_optimizations(
   fiasco_c_options_t * const optionsP,
   unsigned int         const minBlockLevel,
   unsigned int         const maxBlockLevel,
   unsigned int         const maxElements,
   unsigned int         const dictionarySize,
   unsigned int         const optimizationLevel,
   bool *               const succeededP) {
/*----------------------------------------------------------------------------
  Set various optimization parameters.
  - During compression only image blocks of size
    {'minBlockLevel', ... ,'maxBlockLevel'} 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
    'dictionarySize' elements. The smaller these values are
    the faster the coder runs and the worse the image quality will be.
  - 'optimizationLevel' 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 * const this = (c_options_t *) cast_c_options(optionsP);

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

      *succeededP = true;
   }
}



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;
}


