/* Extended Module Player
 * Copyright (C) 1996-1999 Claudio Matsuoka and Hipolito Carraro Jr
 *
 * This file is part of the Extended Module Player and is distributed
 * under the terms of the GNU General Public License. See doc/COPYING
 * for more information.
 */

/*
 * Fri, 05 Jun 1998 16:46:13 -0700  "Mark R. Boyns" <boyns@sdsu.edu>
 * I needed to use xmp as a filter and was able to use /dev/fd/0 as an
 * input source but ran into problems with xmp trying to read interactive
 * commands. So, I added a few lines of code to disable command reading
 * with the --nocmd option.
 */

/*
 * Wed, 10 Jun 1998 15:42:25 -0500  Douglas Carmichael <dcarmich@mcs.com>
 * Support for real-time priority in FreeBSD.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <sys/types.h>
#include <signal.h>
#include <termios.h>
#include <unistd.h>

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

#ifdef HAVE_SYS_RTPRIO_H
#include <sys/resource.h>
#include <sys/rtprio.h>
#endif

#ifdef __EMX__
#define INCL_BASE
#include <os2.h>
#endif

#include "xmp.h"

#ifdef XXMP
#include <sys/wait.h>
#include "xpanel.h"
#endif

extern int optind;

/* Options not belonging to libxmp */
int probeonly = 0;
int randomize = 0;
int loadonly = 0;
int nocmd = 0;
#ifdef HAVE_SYS_RTPRIO_H
int rt = 0;
#endif

#ifdef XXMP
static pid_t pid = 0;
struct ipc_info *ii;
#else
static struct xmp_module_info mi;
#endif

static struct xmp_control opt;
static struct termios term;
static int background = 0;
#ifdef SIGTSTP
static int stopped = 0;
#endif
static int refresh_status = 0;
static int max_nch = 0;
static double rows, tot_nch;
int skip = 0;

static int sigusr = 0;

void get_options (int, char **, struct xmp_control *);


static int set_tty ()
{
    struct termios t;

    if (background)
	return -1;

    if (tcgetattr (0, &term) < 0)
	return -1;

    t = term;
    t.c_lflag &= ~(ECHO | ICANON | TOSTOP);
    t.c_cc[VMIN] = t.c_cc[VTIME] = 0;

    if (tcsetattr (0, TCSAFLUSH, &t) < 0)
	return -1;

    return 0;
}


static int reset_tty ()
{
    if (background)
	return -1;

    if (tcsetattr (0, TCSAFLUSH, &term) < 0) {
	fprintf (stderr, "can't reset terminal!\n");
	return -1;
    }

    return 0;
}

#ifdef SIGTSTP
static void sigtstp_handler ()
{
    if (!stopped) {
#ifndef XXMP
	fprintf (stderr, "] - STOPPED\n");
#endif
	xmp_timer_stop ();
	stopped = 1;
    }

    signal (SIGTSTP, SIG_DFL);
    kill (getpid (), SIGTSTP);
}


static void sigcont_handler ()
{
    background = (tcgetpgrp (0) == getppid ());

    if (background)
        reset_tty ();
    else
        set_tty ();

    if (stopped)
	xmp_timer_restart ();

    stopped = 0;
    refresh_status = 1;

    /* Unlike BSD systems, signals under Linux are reset to their
     * default behavior when raised.
     */
    signal (SIGCONT, sigcont_handler);
    signal (SIGTSTP, sigtstp_handler);
}
#endif

static void sigusr_handler (int i)
{
    signal (sigusr = i, sigusr_handler);
}


static void cleanup (int s)
{
    signal (SIGTERM, SIG_DFL);
    signal (SIGINT, SIG_DFL);
    signal (SIGQUIT, SIG_DFL);
    signal (SIGFPE, SIG_DFL);
    signal (SIGSEGV, SIG_DFL);

#ifdef XXMP
    if (pid) {
#endif
	fprintf (stderr, "\n*** Interrupted: signal %d caught\n", s);
	xmp_stop_module ();
	xmp_close_audio ();

#ifdef XXMP
        kill (pid, SIGTERM);
        waitpid (pid, NULL, 0);
    } else {
        close_window ();
    }
    xmp_detach_shared_mem (ii);
#else
    reset_tty ();
#endif

    exit (-2);
}


