/*
 *  config.c
 *  mod_musicindex
 *
 *  $Id: config.c 1001 2012-07-30 18:45:20Z varenet $
 *
 *  Created by Thibaut VARENE on Thu Mar 20 2003.
 *  Copyright (c) 2003-2006 Regis BOUDIN
 *  Copyright (c) 2003-2005,2007,2009,2012 Thibaut VARENE
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser General Public License version 2.1,
 *  as published by the Free Software Foundation.
 *
 */

/**
 * @file
 * Configuration handling and default settings.
 *
 * @author Regis Boudin
 * @author Thibaut Varene
 * @version $Revision: 1001 $
 * @date 2003-2007
 * @date 2009
 * @date 2012
 *
 * That file takes care of the module configuration, either via setting some
 * known default values, and/or by getting them from environmental configuration
 * variables passed to apache in its config file.
 *
 * http://www.apachetutor.org/dev/config \n
 * http://www.kuzbass.ru/docs/apache_c_mod_perl/147.htm
 */

#include "config.h"
#include "sort.h"

#ifdef	ENABLE_CACHE_FILE
#include "cache-file.h"
#endif
#ifdef	ENABLE_CACHE_MYSQL
#include "cache-mysql.h"
#endif

#ifdef HAVE_STRING_H
#include <string.h>
#endif
#ifdef HAVE_STDLIB_H
#include <stdlib.h>	/* atoi */
#endif

typedef int (*cache_backend_setup)(cmd_parms *cmd, const char *setup_string, mu_config *const conf);

/** list of cache setup functions, to be handled manually */
static const cache_backend_setup const cache_setups[] = {
#ifdef	ENABLE_CACHE_FILE
	cache_file_setup,
#endif
#ifdef	ENABLE_CACHE_MYSQL
	cache_mysql_setup,
#endif
	NULL
};

/** Default sort order */
static const unsigned char const default_order[] = {
	SB_ALBUM,
	SB_POSN,
	SB_TRACK,
	SB_ARTIST,
	SB_TITLE,
	SB_LENGTH,
	SB_BITRATE,
	SB_FREQ,
	SB_FILETYPE,
	SB_FILENAME,
	SB_URI,
};

/** Default fields displayed. */
static const unsigned char const default_fields[] = {
	SB_TITLE,
	SB_ARTIST,
	SB_ALBUM,
	SB_LENGTH,
	SB_BITRATE,
};

static const char const default_directory[] = "/musicindex";	/**< Default musicindex directory */
static const char const default_rootname[] = "Music";		/**< Default root directory name */
static const char const default_css[] = "musicindex.css";	/**< Default CSS file name */

/**
 * Generates a per-directory configuration.
 *
 * This function creates a new configuration structure and fills it with
 * default values.
 *
 * @note It kinda bugs me that we don't need to set @a options_not = ~0, but it
 * seems to work without it...
 *
 * @param p Apache pool for memory allocation.
 * @param dummy string containing the path to the relevant Directory or Location - we don't use it.
 *
 * @return The newly created configuration structure.
 */
void *create_musicindex_config(apr_pool_t *p, char *dummy)
{
	mu_config *const new = (mu_config *)apr_pcalloc(p, sizeof(mu_config));

	memcpy(new->fields, default_fields, sizeof(default_fields));
	memcpy(new->order, default_order, sizeof(default_order));
	
	new->title = default_rootname;
	new->directory = default_directory;
	new->css = default_css;

	new->cookie_life = CONF_COOKIE_LIFE;
	new->dir_per_line = CONF_DIRPERLINE;

	return (void *)new;
}

/**
 * Merge per-directory configurations.
 *
 * This function is supposed to merge two per-directory configurations. We
 * apply a simple inheritance pattern from the parent directory(ies).
 *
 * @note In the absence of a .htaccess in the hierarchy of folders being accessed,
 * #basev contains a freshly created config structure (through
 * create_musicindex_config()) and #addv contains another config struct created
 * through create_musicindex_config(), with the configuration parameters modified
 * by \<Directory\> directive (if any) set appropriately.\n
 *	#basev: clean	#addv: configured	=> keep #addv
 *
 * @note In the presence of a .htaccess file in the directory being accessed,
 * #basev contains the result of the merging of all "parent" configuration directives
 * (\<Directory\> being the top ancestor, and any subsquent .htaccess being merged
 * using merge_musicindex_configs()) up to the one in the current directory being
 * accessed, which config options are set in #addv.\n
 *	#basev: merged	#addv: configured	=> merge
 *
 * @note In the presence of a .htaccess anywhere in the parent hierarchy of the
 * directory being accessed @b BUT in said directory, #basev contains a pristine
 * config structure (as per create_musicindex_config()) and #addv contains the
 * result of merging all parent configuration directives (\<Directory\> being the
 * top ancestor).\n
 *	#basev: clean	#addv: merged		=> keep #addv
 *
 * Hence the logic of this function: if #basev contains init data (data setup in
 * create_musicindex_config()), use #addv element. Otherwise, merge both configs.
 *
 * @param p Apache pool for memory allocation.
 * @param basev Pointer to main configuration structure.
 * @param addv Pointer to parent dir configuration structure,
 *
 * @return The newly created configuration structure.
 */
