/*
 *  EMU10k1 loader
 *
 *  Copyright (c) 2003,2004 by Peter Zubaj
 *
 *   Hwdep usage based on sb16_csp
 *
 *   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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

#include <limits.h>
#include <sys/stat.h>
#include <alsa/asoundlib.h>
#include <getopt.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <linux/ioctl.h>
#include <ctype.h>
#include <sys/stat.h>
#include <unistd.h>
#include <signal.h>
#include <time.h>

#include "ld10k1.h"
#include "ld10k1_fnc.h"
#include "ld10k1_fnc1.h"

int card = 0;
snd_hwdep_t *handle;
char comm_pipe[256];
FILE *comm;
char pidpath[256];
FILE *logfile=NULL;

static void vlog(const char *label, const char *fmt, va_list va)
{
	FILE *out = stderr;

	if (logfile)
		out = logfile;

	if (logfile) {
		char timestr[20];
		time_t tp;

		tp = time(NULL);
		strftime(timestr, sizeof(timestr), "%b %d %H:%M:%S",
			 localtime(&tp));
		fprintf(out, "%s %s", timestr, label);
	} else 
		fprintf(out, "%s", label);
	vfprintf(out, fmt, va);
	fprintf(out, "\n");
	fflush(out);
}

void error(const char *fmt, ...)
{
	va_list va;

	va_start(va, fmt);
	vlog("Error: ", fmt, va);
	va_end(va);
}

static void alog(const char *fmt, ...)
{
	va_list va;

	va_start(va, fmt);
	vlog("", fmt, va);
	va_end(va);
}

static void help(char *command)
{
	fprintf(stderr,
		"Usage: %s [-options]\n"
		"\nAvailable options:\n"
		"  -h, --help        this help\n"
		"  -c, --card        select card number, default = 0\n"
		"  -p, --pipe_name   connect to this, default = /tmp/.ld10k1_port\n"
		"  -n, --network     listen on port\n"
		"      --port        port number, default = 20480\n"
		"  -d  --daemon      start in background\n"
		"  -i  --pidfile     print daemon process id to file, default /var/run/ld10k1.pid\n"
		"  -l  --logfile     \n"
		"  -t, --tram_size   initialize tram with given size\n"
		"		0 -    0 KB\n"
		"		1 -   16 KB\n"
		"		2 -   32 KB\n"
		"		3 -   64 KB\n"
		"		4 -  128 KB\n"
		"		5 -  256 KB\n"
		"		6 -  512 KB\n"
		"		7 - 1024 KB\n"
		"		8 - 2048 KB\n"
		, command);
}

static void cleanup()
{
	if (pidpath[0])
		unlink(pidpath);
	alog("Exiting daemon");
}

static void term_handler(int i)
{
	exit(1);
}

int tram_size_table[] = {0, 8192, 16384, 32768, 65536, 131072, 262144, 524288, 1048576};

int main(int argc, char *argv[])
{
	int dev;
	int c;
	int err;
	int audigy;

	int opt_help = 0;
	int tram_size = 0;
	int opt_daemon = 0;
	unsigned short opt_port = 20480;
	int uses_pipe = 1;
	char logpath[255];

	char card_id[32];
	const char *card_proc_id;

	snd_ctl_t *ctl_handle;
	snd_ctl_card_info_t *card_info;
	snd_hwdep_info_t *hwdep_info;
	
	char name[16];

	comm_param params;
	
	int option_index;
	
	static struct option long_options[] = {
				   {"help", 0, 0, 'h'},
				   {"card", 1, 0, 'c'},
				   {"pipe_name", 1, 0, 'p'},
				   {"network", 0, 0, 'n'},
				   {"port", 1, 0, 0},
				   {"daemon", 0, 0, 'd'},
				   {"tram_size", 1, 0, 't'},
				   {"pidfile", 1, 0, 'i'},
				   {"logfile", 1, 0, 'l'},
                   {0, 0, 0, 0}
               };

	snd_ctl_card_info_alloca(&card_info);
	snd_hwdep_info_alloca(&hwdep_info);

	strcpy(comm_pipe,"/tmp/.ld10k1_port");
	strcpy(pidpath, "/var/run/ld10k1.pid");
	memset(logpath, 0, sizeof(logpath));

	option_index = 0;
	while ((c = getopt_long(argc, argv, "hc:p:t:ndl:i:",
	        long_options, &option_index)) != EOF) {
		switch (c) {
		case 0:
			if (strcmp(long_options[option_index].name, "port") == 0) {
				opt_port = atoi(optarg);
			}
			break;
		case 'h':
			opt_help = 1;
			break;
		case 'c':
			card = snd_card_get_index(optarg);
			if (card < 0 || card > 31) {
				error ("wrong -c argument '%s'\n", optarg);
				return 1;
			}
			break;
		case 'p':
			uses_pipe = 1;
			strncpy(comm_pipe, optarg, sizeof(comm_pipe) - 1);
			comm_pipe[sizeof(comm_pipe) - 1] = '\0';
			break;
		case 'n':
			uses_pipe = 0;
			break;
		case 'd':
			opt_daemon = 1;
			break;
		case 't':
			tram_size = atoi(optarg);
			if (tram_size < 0)
				tram_size = 0;
			else if (tram_size > 8)
				tram_size = 8;
			tram_size = tram_size_table[tram_size];
			break;
		case 'i':
			strncpy(pidpath, optarg, sizeof(pidpath) - 1);
			pidpath[sizeof(pidpath) - 1] = '\0';
			break;
		case 'l':
			strncpy(logpath, optarg, sizeof(logpath) - 1);
			logpath[sizeof(logpath) - 1] = '\0';
			break;
		default:
			return 1;
		}
	}

	if (opt_help) {
		help(argv[0]);
		return 0;
	}
	
	if (getuid() != 0 ) {
		error("You are not running ld10k1 as root.");
		return 1;
	}
	
	if (logpath[0])
		logfile = fopen(logpath, "at");

	if (opt_daemon) {
		FILE *pidfile;

		if (daemon(0, 0) < 0) {
		    error("Unable to run as daemon.");
    		    return 1;
		}
		
		pidfile = fopen(pidpath, "wt");
		if (!pidfile) {
			alog("%s: pidfile (%s)\n", strerror(errno), pidpath);
			return 1;
		}
		
		fprintf(pidfile, "%d\n", getpid());
		fflush(pidfile);
		fclose(pidfile);
		
		atexit(cleanup);
		signal(SIGTERM, term_handler);

		if (logfile) {
			dup2(fileno(logfile), fileno(stderr));
			dup2(fileno(logfile), fileno(stdout));
		}
		
		alog("Starting daemon");
	}

	params.type = uses_pipe ? COMM_TYPE_LOCAL : COMM_TYPE_IP;
	params.name = comm_pipe;
	params.server = 1;
	params.port = opt_port;
	params.wfc = 0;

	/* Get control handle for selected card */
	sprintf(card_id, "hw:%i", card);
	if ((err = snd_ctl_open(&ctl_handle, card_id, 0)) < 0) {
		error("control open (%s): %s", card_id, snd_strerror(err));
		return 1;
	}

	/* Read control hardware info from card */
	if ((err = snd_ctl_card_info(ctl_handle, card_info)) < 0) {
		error("control hardware info (%s): %s", card_id, snd_strerror(err));
		exit(1);
	}

	if (!(card_proc_id = snd_ctl_card_info_get_id (card_info))) {
		error("card id (%s): %s", card_id, snd_strerror(err));
		exit(1);
	}


	/* EMU10k1/EMU10k2 chip is present only on SB Live, Audigy, Audigy 2, E-mu APS cards */
	if (strcmp(snd_ctl_card_info_get_driver(card_info), "EMU10K1") != 0 &&
	    strcmp(snd_ctl_card_info_get_driver(card_info), "Audigy") != 0 &&
		strcmp(snd_ctl_card_info_get_driver(card_info), "Audigy2") != 0 &&
		strcmp(snd_ctl_card_info_get_driver(card_info), "E-mu APS") != 0) {
		error("not a EMU10K1/EMU10K2 based card");
		exit(1);
	}

	if (strcmp(snd_ctl_card_info_get_driver(card_info), "Audigy") == 0 ||
		strcmp(snd_ctl_card_info_get_driver(card_info), "Audigy2") == 0)
		audigy = 1;
	else
		audigy = 0;
		
	/* find EMU10k1 hardware dependant device and execute command */
	dev = -1;
	err = 1;
	while (1) {
		if (snd_ctl_hwdep_next_device(ctl_handle, &dev) < 0)
			error("hwdep next device (%s): %s", card_id, snd_strerror(err));
		if (dev < 0)
			break;
		snd_hwdep_info_set_device(hwdep_info, dev);
		if (snd_ctl_hwdep_info(ctl_handle, hwdep_info) < 0) {
			if (err != -ENOENT)
				error("control hwdep info (%s): %s", card_id, snd_strerror(err));
			continue;
		}
		if (snd_hwdep_info_get_iface(hwdep_info) == SND_HWDEP_IFACE_EMU10K1) {
			sprintf(name, "hw:%i,%i", card, dev);

			/* open EMU10k1 hwdep device */
			if ((err = snd_hwdep_open(&handle, name, O_WRONLY)) < 0) {
				error("EMU10k1 open (%i-%i): %s", card, dev, snd_strerror(err));
				exit(1);
			}

			while (1)
				if (main_loop(&params, audigy, card_proc_id, tram_size, ctl_handle)) {
					error("error in main loop");
					break;
				}
			
			snd_hwdep_close(handle);

			break;
		}
	}

	snd_ctl_close(ctl_handle);

	return 0;
}
