
/* LiViDO host loader example
 *
 * this code is describing how to access a livido plugin
 * make it with:
 * $ gcc $CFLAGS -o host_example host_example.c
 * $ ld -E -z now -shared plugin_host.o -o plugin_host.so
 *
 * Copyleft (C) 2004 by
 *     Denis "jaromil" Rojo - http://rastasoft.org
 *
 * This source code is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Public License as published 
 * by the Free Software Foundation; either version 2 of the License,
 * or (at your option) any later version.
 *
 * This source code is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * Please refer to the GNU Public License for more details.
 *
 * You should have received a copy of the GNU Public License along with
 * this source code; if not, write to:
 * Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * $Id: host_example.c 64 2004-11-14 22:53:21Z jaromil $
 *
 */

/** @file host_example.c
    
    @brief LiViDO loader host implementation example
    
    @todo comment the loader host example
 */


#include <dlfcn.h>
#include <errno.h>

#include <livido.h>

#define VIDEO_WIDTH  640
#define VIDEO_HEIGHT 480

// forward declaration of debugging functions at the end of this file
int check_channel_templates(livido_channel_template_t *channel_templates);
int check_parameter_templates(livido_parameter_template_t *parameter_templates);







//////////////////////////////////////////////////////////////////
// MEMORY HANDLING FUNCTIONS
// this function allocates a blank channel from its template
// all buffers must be allocated and free'd by the host
livido_channel_t *alloca_channel(livido_channel_template_t *tmpl) {
  
  livido_channel_t *chan;

  chan = calloc(1,sizeof(livido_channel_t));
  
  /// save a reference to the template we used to create
  chan->templ = tmpl;
  
  /// setup our palette (it was allready checked for its presence)
  chan->palette = LIVIDO_PALETTE_RGB32;

  /// you want to set shift_u and shift_v here if your palette is planar
  if(tmpl->width>0) chan->width = tmpl->width; // force the width
  if(tmpl->height>0) chan->height = tmpl->height; // force the height
  
  return chan;
}

void free_channels(livido_channel_t *ch) { 
  livido_channel_t *tmp;
  while(ch) {
    tmp = ch->next;
    free(ch);
    ch = tmp;
  }
}

livido_parameter_t *alloca_parameter(livido_parameter_template_t *tmpl) {
  int len;
  livido_parameter_t *par = calloc(1,sizeof(livido_parameter_template_t));

  // assign the template to this instance
  par->templ = tmpl;

  // here copy the default into the actual value

  /*
    Caveat for multi language applications:

    if LC_NUMERIC != "C" then adjust the default value according to the
    interpreted RADIXCHAR and THOUSEP before doing the actual parse.

    Basically ou should take care of the famigerate localization of
    radix comma and thousands separators: when defaults are statically
    declared inside the plugins, they are in the usual "." separated,
    but languages might have different requiremants:
      `1234567.89' in the POSIX locale,
      `1234567,89' in the nl_NL locale,
      `1.234.567,89' in the da_DK locale
    The best solution should be the one of verifying the RADIXCHAR
    and THOUSEP using nl_langinfo(3), as well localeconv(3)
  */
  len = strlen( tmpl->def );
  par->value = calloc( len+1, sizeof(char) );
  strncpy( par->value, tmpl->def, len );

  return par;
}

/// free a whole parameter list
void free_parameters( livido_parameter_t *par ) {
  livido_parameter_t *tmp;

  while(par) {
    tmp = par->next;

    if(par->value) free(par->value);

    if(par->extra) free(par->extra);

    free(par);

    par = tmp;
  }
  
}

// END OF MEMORY HANDLING FUNCTIONS
///////////////////////////////////////////////////////////////////////




