#include <stdio.h>
#include <sys/types.h>
#include <malloc.h>
#include <string.h>
#include <sys/stat.h>

#include "../include/cfgfmt.h"
#include "../include/string.h"
#include "../include/strexp.h"
#include "../include/fio.h"

#include "ymode.h"
#include "soundpaths.h"
#include "ysound.h"
#include "ymixer.h"
#include "rcfile.h"
#include "options.h"


int RCLoadFromFile(const char *filename);


/*
 *      Structure to contain a mixer device settings read from
 *      file.
 */
typedef struct {
	char *name;
	Coefficient val[YTotalMixers][YMixerValues];
} mixer_device_fdata_struct;

static void RCMixerParseMixerValueString(
	const char *s,
	Coefficient *value1, Coefficient *value2,
	Coefficient *value3, Coefficient *value4
);
static void MixerFreeFData(
	mixer_device_fdata_struct **ptr, int total
);
static mixer_device_fdata_struct **MixerRCLoadAllFromFile(
	const char *filename, int *total
);
int MixerRCLoadFromFile(const char *filename, Recorder *recorder);
int MixerRCSaveToFile(const char *filename, Recorder *recorder);


extern int YAntiShift(int in);	/* In main.c */


#define ATOI(s)		(((s) != NULL) ? atoi(s) : 0)
#define ATOL(s)		(((s) != NULL) ? atol(s) : 0)
#define ATOF(s)		(((s) != NULL) ? atof(s) : 0.0f)
#define STRDUP(s)	(((s) != NULL) ? strdup(s) : NULL)

#define MAX(a,b)	(((a) > (b)) ? (a) : (b))
#define MIN(a,b)	(((a) < (b)) ? (a) : (b))
#define CLIP(a,l,h)	(MIN(MAX((a),(l)),(h)))
#define STRLEN(s)	(((s) != NULL) ? strlen(s) : 0)
#define STRISEMPTY(s)	(((s) != NULL) ? (*(s) == '\0') : 1)


/*
 *	Loads configuration.
 *
 *	Any current Audio modes and sound paths will be deleted
 *	and then loaded with the new ones from the file.
 */
