/*******************************************************************************
 * intf_ctrl.c: interface commands access to control functions
 * (c)1999 VideoLAN
 *******************************************************************************
 * Library of functions common to all interfaces, allowing access to various
 * structures and settings. Interfaces should only use those functions
 * to read or write informations from other threads.
 * A control function must be declared in the `local prototypes' section (it's
 * type is fixed), and copied into the control_command array. Functions should
 * be listed in alphabetical order, so when `help' is called they are also
 * displayed in this order.
 * A control function can use any function of the program, but should respect
 * two points: first, it should not block, since if it does so, the whole
 * interface thread will hang and in particular miscelannous interface events
 * won't be handled. Secondly, it should send it's output messages exclusively
 * with intf_IntfMsg() function, except particularly critical messages which
 * can use over intf_*Msg() functions.
 * Control functions should return 0 (INTF_NO_ERROR) on success, or one of the
 * error codes defined in command.h. Custom error codes are allowed, but should
 * be positive.
 * More informations about parameters stand in `list of commands' section.
 *******************************************************************************/

/*******************************************************************************
 * Preamble
 *******************************************************************************/
#include <errno.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/soundcard.h>
#include <sys/stat.h>
#include <sys/uio.h>
#include <X11/Xlib.h>
#include <X11/extensions/XShm.h>

#include "config.h"
#include "common.h"
#include "mtime.h"
#include "vlc_thread.h"
#include "debug.h"

#include "intf_msg.h"

#include "input.h"
#include "input_ctrl.h"
#include "input_vlan.h"
#include "input_psi.h"
#include "input_netlist.h"
#include "decoder_fifo.h"

#include "audio_output.h"
#include "audio_decoder.h"

#include "video.h"
#include "video_output.h"
#include "video_graphics.h"
#include "video_decoder.h"

#include "xconsole.h"
#include "interface.h"
#include "intf_cmd.h"
#include "control.h"
#include "intf_ctrl.h"

#include "pgm_data.h"

/*
 * Local prototypes
 */
static int Demo                 ( int i_argc, intf_arg_t *p_argv );
static int DisplayImage         ( int i_argc, intf_arg_t *p_argv );
static int Exec                 ( int i_argc, intf_arg_t *p_argv );
static int Help                 ( int i_argc, intf_arg_t *p_argv );
static int PlayAudio            ( int i_argc, intf_arg_t *p_argv );
static int PlayVideo            ( int i_argc, intf_arg_t *p_argv );
static int Quit                 ( int i_argc, intf_arg_t *p_argv );
static int SelectPID            ( int i_argc, intf_arg_t *p_argv );
static int SpawnInput           ( int i_argc, intf_arg_t *p_argv );
#ifdef DEBUG
static int Test                 ( int i_argc, intf_arg_t *p_argv );
#endif
static int Vlan                 ( int i_argc, intf_arg_t *p_argv );
static int Psi                  ( int i_argc, intf_arg_t *p_argv );

/*
 * List of commands.
 * This list is used by intf_ExecCommand function to find functions to
 * execute and prepare its arguments. It is terminated by an element  which name 
 * is a null pointer. intf_command_t is defined in command.h.
 *
 * Here is a description of a command description elements:
 *  name is the name of the function, as it should be typed on command line,
 *  function is a pointer to the control function,
 *  format is an argument descriptor (see below),
 *  summary is a text string displayed in regard of the command name when `help'
 *      is called without parameters, and whith usage on syntax error,
 *  usage is a short syntax indicator displayed with summary when the command 
 *      causes a syntax error,
 *  help is a complete help about the command, displayed when `help' is called with
 *      the command name as parameter.
 *
 * Format string is a list of ' ' separated strings, which have following 
 * meanings:
 *  s       string argument
 *  i       integer argument
 *  f       float argument
 *  ?       optionnal argument
 *  *       argument can be repeated
 *  name=   named argument
 * Example: "channel=i? s*? i " means that any number of string arguments, 
 * followed by a single mandatory integer argument are waited. A named argument,
 * which name is `channel' and must have an integer value can be optionnaly
 * specified at beginning. The last space is mandatory if there is at least one
 * element, since it acts as an item terminator.
 * Named arguments MUST be at the beginning of the format string, and in
 * alphabetic order, but their order on command line has no importance. 
 * The format string can't have more than INTF_MAX_ARGS elements.
 */