void *merge_musicindex_configs(apr_pool_t *p, void *basev, void *addv)
{
	mu_config *const restrict new = (mu_config *)apr_pcalloc(p, sizeof(mu_config));
	mu_config *const restrict base = (mu_config *)basev;
	mu_config *const restrict add = (mu_config *)addv;

	if (!base->sets) {
		/* basev is empty we can ignore it */
		memcpy(new->order, add->order, sizeof(add->order));
		memcpy(new->fields, add->fields, sizeof(add->fields));
		new->title = add->title;
		new->css = add->css;
		new->iceserver = add->iceserver;
		new->cookie_life = add->cookie_life;
		new->rss_items = add->rss_items;
		new->dir_per_line = add->dir_per_line;
		new->cache = add->cache;
		new->cache_setup = add->cache_setup;
	}
	else {
		/* we need to merge the configurations */
		if (add->sets & CF_ORDER)
			memcpy(new->order, add->order, sizeof(add->order));
		else
			memcpy(new->order, base->order, sizeof(base->order));

		if (add->sets & CF_FIELDS)
			memcpy(new->fields, add->fields, sizeof(add->fields));
		else
			memcpy(new->fields, base->fields, sizeof(base->fields));

		new->title = (add->sets & CF_TITLE) ? add->title : base->title;
		new->css = (add->sets & CF_CSS) ? add->css : base->css;
		new->iceserver = (add->sets & CF_ICES) ? add->iceserver : base->iceserver;
		new->cookie_life = (add->sets & CF_CKLF) ? add->cookie_life : base->cookie_life;
		new->rss_items = (add->sets & CF_RSS) ? add->rss_items : base->rss_items;
		new->dir_per_line = (add->sets & CF_DPL) ? add->dir_per_line : base->dir_per_line;

		if (add->sets & CF_CACHE) {
			new->cache = add->cache;
			new->cache_setup = add->cache_setup;
		} else {
			new->cache = base->cache;
			new->cache_setup = base->cache_setup;
		}
	}

	new->options = base->options | add->options;
	new->options &= ~(base->options_not | add->options_not);
	
	new->options_not = base->options_not | add->options_not;
	new->options_not &= ~(base->options | add->options);

	/* directory is currently non-modifiable */
	new->directory = default_directory;

	/* flag new as containing meaningful data */
	new->sets |= CF_MERGED;

	return new;
}

static const struct {
	const char *const string;
	const char value;
} const options[] = {
	{"track",	SB_TRACK},
	{"disc",	SB_POSN},
	{"length",	SB_LENGTH},
	{"bitrate",	SB_BITRATE},
	{"freq",	SB_FREQ},
	{"artist",	SB_ARTIST},
	{"album",	SB_ALBUM},
	{"title",	SB_TITLE},
	{"filename",	SB_FILENAME},
	{"date",	SB_DATE},
	{"filetype",	SB_FILETYPE},
	{"genre",	SB_GENRE},
	{"uri",		SB_URI},
	{"size",	SB_SIZE},
	{NULL, 		0}
};

static unsigned short sort_or_fields(cmd_parms *cmd, unsigned char * restrict list, const char *optstr)
{
	const char *r;
	register unsigned short i = 0, j;

	while (optstr[0] && (i < SB_MAX)) {
		r = ap_getword_conf(cmd->temp_pool, &optstr);
		for (j=0; options[j].string; j++) {
			if (!strcasecmp(r, options[j].string)) {
				list[i++] = options[j].value;
				break;
			}
		}
	}
	list[i] = 0;

	return i;
}

static const char *sort_order(cmd_parms *cmd, void *d, const char *optstr)
{
	mu_config *const d_cfg = (mu_config *)d;
	unsigned short i = sort_or_fields(cmd, d_cfg->order, optstr);
	d_cfg->order[i] = SB_URI;	/* fallback on URI sort */
	d_cfg->sets |= CF_ORDER;
	return NULL;
}