int RCLoadFromFile(const char *filename)
{
	int status;
	char *strptr, *strptr2;

	FILE *fp;
	struct stat stat_buf;

	char parm[CFG_PARAMETER_MAX];
	char val[CFG_VALUE_MAX];
	int lines_read = 0;

	YMode *ymode_ptr;
	SoundPath *sp_ptr;


	/* Check if file exists */
	if(filename == NULL)
	    return(-1);
	if(stat(filename, &stat_buf))
	{
	    fprintf(stderr, "%s: No such file.\n", filename);
	    return(-1);
	}

	/* Open YIFF Sound Server configuration file */
	fp = FOpen(filename, "rb");
	if(fp == NULL)
	{
	    fprintf(stderr, "%s: Cannot open.\n", filename);
	    return(-1);
	}


	/* Free allocated resources that would be reloaded with the
	 * contents of the configuration file to be loaded.
	 */

	/* Y Audio Modes */	
	YModeDeleteAll();

	/* Y Sound Paths */
	SoundPathDeleteAll();


/* Reads the next line, fetches parm and val. Will break the loop
 * if end of file is reached or continue as needed. Make sure variable
 * is initially reset to NULL before start of reading file. Uses
 * variables; strptr, strptr2, fp, parm, val, and lines_read.
 */
#define DO_READ_LINE    \
{ \
 /* Free previous line and allocate/read next line */ \
 free(strptr); \
 strptr = FReadNextLineAlloc(fp, UNIXCFG_COMMENT_CHAR); \
 if(strptr == NULL) \
  break; \
 lines_read++; \
 \
 /* Fetch parameter */ \
 strptr2 = StringCfgParseParm(strptr); \
 if(strptr2 == NULL) \
  continue; \
 strncpy(parm, strptr2, CFG_PARAMETER_MAX); \
 parm[CFG_PARAMETER_MAX - 1] = '\0'; \
 \
 /* Fetch value */ \
 strptr2 = StringCfgParseValue(strptr); \
 if(strptr2 == NULL) \
  strptr2 = "0";  /* Set it to "0" if NULL */ \
 strncpy(val, strptr2, CFG_VALUE_MAX); \
 val[CFG_VALUE_MAX - 1] = '\0'; \
}

	/* Begin reading file */
	strptr = NULL;
	while(1)
	{
	    DO_READ_LINE

	    /* VersionMajor */
	    if(!strcasecmp(parm, "VersionMajor"))
	    {

	    }
	    /* VersionMinor */
	    else if(!strcasecmp(parm, "VersionMinor"))
	    {

	    }

	    /* Listening port number */
	    else if(!strcasecmp(parm, "Port"))
	    {
		option.port = atoi(val);
	    }
	    /* Refresh interval */
	    else if(!strcasecmp(parm, "RefreshInterval"))
	    {
		option.refresh_int.ms = atol(val) / 1000;
		option.refresh_int.us = atol(val) % 1000;
	    }
	    /* DSP device */
	    else if(!strcasecmp(parm, "DSPDevice") ||
		    !strcasecmp(parm, "Device")
	    )
	    {
		strncpy(
		    fname.device,
		    val,
		    PATH_MAX + NAME_MAX
		);
		if(stat(fname.device, &stat_buf))
		{
		    fprintf(stderr,
   "%s: Line %i: Warning: %s: No such device.\n",
			    filename,
			    lines_read,
			    fname.device
		    );
		}
	    }
	    /* Mixer device */
	    else if(!strcasecmp(parm, "MixerDevice") ||
		    !strcasecmp(parm, "Mixer")
	    )
	    {
		strncpy(
		    fname.mixer,
		    val,
		    PATH_MAX + NAME_MAX
		);
		if(fname.mixer[0] != '\0')
		{
		    if(stat(fname.mixer, &stat_buf))
			fprintf(stderr,
   "%s: Line %i: Warning: %s: No such device.\n",
			    filename,
			    lines_read,
			    fname.mixer
			);
		}
	    }
	    /* MIDI player command */
	    else if(!strcasecmp(parm, "MIDIPlayCommand"))
	    {
		free(option.midi_play_cmd);
		option.midi_play_cmd = strdup(val);
	    }
	    /* ALSA MIDI device port */
	    else if(!strcasecmp(parm, "MIDIDevicePort"))
	    {
#ifdef ALSA_RUN_CONFORM
		/* If -1 then implies it is not set */
		if(option.midi_device_number < 0)
		{
		    /* Was not previously set, so set it with the
		     * specified value from configuration file.
		     */
		    option.midi_device_number = atoi(val);
		}
#endif	/* ALSA_RUN_CONFORM */
	    }

	    /* **************************************************** */
	    /* BeginYAudioMode */
	    else if(!strcasecmp(parm, "BeginYAudioMode") ||
		    !strcasecmp(parm, "BeginAudioMode") ||
		    !strcasecmp(parm, "BeginYMode") ||
		    !strcasecmp(parm, "BeginMode")
	    )
	    {
		status = YModeAllocate(NULL);
		ymode_ptr = YModeGetPtr(status);
		if(ymode_ptr == NULL)
		    continue;

		while(1)
		{
		    DO_READ_LINE

		    /* Name */
		    if(!strcasecmp(parm, "Name"))
		    {
			free(ymode_ptr->name);
			ymode_ptr->name = STRDUP(val);
		    }
		    /* Cycle */
		    else if(!strcasecmp(parm, "Cycle"))
		    {
			ymode_ptr->cycle.ms = atol(val) / 1000;
			ymode_ptr->cycle.us = atol(val) % 1000;
		    }
		    /* WriteAhead */
		    else if(!strcasecmp(parm, "WriteAhead"))
		    {
			ymode_ptr->write_ahead.ms = atol(val) / 1000;
			ymode_ptr->write_ahead.us = atol(val) % 1000;
		    }
		    /* SampleSize */
		    else if(!strcasecmp(parm, "SampleSize"))
		    {
			ymode_ptr->sample_size = atoi(val);

			if(ymode_ptr->sample_size == 16)
			    ymode_ptr->sample_size = 16;
			else
			    ymode_ptr->sample_size = 8;
		    }
		    /* Channels */
		    else if(!strcasecmp(parm, "Channels"))
		    {
			ymode_ptr->channels = atoi(val);

			if(ymode_ptr->channels == 2)
			    ymode_ptr->channels = 2;
			else
			    ymode_ptr->channels = 1;
		    }
		    /* SampleRate */
		    else if(!strcasecmp(parm, "SampleRate"))
		    {
			ymode_ptr->sample_rate = atoi(val);
		    }
#ifdef OSS_BUFFRAG
		    /* AllowFragmenting */
		    else if(!strcasecmp(parm, "AllowFragmenting"))
		    {
			ymode_ptr->allow_fragments = StringIsYes(val);
		    }
		    /* Fragments */
		    else if(!strcasecmp(parm, "Fragments"))
		    {
			ymode_ptr->num_fragments = atoi(val);
		    }
		    /* FragmentSize */
		    else if(!strcasecmp(parm, "FragmentSize"))
		    {
			/*   Note: On file, it's bytes, in memory it's
			 *   shifts.
			 */
			ymode_ptr->fragment_size = YAntiShift(atoi(val) - 1);
		    }
#endif  /* OSS_BUFFRAG */
		    /* FlipStereo */
		    else if(!strcasecmp(parm, "FlipStereo"))
		    {
			ymode_ptr->flip_stereo = StringIsYes(val);
		    }
		    /* Direction */
		    else if(!strcasecmp(parm, "Direction"))
		    {
			if(!strcasecmp(val, "Record"))
			    ymode_ptr->direction = AUDIO_DIRECTION_RECORD;
			else
			    ymode_ptr->direction = AUDIO_DIRECTION_PLAY;
		    }

		    /* EndYAudioMode */
		    else if(!strcasecmp(parm, "EndYAudioMode") ||
			    !strcasecmp(parm, "EndAudioMode") ||
			    !strcasecmp(parm, "EndYMode") ||
			    !strcasecmp(parm, "EndMode")
		    )
		    {
			break;
		    }

		    /* Unknown parameter */
		    else
		    { 
			fprintf(stderr,
			    "%s: Line %i: Unknown parameter: %s\n",
			    filename,
			    lines_read,
			    parm
			);
			continue;
		    }

		}
	    }

	    /* **************************************************** */
	    /* BeginYSoundPath */
	    else if(!strcasecmp(parm, "BeginYSoundPath") ||
		    !strcasecmp(parm, "BeginSoundPath")
	    )
	    {
		status = SoundPathAllocate();
		sp_ptr = SoundPathGetPtr(status);
		if(sp_ptr == NULL)
		    continue;

		while(1)
		{
		    DO_READ_LINE

		    /* Path */   
		    if(!strcasecmp(parm, "Path"))
		    {
			free(sp_ptr->path);
			sp_ptr->path = STRDUP(val);
		    }
		    /* EndYSoundPath */
		    else if(!strcasecmp(parm, "EndYSoundPath") ||
			    !strcasecmp(parm, "EndSoundPath")
		    )
		    {
			break;
		    }
		    /* Unknown parameter */  
		    else
		    {
			fprintf(stderr,
			    "%s: Line %i: Unknown parameter: %s\n",
			    filename,
			    lines_read,
			    parm
			);
			continue;
		    } 
		}
	    }

	    /* Unknown parameter */
	    else
	    {
		fprintf(stderr, "%s: Line %i: Unknown parameter: %s\n",
		    filename,
		    lines_read,
		    parm
		);
	    }
	}

#undef DO_READ_LINE

	/* Close the file */
	FClose(fp);


	return(0);
}