#ifndef XXMP
static void process_echoback (unsigned long i)
{
    unsigned long msg = i >> 4;
    unsigned char cmd;
    static int _pos = -1;
    static int tpo, bpm, nch, _tpo = -1, _bpm = -1;
    static int pos, pat;
    static int pause = 0;

    if (sigusr == SIGUSR1) {
	skip = 1;
	xmp_mod_stop ();
	if (pause)
	    pause = xmp_mod_pause ();
	sigusr = 0;
    } else if (sigusr == SIGUSR2) {
	skip = -1;
	xmp_mod_stop ();
	if (pause)
	    pause = xmp_mod_pause ();
	sigusr = 0;
    }

    if (background)
	return;

    if (opt.verbose) {
	switch (i & 0xf) {
	case XMP_ECHO_BPM:
	    _bpm = bpm;
	    _tpo = tpo;
	    bpm = msg & 0xff;
	    tpo = msg >> 8;
	    break;
	case XMP_ECHO_ORD:
	    pos = msg & 0xff;
	    pat = msg >> 8;
	    break;
	case XMP_ECHO_NCH:
	    nch = msg & 0xff;
	    if (nch > max_nch)
		max_nch = nch;
	    break;
	case XMP_ECHO_ROW:
	    rows += 1;
	    tot_nch += nch;
	    if
(refresh_status || !(msg & 0xff) || pos != _pos || bpm != _bpm || tpo != _tpo) {
	        fprintf (stderr,
"\rTempo[%02X] BPM[%02X] Pos[%02X/%02X] Pat[%02X/%02X] Row[  /  ] Chn[  /  ]\b",
		    tpo, bpm, pos, mi.len - 1, pat, mi.pat - 1);
		_pos = pos;
		if (refresh_status)
		    refresh_status = 0;
	    }
	    fprintf (stderr, "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b%02X/%02X] Chn[%02X/%02X",
		(int)(msg & 0xff), (int)(msg >> 8), nch, max_nch);
	    break;
	}
    }

    /* Interactive commands */

    if (nocmd)
	return;

    if (read (0, &cmd, 1) > 0) {
	switch (cmd) {
	case 'q':	/* quit */
	    skip = -2;
	    xmp_mod_stop ();
	    if (pause)
		pause = xmp_mod_pause ();
	    break;
	case 'f':	/* jump to next order */
	    xmp_ord_next ();
	    if (pause)
		pause = xmp_mod_pause ();
	    break;
	case 'b':	/* jump to previous order */
	    xmp_ord_prev ();
	    if (pause)
		pause = xmp_mod_pause ();
	    break;
	case 'n':	/* skip to next module */
	    skip = 1;
	    xmp_mod_stop ();
	    if (pause)
		pause = xmp_mod_pause ();
	    break;
	case 'p':	/* skip to previous module */
	    skip = -1;
	    xmp_mod_stop ();
	    if (pause)
		pause = xmp_mod_pause ();
	    break;
	case ' ':	/* pause module */
	    fprintf (stderr, "%s",  (pause = xmp_mod_pause ())
		? "] - PAUSED\b\b\b\b\b\b\b\b\b\b"
		: "]         \b\b\b\b\b\b\b\b\b\b");
	    break;
	case '1':
	case '2':
	case '3':
	case '4':
	case '5':
	case '6':
	case '7':
	case '8':
	case '9':
	    xmp_channel_mute (cmd - '1', 1, -1);
	    break;
	case '0':
	    xmp_channel_mute (9, 1, -1);
	    break;
	case '!':
	    xmp_channel_mute (0, 8, 0);
	    break;
	}
    }
}
#endif


static void shuffle (int argc, char **argv)
{
    int i, j;
    char *x;

    for (i = 1; i < argc; i++) {
	j = 1 + rand () % (argc - 1);
	x = argv[i];
	argv[i] = argv[j];
	argv[j] = x;
    }
}