const intf_command_t control_command[] =
{
  { "demo", Demo,                                                      /* demo */
    /* format: */   "", 
    /* summary: */  "program demonstration", 
    /* usage: */    "demo", 
    /* help: */     "Start program capabilities demonstration." },
  { "display", DisplayImage,                                        /* display */
    /* format: */   "s ",
    /* summary: */  "load and display an image",
    /* usage: */    "display <file>",
    /* help: */     "Load and display an image. Image format is automatically " \
    "identified from file name extension." },
  { "exec", Exec,                                                      /* exec */
    /* format: */   "s ",
    /* summary: */  "execute a script file",
    /* usage: */    "exec <file>",
    /* help: */     "Load an execute a script." },      
  { "exit", Quit,                                         /* exit (quit alias) */
    /* format: */   "",
    /* summary: */  "quit program",
    /* usage: */    "exit",
    /* help: */     "see `quit'." },
  { "help", Help,                                                      /* help */
    /* format: */   "s? ", 
    /* summary: */  "list all functions or print help about a specific function", 
    /* usage: */    "help [command]", 
    /* help: */     "If called without argument, list all available " \
    " functions.\nIf a command name is provided as argument, displays a short " \
    "inline help about the command.\n" },
  { "play-audio", PlayAudio,                                     /* play-audio */
    /* format: */   "stereo=i? rate=i? s ", 
    /* summary: */  "play an audio file",
    /* usage: */    "play-audio [stereo=1/0] [rate=r] <file>",
    /* help: */     "Load and play an audio file." },
  { "play-video", PlayVideo,                                      /* play-video */
    /* format: */   "s ", 
    /* summary: */  "play a video (.vlp) file",
    /* usage: */    "play-video <file>",
    /* help: */     "Load and play a video file." },
  { "quit", Quit,                                                      /* quit */
    /* format: */   "",
    /* summary: */  "quit program",
    /* usage: */    "quit",
    /* help: */     "Terminates the program execution... There is not much to" \
    " say about it !" },
  { "select-pid", SelectPID,                                     /* select-pid */
    /* format: */   "i i ",
    /* summary: */  "spawn a decoder thread for a specified PID",
    /* summary: */  "select-pid <input> <pid>",
    /* help: */     "Spawn a decoder thread for <pid>. The stream will be" \
    " received by <input>." },
  { "spawn-input", SpawnInput,                                  /* spawn-input */
    /* format: */   "method=i? filename=s? hostname=s? ip=s? port=i? vlan=i?",
    /* summary: */  "spawn an input thread",
    /* summary: */  "spawn-input [method=<method>]\n" \
    "[filename=<file>|hostname=<hostname>|ip=<ip>]\n" \
    "[port=<port>] [vlan=<vlan>]",
    /* help: */     "Spawn an input thread. Method is 10, 20, 21, 22, 32, "\
    "hostname is the fully-qualified domain name, ip is a dotted-decimal address." },
#ifdef DEBUG
  { "test", Test,                                                      /* test */
    /* format: */   "i? ",
    /* summary: */  "crazy developper's test",
    /* usage: */    "depends on the last coder :-)",
    /* help: */     "`test' works only in DEBUG mode, and is provide for " \
    "developpers as an easy way to test part of their code. If you don't know " \
    "what it should do, just try !" },
#endif
  { "vlan", Vlan,
    /* format: */   "intf=s? s i? ",
    /* summary: */  "vlan operations",
    /* usage: */    "vlan synchro\n" \
    "vlan [intf=<interface>] request\n" \
    "vlan [intf=<interface>] join <vlan>\n" \
    "vlan [intf=<interface>] leave",
    /* help: */     "Perform various operations on vlans. 'synchro' resynchronize " \
    "with the server. 'request' ask which is the current vlan (for the default " \
    "interface or for a given one). 'join' and 'leave' try to change vlan." },
  { "psi", Psi,
    /* format: */   "i ",
    /* summary: */  "Dump PSI tables",
    /* usage: */    "psi <input thread index>",
    /* help: */     "Display the PSI tables on the console. Warning: this is debug" \
    "command, it can leads to pb since locks are not taken yet" },
  { 0, 0, 0, 0, 0 }                                        /* array terminator */
};

/* following functions are local */

/*******************************************************************************
 * Demo: demo
 *******************************************************************************
 * This function is provided to display a demo of program possibilities. 
 *******************************************************************************/
static int Demo( int i_argc, intf_arg_t *p_argv )
{
    intf_IntfMsg( COPYRIGHT_MESSAGE );

    return( INTF_NO_ERROR );
}