/*
 *	Parses two values seperated by a white space from string
 *	s and puts them into the Coefficient values.
 */
static void RCMixerParseMixerValueString(
	const char *s,
	Coefficient *value1, Coefficient *value2,
	Coefficient *value3, Coefficient *value4
)
{
	/* Seek past spaces and get value 1 */
	while(ISBLANK(*s))
	    s++;
	if(value1 != NULL)
	    *value1 = atof(s);

	/* Seek to and get value 2 */
	while(!ISBLANK(*s) && (*s != '\0'))
	    s++;
	while(ISBLANK(*s))
	    s++;
	if(value2 != NULL)
	    *value2 = atof(s);

	/* Seek to and get value 3 */
	while(!ISBLANK(*s) && (*s != '\0'))
	    s++;
	while(ISBLANK(*s))
	    s++;
	if(value3 != NULL)
	    *value3 = atof(s);

	/* Seek to and get value 4 */
	while(!ISBLANK(*s) && (*s != '\0'))
	    s++;
	while(ISBLANK(*s))
	    s++;
	if(value4 != NULL)
	    *value4 = atof(s);
}

/*
 *	Deallocates mixer device file data list.
 */
static void MixerFreeFData(mixer_device_fdata_struct **ptr, int total)
{
	int i;
	mixer_device_fdata_struct *mdfd_ptr;

	for(i = 0; i < total; i++)
	{
	    mdfd_ptr = ptr[i];
	    if(mdfd_ptr == NULL)
		continue;

	    free(mdfd_ptr->name);
	    free(mdfd_ptr);
	}
	free(ptr);
}