int main(int argc, char **argv) {

  /// variable declarations
  void *plugin_handle;

  livido_setup_f *livido_setup;

  livido_instance_template_t *effect_template;
  livido_instance_template_t *first_instance_template;

  livido_channel_template_t *channel_template;
  livido_parameter_template_t *parameter_template;

  livido_frame_t frame_input1;
  livido_frame_t frame_output1;

  livido_parameter_t *parameter;

  livido_channel_t *channel;

  livido_instance_t *effect;

  int in_channels_num;
  int out_channels_num;
  int in_parameters_num;
  int out_parameters_num;

  int result;
  int c;

  /////////////////////////

  if(argc<2) {
    fprintf(stderr,"we miss an argument: a livido plugin.so file\n");
    exit(0);
  }

  /// dynamic loader of shared objects
  plugin_handle = dlopen(argv[1],RTLD_NOW);
  if(!plugin_handle) {
    fprintf(stderr,"error on dlopen :%s : %s\n",argv[1],dlerror());
    exit(0);
  }

  // call the general livido_setup function to open the plugin object
  livido_setup = (livido_setup_f*) dlsym(plugin_handle,"livido_setup");
  if(!livido_setup) {
    fprintf(stderr,"error on dlsym :livido_setup : %s\n",dlerror());
    exit(0);
  }
  
  /// get the template (can be a linked list)
  effect_template = (livido_instance_template_t*) (*livido_setup)();
  if(!effect_template) {
    fprintf(stderr,"error calling livido_setup");
    exit(0);
  }

  if(effect_template->next != NULL)
    fprintf(stderr,"this plugin contains multiple effects\n");

  first_instance_template = effect_template;

  // cycle thru effect templates
  while(effect_template) {

    
    fprintf(stderr,"found effect name %s version %u\n",
	    effect_template->name, effect_template->version);
    fprintf(stderr,"%s\n", effect_template->description);
    fprintf(stderr,"by %s\n", effect_template->author);
    fprintf(stderr,"properties:"); // check property flags
    if(effect_template->flags & LIVIDO_PROPERTY_REALTIME)
      fprintf(stderr," realtime,");
    if(effect_template->flags & LIVIDO_PROPERTY_CAN_DO_INPLACE)
      fprintf(stderr," inplace,");
    if(effect_template->flags & LIVIDO_PROPERTY_TRANSITION)
      fprintf(stderr," transition,");
    if(effect_template->flags & LIVIDO_PROPERTY_CAN_DO_SCALED)
      fprintf(stderr," can do scaled,");
    if(effect_template->flags & LIVIDO_PROPERTY_CAN_DO_WINDOWED)
      fprintf(stderr," can do windowed,");
    if(effect_template->flags & LIVIDO_PROPERTY_SELF_AUTOMATION)
      fprintf(stderr," auto keyframing,");
    if(effect_template->flags & LIVIDO_PROPERTY_FPS_NEEDED)
      fprintf(stderr," need fps,");
    if(effect_template->flags & LIVIDO_PROPERTY_HOST_CAN_RESIZE)    
      fprintf(stderr," host can resize,");
    if(effect_template->flags & LIVIDO_PROPERTY_HOST_CAN_CHANGE_PALETTE)
      fprintf(stderr," palettes can be changed");
    fprintf(stderr,".\n");
    fprintf(stderr,"\nproceed to test plugin:\n\n");

    ///////////////////////////////////////////////////////////
    /** CREATE AN EFFECT INSTANCE from the TEMPLATE

	the following code takes a template and creates an instance from it;
	parses all input channels and parameters, allocating their buffers.
    */

    /// allocate our effect instance
    effect = (livido_instance_t*) calloc(1,sizeof(livido_instance_t));


    ////////////////////////////////
    /// INPUT CHANNELS

    // use our pointer to parse channel templates
    channel_template = effect_template->in_channel_templates;
    
    /// check input channels and returns the number
    in_channels_num = check_channel_templates( channel_template );

    /** we use as many channels as we want
	here is really up to the host to decide wether to support
	multiple channel plugins, and how to handle them.
	effect plugins will usually have one main channel for in and one for out */

    /// allocate the channel from the template
    channel = alloca_channel( channel_template );

    /// setup width of the video
    channel->width = VIDEO_WIDTH;
    /// setup height of the video
    channel->height = VIDEO_HEIGHT;

    /// assign the channel to the frame we are going to use in processing
    frame_input1.channel = channel;

    /// save it in our effect instance
    effect->in_channels = channel;

    /// parse thru the other input channels
    /// in this host we support only single channels, so we check wether the
    /// others are optional or not to be sure we can use the plugin
    /// this kind of controls should be adapted to your host's purpose
    /// you might want to have different classes of plugins handled in different ways
    if(channel_template->next != NULL) {
      fprintf(stderr,"multiple input channels not supported by the example host\n");

      while(channel_template->next) {
	channel_template = (livido_channel_template_t*) channel_template->next;
	fprintf(stderr,"input channel %s",channel_template->name);

	if(channel_template->flags & LIVIDO_CHANNEL_OPTIONAL)
	  fprintf(stderr," is optional and will be not used\n");
	else
	  fprintf(stderr," is required! we can't really use this plugin\n");

      }
    }


    

    ////////////////////////////////
    /// OUTPUT CHANNELS

    // use our pointer to parse channel templates
    channel_template = effect_template->out_channel_templates;

    /// check output channels and returns the number
    out_channels_num = check_channel_templates( channel_template );

    /// allocate the channel from the template
    channel = alloca_channel( channel_template );

    /// setup width of the video
    channel->width = VIDEO_WIDTH;
    /// setup height of the video
    channel->height = VIDEO_HEIGHT;

    /// assign the channel to the output frame result of processing
    frame_output1.channel = channel;

    /// save it in our effect instance
    effect->out_channels = channel;

    /// parse thru the other input channels
    /// in this host we support only single channels, so we check wether the
    /// others are optional or not to be sure we can use the plugin
    /// this kind of controls should be adapted to your host's purpose
    /// you might want to have different classes of plugins handled in different ways
    if(channel_template->next != NULL) {
      fprintf(stderr,"multiple output channels not supported by the example host\n");

      while(channel_template->next) {
	channel_template = (livido_channel_template_t*)channel_template->next;
	fprintf(stderr,"output channel %s",channel_template->name);

	if(channel_template->flags & LIVIDO_CHANNEL_OPTIONAL)
	  fprintf(stderr," is optional and will be not used\n");
	else
	  fprintf(stderr," is required! we can't really use this plugin\n");

      }
    }




    ////////////////////////////////
    /// INPUT PARAMETERS

    /// use our pointer to parse parameters
    parameter_template = (livido_parameter_template_t*) effect_template->in_parameter_templates;

    /// check all parameters and count them
    in_parameters_num = check_parameter_templates( parameter_template );

    if(!in_parameters_num)
      fprintf(stderr,"there are no input parameters for this effect\n");
    else
      for( c = 1 ;
	   parameter_template != NULL ;
	   parameter_template =  parameter_template->next, c++ ) {
	
	/// allocate the parameter setting defaults in place
	parameter = alloca_parameter( parameter_template );
	
	if(c==1) /// save the first of the list inside the effect instance
	  effect->in_parameters = parameter;
	
	/// here you can set it with your starting values, in 'c' is the param number
	/// allocate parameter array for instance
      }



    ////////////////////////////////
    /// OUTPUT PARAMETERS

    /// use our pointer to parse parameters
    parameter_template =  effect_template->out_parameter_templates;

    /// check all parameters and count them
    out_parameters_num = check_parameter_templates( parameter_template );

    if(!out_parameters_num)
      fprintf(stderr,"there are no output parameters for this effect\n");
    else
      for( c = 1 ;
	   parameter_template != NULL ;
	   parameter_template = parameter_template->next, c++ ) {

	/// allocate the parameter setting defaults in place
	parameter = alloca_parameter( parameter_template );

	if(c==1) /// save the first of the list inside the effect instance
	  effect->out_parameters = parameter;
	
	/// here you can set it with your starting values,
	/// in 'c' is the param number starting from 1
	/// you can parse parameters thru this cycle

      }


    ////////////////////////////////
    /// KEYFRAMING
    /// this setup is optional for hosts that need detailed keyframing and
    /// interpolation of parameters to be done in the plugin: mostly NLE apps
    //    effect->keyframe_next = get_next_keyframe; // can set to NULL if you don't need,
    //    effect->keyframe_prev = get_prev_keyframe; // not all plugins use this keyframing
    // the above presumes you implemented your own get_*_keyframe host functions
    


    fprintf(stderr,"\n");
    
    /// initializing
    result = (*effect_template->init)( (livido_instance_t*)effect );
    
    if(result != LIVIDO_NO_ERROR)
      fprintf(stderr,"effect %s init: error code number %i\n",
	      effect_template->name, result);
    else
      fprintf(stderr,"effect %s succesfully initialized\n",
	      effect_template->name);
    
    
    ////////////////////////////////////////////////////////
    /// processing
    
    {
      float num, res;
      
      /// numerical parameters are all of type float
      /// you need to explicit it on scalar numbers
      /// for example use 56.0 or 56.f instead of simply 56

      for( num=0 ; num<10.0 ; num++) {

	livido_set_parameter( effect->in_parameters, num);

	result = (*effect_template->process)( effect, &frame_input1, &frame_output1 );
	
	livido_get_parameter( effect->in_parameters, &res);
	
	//	fprintf(stderr,"host example: parameter value is %f\n",res);

      }
    }



    
    /// de-initializing
    result = (*effect_template->deinit)( effect );
    
    if(result != LIVIDO_NO_ERROR)
      fprintf(stderr,"effect %s deinit: error code number %i\n",
	      effect_template->name, result);
    else
      fprintf(stderr,"effect %s succesfully de-initialized\n",
	      effect_template->name);
   
    /// loop to the next effect
    effect_template = effect_template->next;    
  }

  fprintf(stderr,"\n\nuntil here, all ok! :)\n");
  fprintf(stderr,"deallocating buffers and closing\n");

  //  free_effects( effect );

  dlclose(plugin_handle);

  exit(1);
}