/*******************************************************************************
 * Exec: execute a script
 *******************************************************************************
 * This function load and execute a script.
 *******************************************************************************/
static int Exec( int i_argc, intf_arg_t *p_argv )
{
    int i_err;                                                   /* error code */
    
    i_err = intf_ExecScript( p_argv[1].psz_str );
    return( i_err ? INTF_OTHER_ERROR : INTF_NO_ERROR );     
}

/*******************************************************************************
 * DisplayImage: load and display an image                               (ok ?)
 *******************************************************************************
 * Try to load an image identified by it's filename and displays it as a still
 * image using interface video heap.
 *******************************************************************************/
static int DisplayImage( int i_argc, intf_arg_t *p_argv )
{
    /* ?? */
    return( INTF_NO_ERROR );
}

/*******************************************************************************
 * Help: list all available commands                                     (ok ?)
 *******************************************************************************
 * This function print a list of available commands
 *******************************************************************************/
static int Help( int i_argc, intf_arg_t *p_argv )
{
    int     i_index;                                          /* command index */

    /* If called with an argument: look for the command and display it's help */
    if( i_argc == 2 )
    {
fprintf( stderr, "maxx debug: coin\n" );
        for( i_index = 0; control_command[i_index].psz_name 
                 && strcmp( control_command[i_index].psz_name, p_argv[1].psz_str );
             i_index++ )
        {
            ;
        }
fprintf( stderr, "maxx debug: meuh\n" );
        /* Command has been found in list */
        if( control_command[i_index].psz_name )
        {
fprintf( stderr, "maxx debug: meow\n" );
            intf_IntfMsg( control_command[i_index].psz_usage );
fprintf( stderr, "maxx debug: blah\n" );
            intf_IntfMsg( control_command[i_index].psz_help );
fprintf( stderr, "maxx debug: blih\n" );
        }
        /* Command is unknown */
        else
        {
            intf_IntfMsg("help: don't know command `%s'", p_argv[1].psz_str);
            return( INTF_OTHER_ERROR );
        }
    }
    /* If called without argument: print all commands help field */
    else
    {
        for( i_index = 0; control_command[i_index].psz_name; i_index++ )
        {
            intf_IntfMsg( "%s: %s",  control_command[i_index].psz_name, 
                          control_command[i_index].psz_summary );
        }
    }

    return( INTF_NO_ERROR );
}

/******************************************************************************
 * PlayAudio: play an audio file                                         (ok ?)
 ******************************************************************************
 * Play a raw audio file from a file, at a given rate.
 ******************************************************************************/
static int PlayAudio( int i_argc, intf_arg_t *p_argv )
{
    char *psz_file;                       /* name of the audio raw file (s16) */
    int i_fd;       /* file descriptor of the audio file that is to be loaded */
    aout_fifo_t fifo;          /* fifo stores the informations about the file */
    struct stat stat_buffer;       /* needed to find out the size of psz_file */
    int i_arg;                                              /* argument index */

    if ( !p_program_data->cfg.b_audio )                  /* audio is disabled */
    {
        intf_IntfMsg("play-audio error: audio is disabled");
        return( INTF_NO_ERROR );
    }

    /* Set default configuration */
    fifo.b_stereo = AOUT_DEFAULT_STEREO;
    fifo.l_rate = AOUT_DEFAULT_RATE;

    /* The stereo and rate parameters are essential ! */
    /* Parse parameters - see command list above */
    for ( i_arg = 1; i_arg < i_argc; i_arg++ )
    {
        switch( p_argv[i_arg].i_index )
        {
        case 0:                                                     /* stereo */
            fifo.b_stereo = p_argv[i_arg].i_num;
            break;
        case 1:                                                       /* rate */
            fifo.l_rate = p_argv[i_arg].i_num;
            break;
        case 2:                                                   /* filename */
            psz_file = p_argv[i_arg].psz_str;
            break;
        }
    }

    /* Setting up the type of the fifo */
    switch ( fifo.b_stereo )
    {
        case 0:
            fifo.i_type = AOUT_INTF_MONO_FIFO;
            break;

        case 1:
            fifo.i_type = AOUT_INTF_STEREO_FIFO;
            break;

        default:
            intf_IntfMsg("play-audio error: stereo must be 0 or 1");
            return( INTF_OTHER_ERROR );
    }

    /* Open file */
    i_fd =  open( psz_file, O_RDONLY );
    if ( i_fd < 0 )                                                  /* error */
    {
        intf_IntfMsg("play-audio error: can't open `%s'", psz_file);
        return( INTF_OTHER_ERROR );
    }

    /* Get file size to calculate number of audio units */
    fstat( i_fd, &stat_buffer );
    fifo.l_units = ( long )( stat_buffer.st_size / (sizeof(s16) << fifo.b_stereo) );

    /* Allocate memory, read file and close it */
    if ( (fifo.buffer = malloc(sizeof(s16)*(fifo.l_units << fifo.b_stereo))) == NULL ) /* !! */
    {
        intf_IntfMsg("play-audio error: not enough memory to read `%s'", psz_file );
        close( i_fd );                                          /* close file */
        return( INTF_OTHER_ERROR );
    }
    if ( read(i_fd, fifo.buffer, sizeof(s16)*(fifo.l_units << fifo.b_stereo))
        != sizeof(s16)*(fifo.l_units << fifo.b_stereo) )
    {
        intf_IntfMsg("play-audio error: can't read %s", psz_file);
        free( fifo.buffer );
        close( i_fd );
        return( INTF_OTHER_ERROR );
    }
    close( i_fd );

    /* Now we can work out how many output units we can compute with the fifo */
    fifo.l_units = (long)(((s64)fifo.l_units*(s64)p_program_data->aout_thread.dsp.l_rate)/(s64)fifo.l_rate);

    /* Create the fifo */
    if ( aout_CreateFifo(&p_program_data->aout_thread, &fifo) == NULL )
    {
        intf_IntfMsg("play-audio error: can't create audio fifo");
        free( fifo.buffer );
        return( INTF_OTHER_ERROR );
    }

    return( INTF_NO_ERROR );
}