/*
 *	Loads all mixer device channel values from the specified
 *	file and returns an allocated array of listings for each
 *	device.
 *
 *	This array needs to be free'ed by the calling function.
 */
static mixer_device_fdata_struct **MixerRCLoadAllFromFile(
	const char *filename, int *total
)
{
	int i, n;
	char *strptr, *strptr2;

	FILE *fp;
	int lines_read = 0;
	struct stat stat_buf;
	char parm[CFG_PARAMETER_MAX];
	char val[CFG_VALUE_MAX];
   
	char *mixer_name[] = YMixerConicalNames;

	mixer_device_fdata_struct **ptr = NULL;
	mixer_device_fdata_struct *mdfd_ptr;


	if((filename == NULL) ||
	   (total == NULL)
	)
	    return(NULL);

	if(stat(filename, &stat_buf))
	    return(NULL);

	/* Reset total */
	(*total) = 0;

	/* Open mixer device file for reading */
	fp = FOpen(filename, "rb");
	if(fp == NULL)
	    return(NULL);

/* Reads the next line, fetches parm and val. Will break the loop
 * if end of file is reached or continue as needed. Make sure variable
 * is initially reset to NULL before start of reading file. Uses
 * variables; strptr, strptr2, fp, parm, val, and lines_read.
 */
#define DO_READ_LINE	\
{ \
 /* Free previous line and allocate/read next line */ \
 free(strptr); \
 strptr = FReadNextLineAlloc(fp, UNIXCFG_COMMENT_CHAR); \
 if(strptr == NULL) \
  break; \
 lines_read++; \
 \
 /* Fetch parameter */ \
 strptr2 = StringCfgParseParm(strptr); \
 if(strptr2 == NULL) \
  continue; \
 strncpy(parm, strptr2, CFG_PARAMETER_MAX); \
 parm[CFG_PARAMETER_MAX - 1] = '\0'; \
 \
 /* Fetch value */ \
 strptr2 = StringCfgParseValue(strptr); \
 if(strptr2 == NULL) \
  strptr2 = "0";  /* Set it to "0" if NULL */ \
 strncpy(val, strptr2, CFG_VALUE_MAX); \
 val[CFG_VALUE_MAX - 1] = '\0'; \
}

	/* Begin reading file */
	strptr = NULL;
	while(1)
	{
	    DO_READ_LINE

	    /* BeginMixer */
	    if(!strcasecmp(parm, "BeginMixer"))
	    {
		/* Allocate a new mixer device structure */
		i = (*total);
		(*total) = i + 1;
		ptr = (mixer_device_fdata_struct **)realloc(
		    ptr,
		    (*total) * sizeof(mixer_device_fdata_struct *)
		);
		if(ptr == NULL)
		{
		    (*total) = 0;
		    continue;
		}

		ptr[i] = (mixer_device_fdata_struct *)calloc(
		    1,
		    sizeof(mixer_device_fdata_struct)
		);
		if(ptr[i] == NULL)
		{
		    (*total) = i;
		    continue;
		}
		mdfd_ptr = ptr[i];

		/* Record mixer device's name */
		mdfd_ptr->name = STRDUP(val);


		while(1)
		{
		    DO_READ_LINE

		    /* Go through mixer device channel names */  
		    for(n = 0; n < YTotalMixers; n++)
		    {
			if(!strcasecmp(parm, mixer_name[n]))
			{
			    RCMixerParseMixerValueString(
				val,
				(YMixerValues > 0) ?
				    &mdfd_ptr->val[n][0] : NULL,
				(YMixerValues > 1) ?
				    &mdfd_ptr->val[n][1] : NULL,
				(YMixerValues > 2) ?
				    &mdfd_ptr->val[n][2] : NULL,
				(YMixerValues > 3) ?
				    &mdfd_ptr->val[n][3] : NULL
			    );
			}
		    }
		    /* EndMixer */
		    if(!strcasecmp(parm, "EndMixer"))
		    {
			break;
		    }       
		} 
	    }
	    /* Unknown parameter */
	    else
	    {
		fprintf(
		    stderr,
		    "%s: Line %i: Unknown parameter: %s\n",
		    filename,
		    lines_read,
		    parm
		);
	    }
	}

#undef DO_READ_LINE

	/* Close mixer device data file */
	FClose(fp);

	return(ptr);
}