static const char *set_fields(cmd_parms *cmd, void *d, const char *optstr)
{
	mu_config *const d_cfg = (mu_config *)d;
	sort_or_fields(cmd, d_cfg->fields, optstr);
	d_cfg->sets |= CF_FIELDS;
	return NULL;
}

/**
 * Enables the module options.
 *
 * Read the different options given as parameters, and set the various flags.
 *
 * @param cmd I don't use it.
 * @param d Pointer to configuration structure.
 * @param optstr The string given as parameter in the configuration.
 *
 * @return NULL don't ask why.
 */
static const char *basic_config(cmd_parms *cmd, void *d, const char *optstr)
{
	const char *r;
	mu_config *const cfg = (mu_config *)d;
	
	while (optstr[0] != '\0') {
		int enable = 1;
		unsigned short flag = 0;
		r = ap_getword_conf(cmd->temp_pool, &optstr);

		/* Should we enable or disable ? Is there a +/- prefix ? */
		if ( *r == '-' ) {
			enable = 0;
			r++;
		} else if (*r == '+') {
			r++;
		}

		/* Which option are we talking about ? */
		if (strcmp(r, "On") == 0) {
			enable = 1;
			flag = MI_ACTIVE;
		} else if (strcmp(r, "Off") == 0) {
			enable = 0;
			flag = MI_ACTIVE;
		} else if (strcmp(r, "Stream") == 0) {
			flag = MI_ALLOWSTREAM;
		} else if (strcmp(r, "Download") == 0) {
			flag = MI_ALLOWDWNLD;
		} else if (strcmp(r, "Search") == 0) {
			flag = MI_ALLOWSEARCH;
#ifdef ENABLE_OUTPUT_ARCHIVE
		} else if (strcmp(r, "Tarball") == 0) {
			flag = MI_ALLOWTARBALL;
#endif
		} else if (strcmp(r, "Rss") == 0) {
			if (enable == 1)
				cfg->rss_items = CONF_RSS_ITEMS;
			else {
				cfg->rss_items = -1;
			}
		}

		/* Have this done in one place instead of five */
		if (flag != 0) {
			if (enable == 1) {
				cfg->options |= flag;
				cfg->options_not &= ~flag;
			} else {
				cfg->options &= ~flag;
				cfg->options_not |= flag;
			}
		}
	}

	return NULL;
}

/**
 * Configures the cache location in the filesystem.
 *
 * This function sets the configuration string for the cache subsystem.
 *
 * @param cmd Struct containing a pointer to the pool I have to use.
 * @param d Pointer to configuration structure.
 * @param optstr The string given as parameter in the configuration.
 *
 * @return NULL don't ask why.
 */
static const char *set_cache_uri(cmd_parms *cmd, void *d, const char *optstr)
{
	mu_config *const d_cfg = (mu_config *)d;
	int i, ret = 1;

	for (i = 0; (ret != 0) && (cache_setups[i] != NULL); i++)
		ret = cache_setups[i](cmd, optstr, d_cfg);

	d_cfg->sets |= CF_CACHE;
	return NULL;
}

/**
 * Sets the title of the page.
 *
 * This function simply chages the name of the root.
 *
 * @param cmd Struct containing a pointer to the pool I have to use.
 * @param d Pointer to configuration structure.
 * @param optstr The string given as parameter in the configuration.
 *
 * @return NULL don't ask why.
 */
static const char *set_page_title(cmd_parms *cmd, void *d, const char *optstr)
{
	mu_config *const d_cfg = (mu_config *)d;
	if ((optstr == NULL) || (optstr[0] == '\0'))
		d_cfg->title = NULL;
	else
		d_cfg->title = apr_pstrdup(cmd->pool, optstr);
	d_cfg->sets |= CF_TITLE;
	return NULL;
}

/**
 * Sets the icecast server address.
 * This function sets an icecast server for streaming.
 *
 * @param cmd Struct containing a pointer to the pool I have to use.
 * @param d Pointer to configuration structure.
 * @param optstr The string given as parameter in the configuration.
 *
 * @return NULL don't ask why.
 */
static const char *set_ice_server(cmd_parms *cmd, void *d, const char *optstr)
{
	mu_config *const d_cfg = (mu_config *)d;
	d_cfg->iceserver = apr_pstrdup(cmd->pool, optstr);
	d_cfg->sets |= CF_ICES;
	return NULL;
}

/**
 * Sets the default CSS file.
 * This function sets which CSS file will be used by default
 *
 * @param cmd Struct containing a pointer to the pool I have to use.
 * @param d Pointer to configuration structure.
 * @param optstr The string given as parameter in the configuration.
 *
 * @return NULL don't ask why.
 */