/*******************************************************************************
 * PlayVideo: play a video sequence from a file
 *******************************************************************************
 * ??
 *******************************************************************************/
static int PlayVideo( int i_argc, intf_arg_t *p_argv )
{
    /* ?? */
    return( INTF_NO_ERROR );    
}

/*******************************************************************************
 * Quit: quit program                                                    (ok ?)
 *******************************************************************************
 * This function set `die' flag of interface, asking the program to terminate.
 *******************************************************************************/
static int Quit( int i_argc, intf_arg_t *p_argv )
{
    p_program_data->intf_thread.b_die = 1;
    return( INTF_NO_ERROR );
}


/******************************************************************************
 *
 ******************************************************************************
 *
 ******************************************************************************/
static int SelectPID( int i_argc, intf_arg_t *p_argv )
{
    int i_input, i_pid;
    int i_arg;
  
    /* Parse parameters - see command list above */
    for ( i_arg = 1; i_arg < i_argc; i_arg++ )
    {
      switch( p_argv[i_arg].i_index )
      {
      case 0:
          i_input = p_argv[i_arg].i_num;
          break;
      case 1:
         i_pid = p_argv[i_arg].i_num;
        break;
      }
    }


    /* Find to which input this command is destinated */
    if(i_input < INPUT_MAX_THREADS )
    {
        if( p_program_data->intf_thread.pp_input[i_input] )
        {
            intf_IntfMsg( "Adding PID %d to input %d\n", i_pid, i_input );
            input_AddPgrmElem( p_program_data->intf_thread.pp_input[i_input],
                               i_pid );
            return( INTF_NO_ERROR );
        }
    }      

    /* No such input was created */
    intf_IntfMsg("No such input thread is currently running: %d\n", i_input);
    return(  INTF_OTHER_ERROR );
}


/******************************************************************************
 * SpawnInput: spawn an input thread                                     (ok ?)
 ******************************************************************************
 * Spawn an input thread with the correct p_cfg parameters.
 ******************************************************************************/
