#include "../config.h"

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

#include "afmt.h"
#include "audio_out.h"
#include "audio_out_internal.h"

#include "audio_plugin.h"

static ao_info_t info = 
{
	"Plugin audio output",
	"plugin",
	"Anders",
	""
};

LIBAO_EXTERN(plugin)

#define plugin(i) (ao_plugin_local_data.plugins[i])
#define driver() (ao_plugin_local_data.driver)

// local data 
typedef struct ao_plugin_local_data_s
{
  void* buf;					 // Output data buffer
  int len;					 // Amount of data in buffer
  int channels;
  int format;
  int bpm;   //bit of format
  float bps;					 // Bytes per second out
  ao_functions_t* driver;      			 // Output driver
  ao_plugin_functions_t** plugins;               // List of used plugins
  ao_plugin_functions_t* available_plugins[NPL]; // List of available plugins
} ao_plugin_local_data_t;

static ao_plugin_local_data_t ao_plugin_local_data={NULL,0,0,0,0,0.0,NULL,NULL,AO_PLUGINS};

// global data 
volatile ao_plugin_data_t ao_plugin_data;             // Data used by the plugins
ao_plugin_cfg_t  ao_plugin_cfg=CFG_DEFAULTS; // Set in cfg-mplayer.h

// to set/get/query special features/parameters
static int control(int cmd,void *arg){
  switch(cmd){
  case AOCONTROL_SET_PLUGIN_DRIVER:
    ao_plugin_local_data.driver=arg;
    return CONTROL_OK;
  case AOCONTROL_GET_VOLUME:
  case AOCONTROL_SET_VOLUME:
  {
    int r=audio_plugin_volume.control(cmd,arg);
    if(CONTROL_OK != r)
      return driver()->control(cmd,arg);
    else
      return r;
  }
  default:
    return driver()->control(cmd,arg);
  }
  return CONTROL_UNKNOWN;
}

// Recursive function for adding plugins
// return 1 for success and 0 for error
int add_plugin(int i,char* cfg){
  int cnt=0;
  // Find end of plugin name
  while((cfg[cnt]!=',')&&(cfg[cnt]!='\0')&&(cnt<100)) cnt++;
  if(cnt >= 100)
    return 0;

  // Is this the last iteration or just another plugin
  if(cfg[cnt]=='\0'){
    ao_plugin_local_data.plugins=malloc((i+2)*sizeof(ao_plugin_functions_t*));
    if(ao_plugin_local_data.plugins){
      ao_plugin_local_data.plugins[i+1]=NULL;
      // Find the plugin matching the cfg string name
      cnt=0;
      while(ao_plugin_local_data.available_plugins[cnt] && cnt<20){
	if(0==strcmp(ao_plugin_local_data.available_plugins[cnt]->info->short_name,cfg)){
	  ao_plugin_local_data.plugins[i]=ao_plugin_local_data.available_plugins[cnt];
	  return 1;
	}
	cnt++;
      }
      printf("[plugin]: Invalid plugin: %s \n",cfg);
      return 0;
    }
    else 
      return 0;
  } else {
    cfg[cnt]='\0';
    if(add_plugin(i+1,&cfg[cnt+1])){
      cnt=0;
      // Find the plugin matching the cfg string name
      while(ao_plugin_local_data.available_plugins[cnt] && cnt < 20){
	if(0==strcmp(ao_plugin_local_data.available_plugins[cnt]->info->short_name,cfg)){
	  ao_plugin_local_data.plugins[i]=ao_plugin_local_data.available_plugins[cnt];
	  return 1;
	}
	cnt++;
      }
      printf("[plugin]: Invalid plugin: %s \n",cfg);
      return 0;
    }
    else 
      return 0;
  }	
  return 0; // Will never happen...
}

