/*  MikMod example player
	(c) 1999 Miodrag Vallat and others - see file AUTHORS for
	complete list.

	This program is free software; you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation; either version 2 of the License, or
	(at your option) any later version.
 
	This program 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.  See the
	GNU General Public License for more details.
 
	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
	02111-1307, USA.
*/

/*==============================================================================

  $Id: mconfig.c,v 1.7 1999/07/05 04:00:02 miod Exp $

  Load/Save of config options.

==============================================================================*/

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

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

#include <mikmod.h>
#include "player.h"
#include "mutilities.h"

/* hope this will be enough... */
#define LINE_LEN 500

typedef struct {
	int id;
	char *label;
} LABEL_CONV;

typedef enum {
	OPT_DRIVER,
#if LIBMIKMOD_VERSION >= 0x030107
	OPT_DRV_OPTION,
#endif
	OPT_STEREO,
	OPT_MODE_16BIT,
	OPT_FREQUENCY,
	OPT_INTERPOLATE,
	OPT_HQMIXER,
	OPT_SURROUND,
	OPT_REVERB,

	OPT_VOLUME,
	OPT_VOLRESTRICT,
	OPT_FADE,
	OPT_LOOP,
	OPT_PANNING,
	OPT_EXTSPD,

	OPT_PM_MODULE,
	OPT_PM_MULTI,
	OPT_PM_SHUFFLE,
	OPT_PM_RANDOM,
	OPT_CURIOUS,
	OPT_TOLERANT,
	OPT_RENICE,
	OPT_STATUSBAR,

	OPT_S_CONFIG,
	OPT_S_PLAYLIST,
	OPT_PL_NAME,
	OPT_FULLPATHS,
	OPT_NONE
} OPTION_ID;

typedef struct {
	OPTION_ID id;
	char *label,*description;
} OPTION;

static LABEL_CONV renice_conv[]={
	{RENICE_NONE,"RENICE_NONE"},
	{RENICE_PRI, "RENICE_PRI"},
	{RENICE_REAL,"RENICE_REAL"},
	{-1,NULL}
};

static OPTION options[]={
	{OPT_DRIVER, "DRIVER",
	 "# DRIVER = <val>, nth driver for output, default: 0\n"},
#if LIBMIKMOD_VERSION >= 0x030107
	{OPT_DRV_OPTION, "DRV_OPTIONS",
	 "# DRV_OPTIONS = \"options\", the driver options, e.g. \"buffer=14,count=16\"\n"
	 "#                          for the OSS-driver\n"},
#endif
	{OPT_STEREO, "STEREO",
	 "# STEREO = Yes|No, stereo or mono output, default: stereo\n"},
	{OPT_MODE_16BIT, "16BIT",
	 "# 16BIT = Yes|No, 8 or 16 bit output, default: 16 bit\n"},
	{OPT_FREQUENCY, "FREQUENCY",
	 "# FREQUENCY = <val>, mixing frequency, default: 44100 Hz\n"},
	{OPT_INTERPOLATE, "INTERPOLATE",
	 "# INTERPOLATE = Yes|No, use interpolate mixing, default: No\n"},
	{OPT_HQMIXER, "HQMIXER",
	 "# HQMIXER = Yes|No, use high-quality (but slow) software mixer, default: No\n"},
	{OPT_SURROUND, "SURROUND",
	 "# SURROUND = Yes|No, use surround mixing, default: No\n"},
	{OPT_REVERB, "REVERB",
	 "# REVERB = <val>, set reverb amount (0-15), default:0 (none)\n"},

	{OPT_VOLUME, "VOLUME",
	 "# VOLUME = <val>, volume from 0 (silence) to 100, default: 100\n"},
	{OPT_VOLRESTRICT, "VOLRESTRICT",
	 "# VOLRESTRICT = Yes|No, restrict volume of player to volume supplied by user,\n"
	 "                        default: No\n"},
	{OPT_FADE, "FADEOUT",
	 "# FADEOUT = Yes|No, volume fade at the end of the module, default: No\n"},
	{OPT_LOOP, "LOOP",
	 "# LOOP = Yes|No, enable in-module loops, default: No\n"},
	{OPT_PANNING, "PANNING",
	 "# PANNING = Yes|No, process panning effects, default: Yes\n"},
	{OPT_EXTSPD, "EXTSPD",
	 "# EXTSPD = Yes|No, process Protracker extended speed effect, default: Yes\n"},

	{OPT_PM_MODULE ,"PM_MODULE",
	 "# PM_MODULE = Yes|No, Module repeats, default: No\n"},
	{OPT_PM_MULTI ,"PM_MULTI",
	 "# PM_MULTI = Yes|No, PlayList repeats, default: Yes\n"},
	{OPT_PM_SHUFFLE ,"PM_SHUFFLE",
	 "# PM_SHUFFLE = Yes|No, Shuffle list at start and if all entries are played,\n"
	 "#                      default: No\n"},
	{OPT_PM_RANDOM ,"PM_RANDOM",
	 "# PM_RANDOM = Yes|No, PlayList in random order, default: No\n"},

	{OPT_CURIOUS, "CURIOUS",
	 "# CURIOUS = Yes|No, look for hidden patterns in module, default: No\n"},
	{OPT_TOLERANT, "TOLERANT",
	 "# TOLERANT = Yes|No, don't halt on file access errors, default: No\n"},
	{OPT_RENICE, "RENICE",
	 "# RENICE = RENICE_NONE (change nothing), RENICE_PRI (Renice to -20) or\n"
	 "#          RENICE_REAL (get realtime priority), default: RENICE_NONE\n"
	 "#   Note that RENICE_PRI is only available under FreeBSD, Linux, NetBSD,\n"
	 "#   OpenBSD and OS/2, and RENICE_REAL is only available under FreeBSD, Linux\n"
	 "#   and OS/2.\n"},
	{OPT_STATUSBAR, "STATUSBAR",
	 "# STATUSBAR = <val>, size of statusbar from 0 to 2, default: 2\n"},
	{OPT_S_CONFIG, "SAVECONFIG",
	 "# SAVECONFIG = Yes|No, save configuration on exit, default: No\n"},
	{OPT_S_PLAYLIST, "SAVEPLAYLIST",
	 "# SAVEPLAYLIST = Yes|No, save playlist on exit, default: No\n"},
	{OPT_PL_NAME, "PL_NAME",
	 "# PL_NAME = \"name\", name under which the playlist will be saved\n"
	 "#                   by selecting 'Save' in the playlist-menu\n"},
	{OPT_FULLPATHS, "FULLPATHS",
	 "# FULLPATHS = Yes|No, display full path of files, default: No\n"},
	{OPT_NONE, NULL, NULL}
};