/*
 *	Loads the mixer device channel values from the specified
 *	file and updates the recorder's mixer device channel values.
 *
 *	The recorder's mixer must already be opened for this
 *	function to have any affect.
 *
 *	If global fname.mixer is an empty string then no mixer data
 *	will be loaded.
 */
int MixerRCLoadFromFile(const char *filename, Recorder *recorder)
{
	int i, mixer_index, total;
	int set_our_mixer = 0;
	mixer_device_fdata_struct **ptr, *mdfd_ptr;
	const char *this_mixer_dev_name;


	if((filename == NULL) || (recorder == NULL))
	    return(-1);

	/* Get pointer to our mixer device's name, this will be
	 * used to check through the listing of mixers to see which
	 * one we want to load values for.
	 */
	this_mixer_dev_name = fname.mixer;
	if(this_mixer_dev_name[0] == '\0')
	{
	    /* No mixer device to load */
	    return(0);
	}

	/* Load all mixer devices data from file */
	ptr = MixerRCLoadAllFromFile(filename, &total);
	if(ptr == NULL)
	    total = 0;

	/* No mixer devices loaded? */
	if(total <= 0)
	    return(-1);

	/* Go through mixer device listing */
	for(i = 0; i < total; i++)
	{
	    mdfd_ptr = ptr[i];

	    if(mdfd_ptr == NULL)
		continue;
	    if(mdfd_ptr->name == NULL)
		continue;

	    /* Match mixer device name */
	    if(!strcasecmp(mdfd_ptr->name, this_mixer_dev_name))
	    {
		/* This is the mixer device we're looking for, now go
		 * through each mixer device channel setting.
		 */
		for(mixer_index = 0; mixer_index < YTotalMixers; mixer_index++)
		{
		    /* Set mixer values for our mixer */
		    YMixerSet(
			recorder, mixer_index + YMixerCodeBaseOffset,
			mdfd_ptr->val[mixer_index]
		    );
		}

		set_our_mixer = 1;
	    }
	}

	/* Deallocate the loaded mixer data listing */
	MixerFreeFData(ptr, total);

	if(set_our_mixer)
	    return(0);
	else
	    return(-1);
}

/*
 *	Saves the mixer device channel values specified in the
 *	recorder to file.
 *
 *	Other mixer device channel values in the file will also
 *	be read and rewritten to file (their values will be
 *	preserved).
 *
 *	If the fname.mixer is an empty string then no operation
 *	will be performed.
 *
 *	The recorder's mixer must already be opened for this
 *	function to have any affect.
 */