static int SpawnInput( int i_argc, intf_arg_t *p_argv )
{
    input_cfg_t         cfg;
    int                 i_arg;
    
    /* Erase p_cfg. */
    bzero( &cfg, sizeof( cfg ) );

    /* Parse parameters - see command list above */
    for ( i_arg = 1; i_arg < i_argc; i_arg++ )
    {
        switch( p_argv[i_arg].i_index )
        {
        case 0:                                                     /* method */
            cfg.i_method = p_argv[i_arg].i_num;
            break;
        case 1:                                                   /* filename */
            cfg.psz_filename = p_argv[i_arg].psz_str;
            break;
        case 2:                                                   /* hostname */
            cfg.psz_hostname = p_argv[i_arg].psz_str;
            break;
        case 3:                                                         /* ip */
            cfg.psz_ip = p_argv[i_arg].psz_str;
            break;
        case 4:                                                       /* port */
            cfg.i_port = p_argv[i_arg].i_num;
            break;
        case 5:                                                       /* VLAN */
            cfg.i_vlan = p_argv[i_arg].i_num;
            break;
        }
    }

    /* Setting i_properties to indicate which parameters are set. */
    if( cfg.i_method )
    {
        cfg.i_properties |= INPUT_CFG_METHOD;
    }
    if( cfg.psz_filename )
    {
        cfg.i_properties |= INPUT_CFG_FILENAME;
    }
    if( cfg.psz_hostname )
    {
        cfg.i_properties |= INPUT_CFG_HOSTNAME;
    }
    if( cfg.psz_ip )
    {
        cfg.i_properties |= INPUT_CFG_IP;
    }
    if( cfg.i_port )
    {
        cfg.i_properties |= INPUT_CFG_PORT;
    }
    if( cfg.i_vlan )
    {
        cfg.i_properties |= INPUT_CFG_VLAN;
    }

    /* Default settings for the decoder threads */
    cfg.p_aout = p_program_data->intf_thread.p_aout;

    /* Create the input thread */
    if( intf_CreateInputThread( &p_program_data->intf_thread, &cfg ) == -1)
    {
        return( INTF_OTHER_ERROR );
    }

    return( INTF_NO_ERROR );
}

/*******************************************************************************
 * Test: test function
 *******************************************************************************
 * This function is provided to test new functions in the program. Fell free
 * to modify ! 
 * This function is only defined in DEBUG mode.
 *******************************************************************************/
#ifdef DEBUG
static int Test( int i_argc, intf_arg_t *p_argv )
{
    int i_thread;    

    if( i_argc == 1 )
    {
        i_thread = intf_CreateVoutThread( &p_program_data->intf_thread, NULL, -1, -1);
        intf_IntfMsg("return value: %d", i_thread );        
    }
    else
    {
        i_thread = p_argv[1].i_num;        
        intf_DestroyVoutThread( &p_program_data->intf_thread, i_thread );        
    }    

    return( INTF_NO_ERROR );
}
#endif

/*******************************************************************************
 * Vlan: vlan operations
 *******************************************************************************
 * This function performs various vlan operations.
 *******************************************************************************/
static int Vlan( int i_argc, intf_arg_t *p_argv  )
{
    int i_command;                                  /* command argument number */

    /* Do not try anything if vlans are desactivated */
    if( !p_program_data->cfg.b_vlans )
    {
        intf_IntfMsg("vlans are desactivated");
        return( INTF_OTHER_ERROR );
    }
    
    /* Look for command in list of arguments - this argument is mandatory and
     * imposed by the calling function */
    for( i_command = 1; p_argv[i_command].i_index == 1; i_command++ )
    {
        ;        
    }
    
    /* Command is 'synchro' */
    if( !strcmp(p_argv[i_command].psz_str, "synchro") )
    {
        input_VlanSynchronize();
    }
    /* Command is 'request' */
    else if( !strcmp(p_argv[i_command].psz_str, "request") )
    {
        /* ?? */
    }
    /* Command is 'join' */
    else if( !strcmp(p_argv[i_command].psz_str, "join") )
    {
        /* ?? */
    }    
    /* Command is 'leave' */
    else if( !strcmp(p_argv[i_command].psz_str, "leave") )
    {
        /* ?? */
    }
    /* Command is unknown */
    else
    {
        intf_IntfMsg("vlan error: unknown command %s", p_argv[i_command].psz_str );
        return( INTF_USAGE_ERROR );        
    }
         
    return( INTF_NO_ERROR );
}


/*******************************************************************************
 * Psi
 *******************************************************************************
 * This function is provided to display PSI tables. 
 *******************************************************************************/
static int Psi( int i_argc, intf_arg_t *p_argv )
{
    int i_index = p_argv[1].i_num;
  
    if(i_index < INPUT_MAX_THREADS )
    {
      if(p_program_data->intf_thread.pp_input[i_index])
      {
        /* Read the Psi table for that thread */
        intf_IntfMsg("Reading PSI table for input %d\n", i_index);
        input_PsiRead(p_program_data->intf_thread.pp_input[i_index]);
        return( INTF_NO_ERROR );
      }
    }      

    /* No such input was created */
    intf_IntfMsg("No such input thread is currently running: %d\n", i_index);
    
    return(  INTF_OTHER_ERROR );
}
