/*
 *  options.c - Handles commandline options and selection of next module.
 *
 *  (C) 1994 Mikael Nordqvist (d91mn@efd.lth.se, mech@df.lth.se)
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include <fcntl.h>
#include <limits.h>

#include "mod.h"

extern struct mod_info M;
extern char workdir[PATH_MAX+1];
extern struct mod_file *files;
extern int nr_visible_voices;

struct options opt, default_opt;
int nr_songs;                  /* Nr of songs specified */
short *songs;           /* Starting pos in argv for the songs */
short *song_sequence;   /* Order of the songs (if played) */

char **av;              /* Local pointer to argv */
int ac;                 /* Local copy of argc    */

char background=0, totallyQ=0;


void init_options(int argc, char *argv[])
{
    int fd;
    char *env;
    char buf[2048], *env_av[80];  /* Should be enough */
    int env_ac, env_pos, buf_pos;
    
    nr_songs=0;

    bzero((void *)&default_opt, sizeof(struct options));
    default_opt.max_lines=4711;   /* As many as possible   */
    default_opt.active_voices=-1; /* All voices active     */
    default_opt.maxtime=0;        /* No maxtime by default */

    default_opt.tempo=125;        /* PAL-tempo             */
    default_opt.speed=6;

    default_opt.speed0stop=1;     /* PT3.0 compatability         */
    default_opt.click_removal=0;  /* Clickremoval off by default */
    default_opt.low_note=0;       /* Let patternloader set these */
    default_opt.high_note=0;

    default_opt.loop_module=0;    /* No forced looping by default    */
    default_opt.break_loops=-1;   /* Set to default if not specified */
    default_opt.auto_next=-1;     /* Set to default if not specified */

    default_opt.voice_detail=0;   /* Default to not changing it */

    /* Check validity and set the "always global" options. First check
     * the environment-variable, then the commandline.
     */

    /* Check environment */
    if((env=getenv(ENV_VAR_NAME))) {
	if(env[0]) {
	    if(env[0] != '-')
		error("Environment-variable " ENV_VAR_NAME
		      " must begin with a hyphen.\n");
	    env_pos=env_ac=0;
	    strcpy(buf, argv[0]);
	    buf_pos=strlen(buf)+1;

	    /* Build the argument-array */
	    while(env[env_pos]) {
		env_av[++env_ac]=&buf[buf_pos];
		while(env[env_pos] && env[env_pos] != ' ' &&
		      env[env_pos] != '\t')
		    buf[buf_pos++]=env[env_pos++];
		buf[buf_pos++]=0;
		
		while(env[env_pos] && (env[env_pos] == ' ' ||
				       env[env_pos] == '\t'))
		    env_pos++;
	    }
	    av=env_av;
	    ac=++env_ac;
	    check_options(1); /* Parse, but abort at the first filename */
	    opt=default_opt;
	    parse_options(0);
	    default_opt=opt;
	}
    }

    /* These must allocated before check_options(0) is called */
    songs=(short *)malloc(argc*sizeof(short));
    song_sequence=(short *)malloc(argc*sizeof(short));

    /* Check commandline */
    av=argv;
    ac=argc;
    check_options(0);
    
    if(background && default_opt.verbose)
	error("Can't put a verbose session in the background.\n");
    
    if(default_opt.quiet && default_opt.verbose)
	error("Quiet and verbose can't be used together.\n");
    
    opt=default_opt;
    parse_options(0);
    default_opt=opt;
    
    /* Turn on interactive mode if nothing else is specified */
    if(!default_opt.verbose && !default_opt.quiet)
	default_opt.interactive=1;

    /* Set defaults that depend on opt.play_list */
    if(nr_songs < 1) {
	default_opt.play_list=0;
	if(default_opt.break_loops < 0)
	    default_opt.break_loops=0;
	if(default_opt.auto_next < 0)
	    default_opt.auto_next=0;
	opt=default_opt;
	init_dir(workdir);
    }
    else {
	default_opt.play_list=1;
	if(default_opt.break_loops < 0)
	    default_opt.break_loops=1;
	if(default_opt.auto_next < 0)
	    default_opt.auto_next=1;

	opt=default_opt;
	init_playsequence();
	list_to_files();
    }

    if(default_opt.loop_module)    /* Loop as composer intended */
	default_opt.break_loops=0;

    /* Rediect output if needed */
    if(totallyQ || background) {
	fd=open("/dev/null", O_RDWR, 0);
	dup2(fd, STDIN_FILENO);
	dup2(fd, STDOUT_FILENO);
	dup2(fd, STDERR_FILENO);
	close(fd);
    }

    /* Detach if needed */
    if(background) {
	switch(fork()) {
	  case -1:
	    error("fork() failed.\n");
	  case 0:
	    write_pid(getpid());
	    break;
	  default:
	    exit(0);
	}
    }
    opt=default_opt;
}