/////////////////////////////////////////////////////////
/// DEBUG FUNCTIONS TO CHECK DATA STRUCTURES


/**
  @returns: the number of channels in this template */
int check_channel_templates(livido_channel_template_t *channel_templates) {
  int c;
  int *palettes;
  livido_channel_template_t *ch_tmpl;

  if(!channel_templates) return 0;

  // cycle thru channel templates
  
  for(ch_tmpl = channel_templates, c = 1 ;
      ch_tmpl != NULL ;
      ch_tmpl = ch_tmpl->next, c++) {
    
    
    fprintf(stderr,"channel %i name %s\n",c,ch_tmpl->name);
    
    /// PALETTE CHECK
    for(palettes = ch_tmpl->palettes;
	palettes != LIVIDO_PALETTE_END; palettes++) { // cycle thru supported palettes
      
      if(*palettes == LIVIDO_PALETTE_RGB32) break; // found our palette!
    }
    if(*palettes == LIVIDO_PALETTE_END) { // our palette was not found
      fprintf(stderr,"palette RGB32 not supported by channel\n");
      exit(0);
    } else
      fprintf(stderr,"palette RGB32 is supported on this channel\n");


    
    if(ch_tmpl->flags & LIVIDO_CHANNEL_OPTIONAL)
      fprintf(stderr,"channel is optional, can be switched off\n");

    
    /// coercion check of ::livido_channel_template_t::same_as
    if(ch_tmpl->same_as==0)
      fprintf(stderr,"channel has free choice for palette and size\n");
    else if(ch_tmpl->same_as)
      /// check the flags #LIVIDO_CHANNEL_SAME_AS_SIZE and SAME_AS_PALETTE
      fprintf(stderr,"channel must match %s channel %i for:%s%s\n",
	      (ch_tmpl->same_as>0)?"input":"output",
	      (ch_tmpl->same_as>0)?ch_tmpl->same_as:-(ch_tmpl->same_as),
	      (ch_tmpl->flags&LIVIDO_CHANNEL_SAME_AS_SIZE)?" size":" ",
	      (ch_tmpl->flags&LIVIDO_CHANNEL_SAME_AS_PALETTE)?" palette":" ");

    
    if(ch_tmpl->width != 0)
      fprintf(stderr,"channel supports only width %i\n",ch_tmpl->width);
    
    if(ch_tmpl->height != 0)
      fprintf(stderr,"channel supports only height %i\n",ch_tmpl->height);

    if(!ch_tmpl->width && !ch_tmpl->height)
      fprintf(stderr,"channel can support any width and height\n");

    if(ch_tmpl->flags & LIVIDO_CHANNEL_RESIZABLE)
      fprintf(stderr,"channel can be resized while processing\n");
    
    if(ch_tmpl->flags & LIVIDO_CHANNEL_MASK)
      fprintf(stderr,"channel is a mask (one component per pixel)\n");
    
    fprintf(stderr,"\n");
  }
  
  return c;
}