static const char *set_css_default(cmd_parms *cmd, void *d, const char *optstr)
{
	mu_config *const d_cfg = (mu_config *)d;
	d_cfg->css = apr_pstrdup(cmd->pool, optstr);
	d_cfg->sets |= CF_CSS;
	return NULL;
}

/**
 * Sets the cookie lifetime.
 * This function sets the lifetime in seconds of the cookie used for custom
 * playlist constructs.
 *
 * @param cmd I don't use it.
 * @param d Pointer to configuration structure.
 * @param optstr The string given as parameter in the configuration.
 *
 * @return NULL don't ask why.
 */
static const char *set_cookie_life(cmd_parms *cmd, void *d, const char *optstr)
{
	mu_config *const d_cfg = (mu_config *)d;
	d_cfg->cookie_life = atoi(optstr);
	d_cfg->sets |= CF_CKLF;
	return NULL;
}

/**
 * Sets the number of directories to display per line.
 * Negative values will disable the "pretty folders" system.
 *
 * @param cmd I don't use it.
 * @param d Pointer to configuration structure.
 * @param optstr The string given as parameter in the configuration.
 *
 * @return NULL don't ask why.
 */
static const char *set_dirperline(cmd_parms *cmd, void *d, const char *optstr)
{
	mu_config *const d_cfg = (mu_config *)d;
	d_cfg->dir_per_line = (atoi(optstr) ? atoi(optstr) : CONF_DIRPERLINE);
	d_cfg->sets |= CF_DPL;
	return NULL;
}

/**
 * Sets the default display.
 *
 * @bug Wouldn't this override whatever is set with MusicSortOrder?
 *
 * @param cmd Struct containing a pointer to the pool I have to use.
 * @param d Pointer to configuration structure.
 * @param optstr The string given as parameter in the configuration.
 *
 * @return NULL don't ask why.
 */
static const char *set_display(cmd_parms *cmd, void *d, const char *optstr)
{
	mu_config *const cfg = (mu_config *)d;
	if (strcmp(optstr, "RSS") == 0) {
		cfg->options |= MI_RSS;
		cfg->options &= ~MI_RECURSIVE;
		cfg->order[0] = SB_MTIME;
		cfg->order[1] = SB_URI;
		cfg->rss_items = CONF_RSS_ITEMS; /* XXX necessary? */
		cfg->sets |= (CF_ORDER | CF_RSS);
	}
	else if (strcmp(optstr, "HTML") == 0) {
		cfg->options &= ~MI_RSS;
		cfg->options_not |= MI_RSS;
		memcpy(cfg->order, default_order, sizeof(default_order)); /* XXX necessary? */
		/* cfg->sets |= CF_ORDER; */
	}
	return NULL;
}

/* XXX if somebody puts a ACCESS_CONF setting in .htaccess, Apache triggers a 500 */
const command_rec musicindex_cmds[] = {
	AP_INIT_RAW_ARGS("MusicIndex", basic_config, NULL, OR_INDEXES,
		"can be : On/Off +/-Stream +/-Download +/-Search +/-Rss"),
	AP_INIT_RAW_ARGS("MusicSortOrder", sort_order, NULL, OR_INDEXES,
		"can be : title album artist track disc length bitrate filetype genre filename date uri"),
	AP_INIT_RAW_ARGS("MusicFields", set_fields, NULL, OR_INDEXES,
		"can be : title album artist track disc length bitrate filetype genre filename date"),
	AP_INIT_RAW_ARGS("MusicIndexCache", set_cache_uri, NULL, ACCESS_CONF,
		"Set the cache configuration string"),
	AP_INIT_RAW_ARGS("MusicPageTitle", set_page_title, NULL, OR_INDEXES,
		"Set the root title of the page."),
	AP_INIT_TAKE1("MusicIceServer", set_ice_server, NULL, ACCESS_CONF,
		"Set the icecast server address : [server.domain.org]:8000"),
	AP_INIT_TAKE1("MusicDefaultCss", set_css_default, NULL, OR_INDEXES,
		"Set the default CSS file"),
	AP_INIT_TAKE1("MusicCookieLife", set_cookie_life, NULL, ACCESS_CONF,
		"Set the lifetime (in seconds) of the cookie sent for custom playlists"),
	AP_INIT_TAKE1("MusicDefaultDisplay", set_display, NULL, ACCESS_CONF,
		"Set the default display returned by the module (HTML or RSS)"),
	AP_INIT_TAKE1("MusicDirPerLine", set_dirperline, NULL, OR_INDEXES,
		"Set the number of directories per line in the directory listing"),
	{NULL}
};