char* CF_GetFilename(void)
{
#if defined(__OS2__)||defined(__EMX__)
	return get_cfg_name("mikmod.cfg");
#else
	return get_cfg_name(".mikmodrc");
#endif
}

void CF_Init(CONFIG *cfg)
{
	cfg->driver       =0;
#if LIBMIKMOD_VERSION >= 0x030107
	CF_set_string(&cfg->driveroptions,"",99);
#endif
	cfg->stereo       =1;
	cfg->mode_16bit   =1;
	cfg->frequency    =44100;
	cfg->interpolate  =0;
	cfg->hqmixer      =0;
	cfg->surround     =0;
	cfg->reverb		  =0;

	cfg->volume       =100;
	cfg->volrestrict  =0;
	cfg->fade         =0;
	cfg->loop         =0;
	cfg->panning      =1;
	cfg->extspd       =1;

	cfg->playmode	  =PM_MULTI;
	cfg->curious      =0;
	cfg->tolerant     =0;
	cfg->renice       =RENICE_NONE;
	cfg->statusbar	  =2;

	cfg->save_config  =0;
	cfg->save_playlist=0;
	cfg->fullpaths    =0;

	CF_set_string(&cfg->pl_name,"playlist.mpl",PATH_MAX);
}

static void write_bool(FILE *file,OPTION option,BOOL arg)
{
	if (option.description)
		fputs(option.description,file);
	if (arg)
		fprintf(file,"%s=yes\n\n",option.label);
	else
		fprintf(file,"%s=no\n\n",option.label);
}

static void write_int(FILE *file,OPTION option,int arg)
{
	if (option.description)
		fputs(option.description,file);
	fprintf(file,"%s=%d\n\n",option.label,arg);
}

static void write_label(FILE *file,OPTION option,LABEL_CONV *convert,int arg)
{
	int i;

	if (option.description)
		fputs(option.description,file);
	for (i=0;convert[i].id!=arg;i++);
	fprintf(file,"%s=%s\n\n",option.label,convert[i].label);
}

static void write_string(FILE *file,OPTION option,char *arg)
{
	if (option.description)
		fputs(option.description,file);
	if (arg)
		fprintf(file,"%s=\"%s\"\n\n",option.label,arg);
	else
		fprintf(file,"%s=\"\"\n\n",option.label);
}