/**
   @returns: the number of parameters in this template */
int check_parameter_templates(livido_parameter_template_t *parameter_templates) {
  int c;
  livido_parameter_template_t *par_tmpl;

  if(!parameter_templates) return 0;
  
  // cycle thru parameter templates
  for(par_tmpl = parameter_templates, c=1 ;
      par_tmpl != NULL ;
      par_tmpl = par_tmpl->next, c++) {
    
    fprintf(stderr,"parameter %i is %s\n", c, par_tmpl->name);
    fprintf(stderr,"description:\n%s\n", par_tmpl->description);
    fprintf(stderr,"parameter type: %s format: \"%s\"\n",
	    (par_tmpl->type == LIVIDO_PARAM_SWITCH)     ? "switch"    : //       "%i"
	    (par_tmpl->type == LIVIDO_PARAM_NUMBER)     ? "number"    : //       "%f"
	    (par_tmpl->type == LIVIDO_PARAM_RGBA32)     ? "rgba"      : //       "%f %f %f %f"
	    (par_tmpl->type == LIVIDO_PARAM_COORD2D)    ? "2d coords" : //       "%f %f"
	    (par_tmpl->type == LIVIDO_PARAM_STRING)     ? "string"    : //       "%s"
	    (par_tmpl->type == LIVIDO_PARAM_POINTER)    ? "data pointer" : //    "%p"
	    (par_tmpl->type == LIVIDO_PARAM_CUSTOM)     ? "custom": // parse the scan parameter..
	    "unknown", par_tmpl->format);

    fprintf(stderr,"parameter");
    if(par_tmpl->min)
      fprintf(stderr," min:[%f]",par_tmpl->min);
    if(par_tmpl->max)
      fprintf(stderr," max:[%f]",par_tmpl->max);
    if(par_tmpl->def)
      fprintf(stderr," default:[%s]",par_tmpl->def);
    fprintf(stderr,"\n");

    if(par_tmpl->flags & LIVIDO_PARAMETER_NEEDS_INIT)
      fprintf(stderr,"parameter needs reinitialization when its value is changed\n");

    if(par_tmpl->flags & LIVIDO_PARAMETER_INTERPOLATED)
      fprintf(stderr,"plugin should interpolate parameter value on change\n");
    
    if(par_tmpl->hint != NULL)
      fprintf(stderr,"plugin GUI hint is %s\n",par_tmpl->hint);

  }

  return c;
}