// open & setup audio device and plugins
// return: 1=success 0=fail
static int init(int rate,int channels,int format,int flags){
  int use_plugin[NPL];
  int x,npl,unused=0;
  int ok=1;
  char *config;

  // Create list of plugins from cfg option
  int i=0; 
  if(ao_plugin_cfg.plugin_list){
    config = malloc(strlen(ao_plugin_cfg.plugin_list)+1);
    if(!config) return 0;
    if(!strcpy(config, ao_plugin_cfg.plugin_list) || !add_plugin(i,config)){
      free(config);
      return 0;
    }
    free(config);
  }

  /* Set input parameters and itterate through plugins each plugin
     changes the parameters according to its output */

  ao_plugin_local_data.format=format;
  ao_plugin_local_data.channels=channels;
  ao_plugin_local_data.bpm=audio_out_format_bits(format);

  ao_plugin_data.rate=rate;
  ao_plugin_data.channels=channels;
  ao_plugin_data.format=format;
  ao_plugin_data.sz_mult=1;
  ao_plugin_data.sz_fix=0;
  ao_plugin_data.delay_mult=1;
  ao_plugin_data.delay_fix=0;

  for(i=0;i<NPL && plugin(i);i++){
    use_plugin[i]=plugin(i)->init();
    if(!use_plugin[i]) plugin(i)->uninit();
  }
  npl=i;
  for(i=0;i<npl && plugin(i);i++)
    if(!use_plugin[i+unused]){
      unused++;
      for(x=i;x<npl && plugin(x+1);x++) plugin(x)=plugin(x+1);
      plugin(x)=NULL;
      npl--;
      i--;
    }
  i=npl;

  // Calculate bps
  ao_plugin_local_data.bps=(float)(ao_plugin_data.rate * 
				   ao_plugin_data.channels);
  ao_plugin_local_data.bps*=audio_out_format_bits(ao_plugin_data.format)/8;

  // This should never happen but check anyway 
  if(NULL==ao_plugin_local_data.driver)
    return 0;
  
  ok = driver()->init(ao_plugin_data.rate,
		      ao_plugin_data.channels,
		      ao_plugin_data.format,
		      flags);
  if(!ok) return 0;

  /* Now that the driver is initialized we can calculate and set the
     input and output buffers for each plugin */
  ao_plugin_data.len=driver()->get_space();
  while((i>0) && ok)
    ok=plugin(--i)->control(AOCONTROL_PLUGIN_SET_LEN,0);

  if(!ok) return 0;

  // Allocate output buffer
  if(ao_plugin_local_data.buf)
    free(ao_plugin_local_data.buf);
  ao_plugin_local_data.buf=malloc(MAX_OUTBURST);

  if(!ao_plugin_local_data.buf) return 0;

  return 1;
}

// close audio device
static void uninit(int immed){
  int i=0;
  driver()->uninit(immed);
  while(plugin(i))
    plugin(i++)->uninit();
  if(ao_plugin_local_data.plugins)
    free(ao_plugin_local_data.plugins);
  ao_plugin_local_data.plugins=NULL;
  if(ao_plugin_local_data.buf)
    free(ao_plugin_local_data.buf);
  ao_plugin_local_data.buf=NULL;
}

// stop playing and empty buffers (for seeking/pause)
static void reset(){
  int i=0;
  driver()->reset();
  while(plugin(i))
    plugin(i++)->reset();
  ao_plugin_local_data.len=0;
}

// stop playing, keep buffers (for pause)
static void audio_pause(){
  driver()->pause();
}

// resume playing, after audio_pause()
static void audio_resume(){
  driver()->resume();
}

// return: how many bytes can be played without blocking
static int get_space(){
double sz;
int isz;
  sz=(double)(driver()->get_space());
  if(sz+(double)ao_plugin_local_data.len > (double)MAX_OUTBURST)
    sz=(double)MAX_OUTBURST-(double)ao_plugin_local_data.len;
  sz*=ao_plugin_data.sz_mult;
  sz+=ao_plugin_data.sz_fix;
  isz=(int)(sz);
  isz-=isz%(ao_plugin_local_data.channels*ao_plugin_local_data.bpm/8);
  return isz;
}

// plays 'len' bytes of 'data'
// return: number of bytes played
static int play(void* data,int len,int flags){
  int l,i=0;
  // Limit length to avoid over flow in plugins
  int tmp = get_space();
  int ret_len =(tmp<len)?tmp:len;
  // keep all channels of each sample together
  ret_len -= ret_len % (ao_plugin_local_data.channels*ao_plugin_local_data.bpm/8);
  if(ret_len){
    // Filter data
    ao_plugin_data.len=ret_len;
    ao_plugin_data.data=data;
    
    while(plugin(i))
      plugin(i++)->play();
    // Copy data to output buffer
    memcpy(ao_plugin_local_data.buf+ao_plugin_local_data.len,
	   ao_plugin_data.data,ao_plugin_data.len);
    // Send data to output
    l=driver()->play(ao_plugin_local_data.buf,
		     ao_plugin_data.len+ao_plugin_local_data.len,flags);
    // Save away unsent data
    ao_plugin_local_data.len=ao_plugin_data.len+ao_plugin_local_data.len-l;
    memcpy(ao_plugin_local_data.buf,ao_plugin_local_data.buf+l,
	   ao_plugin_local_data.len);
  }
  return ret_len;
}

// return: delay in seconds between first and last sample in buffer
static float get_delay(){
  float delay=driver()->get_delay();
  delay+=(float)ao_plugin_local_data.len/ao_plugin_local_data.bps;
  delay*=ao_plugin_data.delay_mult;
  delay+=ao_plugin_data.delay_fix;
  return delay;
}