BOOL CF_Save(CONFIG *cfg)
{
	FILE *file;
	char *name;

	if (!(name=CF_GetFilename())) return 0;
	if (!(file=fopen(name,"w"))) return 0;
	free(name);

	if (fputs("#\n"
	          "# "mikversion"\n"
	          "# configuration file\n"
	          "#\n\n",file)==EOF) {
		fclose(file);
		return 0;
	}
	write_int(file,options[OPT_DRIVER],       cfg->driver);
#if LIBMIKMOD_VERSION >= 0x030107
	write_string(file,options[OPT_DRV_OPTION],cfg->driveroptions);
#endif
	write_bool(file,options[OPT_STEREO],      cfg->stereo);
	write_bool(file,options[OPT_MODE_16BIT],  cfg->mode_16bit);
	write_int(file,options[OPT_FREQUENCY],    cfg->frequency);
	write_bool(file,options[OPT_INTERPOLATE], cfg->interpolate);
	write_bool(file,options[OPT_HQMIXER],     cfg->hqmixer);
	write_bool(file,options[OPT_SURROUND],    cfg->surround);
	write_int(file,options[OPT_REVERB],       cfg->reverb);

	write_int(file,options[OPT_VOLUME],       cfg->volume);
	write_bool(file,options[OPT_VOLRESTRICT], cfg->volrestrict);
	write_bool(file,options[OPT_FADE],        cfg->fade);
	write_bool(file,options[OPT_LOOP],        cfg->loop);
	write_bool(file,options[OPT_PANNING],     cfg->panning);
	write_bool(file,options[OPT_EXTSPD],      cfg->extspd);

	write_bool(file,options[OPT_PM_MODULE],   BTST(cfg->playmode,PM_MODULE));
	write_bool(file,options[OPT_PM_MULTI],    BTST(cfg->playmode,PM_MULTI));
	write_bool(file,options[OPT_PM_SHUFFLE],  BTST(cfg->playmode,PM_SHUFFLE));
	write_bool(file,options[OPT_PM_RANDOM],   BTST(cfg->playmode,PM_RANDOM));

	write_bool(file,options[OPT_CURIOUS],     cfg->curious);
	write_bool(file,options[OPT_TOLERANT],    cfg->tolerant);
	write_label(file,options[OPT_RENICE],     renice_conv,cfg->renice);
	write_int(file,options[OPT_STATUSBAR],    cfg->statusbar);

	write_bool(file,options[OPT_S_CONFIG],	  cfg->save_config);
	write_bool(file,options[OPT_S_PLAYLIST],  cfg->save_playlist);

	write_string(file,options[OPT_PL_NAME],   cfg->pl_name);
	
	write_bool(file,options[OPT_FULLPATHS],   cfg->fullpaths);

	fclose(file);
	return 1;
}

static char skip_space(char **line)
{
	while ((**line==' ')||(**line=='\t'))
		(*line)++;
	return **line;
}

static BOOL parse_line(char *line,char **label,char **arg)
{
	char *end;

	*label=*arg=NULL;

	if (skip_space(&line)=='#')
		return 0;

	*label=line;
	while (isalnum((int)*line)||(*line == '_')) {
		*line=toupper((int)*line);
		line++;
	}
	end=line;

	if (skip_space(&line)=='='){
		line++;
		skip_space(&line);
		if ((isgraph((int)*line))&&(*line!='\t')&&(*line!=' ')) {
			BOOL string=(*line=='"');
			if (string) line++;
			*end='\0';
			*arg=line;
			while ((!string && isgraph((int)*line) && *line!='\t' && *line!=' ') ||
					(string && *line && *line!='"')) {
				if (!string) *line=toupper((int)*line);
				line++;
			}
			*line='\0';
			return 1;
		}
	}
	return 0;
}

static void set_int(int *value,char *arg,int min,int max)
{
	char *end;

	int t=strtol(arg,&end,10);
	if ((!*end)&&(t>=min)&&(t<=max))
		*value=t;
}

static void set_bool(BOOL *value,char *arg)
{
	if ((!strcasecmp(arg,"YES"))||(!strcasecmp(arg,"ON"))||(*arg=='1'))
		*value=1;
	else if ((!strcasecmp(arg,"NO"))||(!strcasecmp(arg,"OFF"))||(*arg=='0'))
		*value = 0;
}

static void set_bit(int *value,int mask,char *arg)
{
	if ((!strcasecmp(arg,"YES"))||(!strcasecmp(arg,"ON"))||(*arg=='1'))
		*value |= mask;
	else if ((!strcasecmp(arg,"NO"))||(!strcasecmp(arg,"OFF"))||(*arg=='0'))
		*value &= ~mask;
}