void check_options(char no_files)
{
    char next, tmp;
    char *s;
    int index=1;

    while(index < ac) {
	while(index < ac) {
	    s=av[index];
	    if(*s == '-') {
		s++;
		next=0;
		while(*s && !next) {
		    switch(*s) {
		      case 'a':
			default_opt.auto_next=1;
			break;
		      case 'A':
			default_opt.auto_next=0;
			break;
		      case 'b':
			default_opt.break_loops=1;
			break;
		      case 'B':
			default_opt.break_loops=0;
			break;
		      case 'C':
			default_opt.replay_forever=1;
			break;
		      case '?':
		      case 'h':
			print_helptext(av[0]);
			exit(0);
			break;
		      case 'L':
			default_opt.loop_module=1;
			break;
		      case 'n':
			default_opt.noscroll=1;
			break;
		      case 'Q':
			totallyQ=1;
			/* Fall through to 'q' */
		      case 'q':
			default_opt.quiet=1;
			break;
		      case 'r':
			default_opt.random_mode=1;
			break;
		      case 'v':
			default_opt.verbose++;
			break;
			
		      case 'z':
			background=1;
			default_opt.quiet=1;
			break;

		      case 'k':
			error("Option '-k' must be the only argument.\n");
			break;
			      
		      case 'M':
		      case 'N':
		      case 'o':
		      case 'O':
		      case 'P':
		      case 'T':
		      case '0':
		      case '5':
		      case '6':
			break;

		      /* Parsed numerical arguments */
		      case 'l':
			tmp=*s;
			if(!getarg(&s, &index))
			    goto horrible_goto;
			next=1;
			default_opt.max_lines=MAX(atoi(s), 1);
			break;
		      case 'x':
			tmp=*s;
			if(!getarg(&s, &index))
			    goto horrible_goto;
			next=1;
			break;

		      /* Parsed alphanumerical arguments */
		      case 'D':
			tmp=*s;
			getarg(&s, &index);
			next=1;
			if(chdir(s))
			    error("No such directory.\n");
			getcwd(workdir, PATH_MAX);
			break;

		      /* Numerical arguments */
		      case 'c':
		      case 'm':
		      case 'p':
		      case 's':
		      case 't':
			tmp=*s;
			if(!getarg(&s, &index)) {
			  horrible_goto:
			    error("%s: option -%c requires a (positive)"
				   " numerical argument\n", av[0], tmp);
			}
			next=1;
			break;

                      /* Alphanumerical arguments */
		      case 'f':
			getarg(&s, &index);
			next=1;
			break;

		      default:
			error("%s: Unknown option -- %c\n", av[0], *s);
			break;
		    }
		    s++;
		}
		index++;
	    }
	    else {
		if(no_files) /* Abort after a file is found if no_files */
		    return;

		songs[nr_songs++]=index;
		index++;
		break;
	    }
	}
    }
}


/* Parse options */