int MixerRCSaveToFile(const char *filename, Recorder *recorder)
{
	int i, mixer_index, total;
	int got_our_mixer = 0;
	FILE *fp;
	mixer_device_fdata_struct **ptr, *mdfd_ptr;
	const char *this_mixer_dev_name;
	char *mixer_name[] = YMixerConicalNames;


	if((filename == NULL) ||
	   (recorder == NULL)
	)
	    return(-1);

	/* Get pointer to our mixer device's name, this will be
	 * used to check through the listing of mixers to see which
	 * one we want to save values for.
	 */
	this_mixer_dev_name = fname.mixer;
	if(this_mixer_dev_name[0] == '\0')
	{
	    /* Mixer device name is empty string, nothing to save */
	    return(0);
	}

	/* Load all mixer devices data from file */
	ptr = MixerRCLoadAllFromFile(filename, &total);
	if(ptr == NULL)
	    total = 0;

	/* Go through mixer device listing, search for our mixer
	 * device by matching name. If it is found, then update
	 * its values with the current mixer device channel values.
	 */
	for(i = 0; i < total; i++)
	{
	    mdfd_ptr = ptr[i];

	    if(mdfd_ptr == NULL)
		continue;
	    if(mdfd_ptr->name == NULL)
		continue;

	    /* Match mixer device name */
	    if(!strcasecmp(mdfd_ptr->name, this_mixer_dev_name))
	    {
		/* This is the mixer device we're looking for, now go
		 * through each mixer device channel setting.
		 */
		for(mixer_index = 0; mixer_index < YTotalMixers; mixer_index++)
		{
		    /* Get current mixer values for our mixer, store
		     * the fetched values into the mixer device file
		     * data structure which we will save later.
		     */
		    YMixerGet(
			recorder, mixer_index + YMixerCodeBaseOffset,
			mdfd_ptr->val[mixer_index]
		    );
		}
	    }

	    /* Mark that we have set values for our mixer device
	     * in the list.
	     */
	    got_our_mixer = 1;
	}
	/* If our mixer device was not in the loaded mixer device listing
	 * then append it to the list.
	 */
	if(!got_our_mixer)
	{
	    /* Sanitize total */
	    if(total < 0)
		total = 0;

	    /* Allocate one more pointer */
	    i = total;
	    total++;
	    ptr = (mixer_device_fdata_struct **)realloc(
		ptr,
		total * sizeof(mixer_device_fdata_struct *)
	    );
	    if(ptr == NULL)
	    {
		total = 0;
	    }
	    else
	    {
		/* Allocate our mixer device structure in the list */
		ptr[i] = (mixer_device_fdata_struct *)calloc(
		    1,
		    sizeof(mixer_device_fdata_struct)
		);
		mdfd_ptr = ptr[i];
		if(mdfd_ptr != NULL)
		{
		    mdfd_ptr->name = strdup(this_mixer_dev_name);

		    /* Go through mixer device channel settings */
		    for(mixer_index = 0; mixer_index < YTotalMixers; mixer_index++)
		    {
			/* Get current mixer values for our mixer, store
			 * the fetched values into the mixer device file
			 * data structure which we will save later.
			 */   
			YMixerGet(
			    recorder, mixer_index + YMixerCodeBaseOffset,
			    mdfd_ptr->val[mixer_index]
			);
		    }
		}
	    }
	}

	/* Do not deallocate mixer device listing just yet, we need
	 * to write them back to file below.
	 */

	/* Open mixer device file for writing */
	fp = FOpen(filename, "wb");
	if(fp != NULL)
	{
	    /* Write commented heading */
	    fprintf(fp,
"# Mixer device channel settings.\n\
# This file is automatically generated by the YIFF Sound Server.\n\
# You may manually edit this file as needed.\n\
#\n"
	    );

	    /* Write each mixer device channel values to file */
	    for(i = 0; i < total; i++)
	    {
		mdfd_ptr = ptr[i];

		if(mdfd_ptr == NULL)
		    continue;
		if(mdfd_ptr->name == NULL)
		    continue;

		fprintf(fp, "BeginMixer = %s\n",
		    mdfd_ptr->name
		);

		/* Write each mixer channel values */
		for(mixer_index = 0; mixer_index < YTotalMixers; mixer_index++)
		{
		    fprintf(fp, "    %s = %f %f %f %f\n",
			mixer_name[mixer_index],
			(YMixerValues > 0) ?
			    mdfd_ptr->val[mixer_index][0] : 0.0,
			(YMixerValues > 1) ?
			    mdfd_ptr->val[mixer_index][1] : 0.0,
			(YMixerValues > 2) ?
			    mdfd_ptr->val[mixer_index][2] : 0.0,
			(YMixerValues > 3) ?
			    mdfd_ptr->val[mixer_index][3] : 0.0
		    );
		}

		fprintf(fp, "EndMixer\n");
	    }

	    /* Close the file */
	    FClose(fp);
	    fp = NULL;
	}

	/* Deallocate loaded mixer device file data listing */
	MixerFreeFData(ptr, total);

	return(0);
}