int main (int argc, char **argv)
{
    int i, t, lf_flag, first, num_mod, verb = 0;
    time_t t0, t1;
    struct timeval tv;
    struct timezone tz;
#ifdef HAVE_SYS_RTPRIO_H
    struct rtprio rtp;
#endif
    int getprevious = 0, skipprev = 0;
#ifdef XXMP
    char *fn;
#endif
#ifdef __EMX__
    long rc;
#endif

    xmp_init (argc, argv, &opt);
    get_options (argc, argv, &opt);

    if (!(probeonly || argv[optind])) {
	fprintf (stderr, "%s: no modules to play\n"
		"Use `%s --help' for more information.\n", argv[0], argv[0]);
	exit (-1);
    }

    gettimeofday (&tv, &tz);
    srand (tv.tv_usec);

    if (randomize)
	shuffle (argc - optind + 1, &argv[optind - 1]);

    if (opt.outfile)
	opt.drv_id = "file";

/* #ifndef XXMP */
    if ((background = (tcgetpgrp (0) == getppid ()))) {
	verb = opt.verbose;
	opt.verbose = 0;
	i = xmp_open_audio (&opt);
	xmp_verbosity_level (opt.verbose = verb);
    } else
/* #endif */
	i = xmp_open_audio (&opt);

    if (i < 0) {
	fprintf (stderr, "%s: ", argv[0]);
	switch (i) {
	case XMP_ERR_DINIT:
	    fprintf (stderr, "can't initialize driver\n");
	    return -1;
	case XMP_ERR_NODRV:
	    fprintf (stderr, "driver does not exist\n");
	    return -2;
	case XMP_ERR_DSPEC:
	    fprintf (stderr, "driver not specified\n");
	    return -3;
	default:
	    fprintf (stderr, "unknown error\n");
	    return -128;
	}
    }

#ifdef XXMP
    xmp_register_event_callback (x11_event_callback);
#else
    xmp_register_event_callback (process_echoback);
#endif

    if (opt.verbose) {
	fprintf (stderr, "Extended Module Player %s %s\n"
	"Copyright (C) 1996-2000 Claudio Matsuoka and Hipolito Carraro Jr\n",
	    xmp_version, xmp_date);
#ifdef __EMX__
   fprintf(stderr, "OS/2 Port by Kevin Langman (langman@earthling.net)\n" );
#endif
    }

    if (probeonly || (opt.verbose)) {
	int srate, res, chn, itpt;
	
	xmp_get_driver_cfg (&srate, &res, &chn, &itpt);
	fprintf (stderr, "Using %s\n", (char*)xmp_get_driver_description ());
	if (srate) {
	    fprintf (stderr, "Mixer set to %dbit, %d Hz, %s%s\n", res, srate,
		itpt ? "interpolated " : "", chn > 1 ? "stereo" : "mono");
	}
    }

    if (probeonly)
	exit (0);

    /*
     * Support for real-time priority by Douglas Carmichael <dcarmich@mcs.com>
     */
#ifdef HAVE_SYS_RTPRIO_H
    if (rt) {
	rtp.type = RTP_PRIO_REALTIME;
	rtp.prio = 0;
	if (rtprio (RTP_SET, 0, &rtp) == -1) {
	    fprintf (stderr, "%s: Can't get realtime priority.\n", argv[0]);
	    exit(1);
	}
    }
#endif

#ifdef __EMX__
   rc = DosSetPriority( PRTYS_THREAD, PRTYC_TIMECRITICAL, PRTYD_MINIMUM, 0 );
   if( rc!= 0 ){
      printf( "Error setting Priority.. Priority defaulting to the System Default.\n" );
   }
#endif

    signal (SIGTERM, cleanup);
    signal (SIGINT, cleanup);
    signal (SIGQUIT, cleanup);
    signal (SIGFPE, cleanup);
    signal (SIGSEGV, cleanup);
#ifdef SIGTSTP
    signal (SIGTSTP, sigtstp_handler);
    signal (SIGCONT, sigcont_handler);
#endif
    signal (SIGUSR1, sigusr_handler);
    signal (SIGUSR2, sigusr_handler);

#ifdef XXMP
    if ((ii = xmp_get_shared_mem (sizeof (struct ipc_info))) <= 0) {
        fprintf (stderr, "can't map shared memory\n");
        exit (-1);
    }

    ii->wresult = 42;
#else
    set_tty ();
#endif

    time (&t0);

    for (first = optind, num_mod = lf_flag = 0; optind < argc; optind++) {
	if (getprevious) {
	    optind -= 2;
	    if (optind < first) {
		optind += 2;
	    }
	}

	if (opt.verbose && !background) {
	    if (lf_flag)
		fprintf (stderr, "\n");
	    lf_flag = fprintf (stderr, "Loading %s... (%d of %d)\n",
		argv[optind], optind - first + 1, argc - first);
	}

	if (background) {
	    verb = xmp_verbosity_level (0);
	    t = xmp_load_module (argv[optind]);
	    xmp_verbosity_level (verb);
	} else {
	    t = xmp_load_module (argv[optind]);
	}

	if (t < 0) {
	    switch (t) {
	    case -1:
		fprintf (stderr, "%s: %s: unrecognized file format\n",
		    argv[0], argv[optind]);
		getprevious = skipprev;
		continue;
	    case -2:
		fprintf (stderr, "%s: %s: possibly corrupted file\n",
		    argv[0], argv[optind]);
		getprevious = skipprev;
		continue;
	    case -3: {
		char *line;
		line = malloc (strlen (*argv) + strlen (argv[optind]) + 10);
		sprintf (line, "%s: %s", *argv, argv[optind]);
		perror (line);
		getprevious = skipprev;
		continue; }
	    }
	}

	rows = tot_nch = max_nch = getprevious = skipprev = 0;
        num_mod++;

#ifdef XXMP
	if (!(fn = strrchr (argv[optind], '/')))
	    fn = argv[optind];
	else
	    fn++;

	xmp_get_module_info (&ii->mi);
	strncpy (ii->filename, fn, 80);

	if (ii->wresult) {
	    if (!(pid = fork ())) {
		signal (SIGCONT, SIG_DFL);
		signal (SIGTSTP, SIG_DFL);
		if ((ii->wresult = create_window
			("xmp", "Xmp", RES_X, RES_Y, argc, argv)))
		    exit (-1);
		set_palette ();
		prepare_screen ();
		display_loop ();
	    }
	}
	xmp_tell_child ();  /* We have module_info */
	xmp_wait_child ();
#else
	xmp_get_module_info (&mi);
#endif

	if (loadonly)
	    goto skip_play;

	t = xmp_play_module ();

	if (opt.verbose && !background) {
	    fprintf (stderr,
"\rElapsed time   : %dmin%02ds %s                                              \n",
	    t / 60, t % 60, skip ? "(SKIPPED)" : " ");

#ifndef XXMP
	    fprintf (stderr, "Channels used  : %d/%d", max_nch, mi.chn);
	    if (max_nch)
		fprintf (stderr, ", avg %.2f (%.1f%%)\n",
			tot_nch / rows, 100.0 * tot_nch / rows / mi.chn);
	    else
		fprintf (stderr, "\n");
#endif
	}

skip_play:

	if (skip == -1) {
	    optind -= optind > first ? 2 : 1;
	    skipprev = 1;
	}

	if (skip == -2)
	    break;

	skip = 0;
    }

    time (&t1);

    if (!loadonly && opt.verbose && !background && num_mod > 1) {
	t = difftime (t1, t0);
	fprintf (stderr, "\n\t%d modules played, total time %dh%02dmin%02ds\n",
	     num_mod, t / 3600, (t % 3600) / 60, t % 60); 
    }

    xmp_close_audio ();

#ifdef XXMP
    if (pid) {
	kill (pid, SIGTERM);
	waitpid (pid, NULL, 0);
    }
    xmp_detach_shared_mem (ii);
#else
    reset_tty ();
#endif

    return 0;
}