void parse_options(int index)
{
    char next;
    char *s;

    index++; /* Skip the filename */
    
    for(;;) {
	s=av[index];
	if(*s == '-') {
	    s++;
	    next=0;
	    while(*s && !next) {
		switch(*s) {

		    /* These needs no further processing */

		  case 'a': case 'A': case 'b': case 'B':
		  case 'C': case '?': case 'h': case 'L':
		  case 'n': case 'q': case 'Q': case 'r': case 'v': case 'z':
		    break;

		  case 'D':
		  case 'l':
		    getarg(&s, &index);
		    next=1;
		    break;

		    /* These take no arguments */
		    
		  case 'M':
		    opt.mono=1;
		    break;
		  case 'N':
		    opt.ntsc_samples=1;
		    break;
		  case 'o':
		    opt.low_note=BASE_NOTE+3*12;
		    opt.high_note=BASE_NOTE+6*12-1;
		    break;
		  case 'O':
		    opt.low_note=BASE_NOTE+0*12;
		    opt.high_note=BASE_NOTE+NR_OCTAVES*12-1;
		    break;
		  case 'P':
		    opt.ntsc_samples=0;
		    break;
		  case 'T':
		    opt.tolerant=1;
		    break;
		  case '0':
		    opt.speed0stop=0;
		    break;
		  case '5':
		    opt.tempo=125; /* 50 Hz (125 BPM) -> 2.000/100 s */
		    opt.nobpm=1;
		    break;
		  case '6':
		    opt.tempo=150; /* 60 Hz (150 BPM) -> 1.667/100 s */
		    opt.nobpm=1;
		    break;
		    
		    /* These take arguments */
		    
		  case 'c':
		    getarg(&s, &index);
		    next=1;
		    opt.click_removal=atoi(s);
		    break;
		  case 'f':
		    getarg(&s, &index);
		    if(!strcmp(s, "mod"))
			opt.format=MODFORMAT_MOD;
		    else if(!strcmp(s, "ult"))
			opt.format=MODFORMAT_ULT;
		    else if(!strcmp(s, "mtm"))
			opt.format=MODFORMAT_MTM;
		    else if(!strcmp(s, "s3m"))
			opt.format=MODFORMAT_S3M;
		    else
			info("Unknown format '%s' specified, "
			     "option ignored.\n", s);
		    next=1;
		    break;
		  case 'm':
		    getarg(&s, &index);
		    next=1;
		    opt.maxtime=MAX(atoi(s), 1);
		    break;
		  case 'p':
		    getarg(&s, &index);
		    next=1;
		    opt.start_pos=MAX(atoi(s), 0);
		    break;
		  case 's':
		    getarg(&s, &index);
		    next=1;
		    opt.speed=MAX(atoi(s), 1);
		    break;
		  case 't':
		    getarg(&s, &index);
		    next=1;
		    opt.tempo=MIN(MAX(atoi(s), 32), 255);
		    break;
		  case 'x':
		    getarg(&s, &index);
		    next=1;
		    opt.voice_detail=MAX(1, MIN(3, atoi(s)));
		    break;

		  default:
		    error("Internal error (unknown option).\n");
		}
		s++;
	    }
	    index++;
	}
	else
	    return; /* No more options */
    }
    error("Never reached\n");
}


/* This is rather pointless as we are about to exit() anyways... */

void cleanup_options(void)
{
    free(songs);
    free(song_sequence);
}


/* Sets *s to the start of the argument. Returns true if the first character
 * of the argument is a digit.
 */
int getarg(char **s, int *index)
{
    *s=*s+1;
    if(!**s) {
	*index=*index+1;
	if(*index >= ac)
	    error("%s: Missing argument for option "
		  "-- %c\n", av[0], *(*s-1));
	*s=av[*index];
    }
    
    return (**s >= '0' && **s <= '9');
}


/* Selects next module by parsing it's options and setting the filename */

void get_module(int nr)
{
    int idx;

    opt=default_opt;
    if(opt.play_list) {
	idx=songs[song_sequence[nr]];
	strcpy(M.real_filename, av[idx]); /* Set filename */
	parse_options(idx); 
    }
    else {
	strcpy(M.real_filename, files[nr].name);
    }
}

/* Returns the filename with path removed (sequenced order). Used to
 * print current modulename on the screen.
 */

char *get_modulename(int nr)
{
    static char buf[PATH_MAX+1];
    int i;

    if(opt.play_list)
	strcpy(buf, av[songs[song_sequence[nr]]]);
    else
	strcpy(buf, files[nr].name);

    for(i=strlen(buf); i >= 0 && buf[i] != '/'; --i)
	;
    return &buf[++i];
}

/* Returns a pointer to the full filename (real order) ONLY FOR play_list! */

char *get_fullmodulename_ptr(int nr)
{
    return av[songs[nr]];
}


void init_playsequence(void)
{
    int i, j, n;
    
    for(i=0; i < nr_songs; ++i)
	song_sequence[i]=-1;
    
    for(i=0; i < nr_songs; ++i) {
	if(default_opt.random_mode) {
	    n=rand()%(nr_songs-i);
	    for(j=0; n >= 0; ++j)
		if(song_sequence[j] < 0)
		    --n;
	    song_sequence[--j]=i;
	}
	else {
	    song_sequence[i]=i;
	}
    }
}


int fileidx_to_seqidx(int nr)
{
    int i;
    
    if(opt.play_list) { /* song_sequence not used when there is no play_list */
	for(i=0; ; ++i) {
	    if(song_sequence[i] == nr) {
		nr=i;
		break;
	    }
	}
    }
    return nr;
}


int seqidx_to_fileidx(int nr)
{
    return song_sequence[nr];
}