static void set_label(int *value,char *arg,LABEL_CONV *convert)
{
	int i=0;

	while (convert[i].label) {
		if (!strcmp(convert[i].label,arg)) {
			*value=convert[i].id;
			return;
		}
		i++;
	}
}

void CF_set_string(char **value,char *arg,int length)
{
	int len=strlen(arg);

	if (len>length) len=length;
	if (*value) free(*value);
	*value=(char*)malloc((len+1)*sizeof(char));
	strncpy(*value,arg,len);
	(*value)[len]='\0';
}

static void set_option(CONFIG *cfg,OPTION_ID opt,char *arg)
{
	switch (opt) {
		case OPT_NONE:
			break;
		case OPT_DRIVER:
			set_int(&cfg->driver,arg,0,999);
			break;
#if LIBMIKMOD_VERSION >= 0x030107
		case OPT_DRV_OPTION:
			CF_set_string(&cfg->driveroptions,arg,99);
			break;
#endif
		case OPT_STEREO:
			set_bool(&cfg->stereo,arg);
			break;
		case OPT_MODE_16BIT:
			set_bool(&cfg->mode_16bit,arg);
			break;
		case OPT_FREQUENCY:
			set_int(&cfg->frequency,arg,4000,60000);
			break;
		case OPT_INTERPOLATE:
			set_bool(&cfg->interpolate,arg);
			break;
		case OPT_HQMIXER:
			set_bool(&cfg->hqmixer,arg);
			break;
		case OPT_SURROUND:
			set_bool(&cfg->surround,arg);
			break;
		case OPT_REVERB:
			set_int(&cfg->reverb,arg,0,15);
			break;
		case OPT_VOLUME:
			set_int(&cfg->volume,arg,0,100);
			break;
		case OPT_VOLRESTRICT:
			set_bool(&cfg->volrestrict,arg);
			break;
		case OPT_FADE:
			set_bool(&cfg->fade,arg);
			break;
		case OPT_LOOP:
			set_bool(&cfg->loop,arg);
			break;
		case OPT_PANNING:
			set_bool(&cfg->panning,arg);
			break;
		case OPT_EXTSPD:
			set_bool(&cfg->extspd,arg);
			break;
		case OPT_PM_MODULE:
			set_bit(&cfg->playmode,PM_MODULE,arg);
			break;
		case OPT_PM_MULTI:
			set_bit(&cfg->playmode,PM_MULTI,arg);
			break;
		case OPT_PM_SHUFFLE:
			set_bit(&cfg->playmode,PM_SHUFFLE,arg);
			break;
		case OPT_PM_RANDOM:
			set_bit(&cfg->playmode,PM_RANDOM,arg);
			break;
		case OPT_CURIOUS:
			set_bool(&cfg->curious,arg);
			break;
		case OPT_TOLERANT:
			set_bool(&cfg->tolerant,arg);
			break;
		case OPT_RENICE:
			set_label(&cfg->renice,arg,renice_conv);
			break;
		case OPT_STATUSBAR:
			set_int(&cfg->statusbar,arg,0,2);
			break;
		case OPT_S_CONFIG:
			set_bool(&cfg->save_config,arg);
			break;
		case OPT_S_PLAYLIST:
			set_bool(&cfg->save_playlist,arg);
			break;
		case OPT_PL_NAME:
			CF_set_string(&cfg->pl_name,arg,PATH_MAX);
			break;
		case OPT_FULLPATHS:
			set_bool(&cfg->fullpaths,arg);
			break;
	}
}

BOOL CF_Load(CONFIG *cfg)
{
	FILE *file;
	char *name=CF_GetFilename();
	CHAR line[LINE_LEN],*label,*arg;
	int i;

	if (!name)
		return 0;
	if (!(file=fopen(name,"r")))
		return 0;
	free(name);

	while (fgets(line,LINE_LEN,file)) {
		if (line[strlen(line)-1]=='\n')
			line[strlen(line)-1]='\0';
		if (!strcasecmp(line,PL_IDENT)) {
			fclose(file);
			return 1;
		}

		if (parse_line(line,&label,&arg)) {
			i = 0;
			while ((options[i].label)&&(strcmp(options[i].label,label))) i++;
			if (options[i].label)
				set_option(cfg,options[i].id,arg);
		}
	}
	fclose(file);
	return 1;
}

/* ex:set ts=4: */
