/*
 *  load.c - Handles uncompressing and calls loaders for different formats.
 *
 *  (C) 1994 Mikael Nordqvist (d91mn@efd.lth.se, mech@df.lth.se)
 */

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/soundcard.h>
#include <sys/ultrasound.h>
#include <limits.h>
#include <string.h>
#include <ctype.h>
#include <assert.h>

#include "mod.h"

/* External global variables */

SEQ_DECLAREBUF();
extern int seqfd, gus_dev;

extern struct mod_info M;
extern struct options opt;

extern int periodtable[NR_OCTAVES*12];
extern char effect_used[NR_EFX], *effectnames[NR_EFX], workdir[PATH_MAX+1];

/* Constants */

#define COMPRESSED_GZIP 1
#define COMPRESSED_LHA  2
#define COMPRESSED_ZIP  3
#define COMPRESSED_ARJ  4

/* Loads a module and returns nonzero if M is valid after loading
 * Directory may or may not be equal to workdir upon exit.
 */

int load_module(void)
{
    int fd, i, tmp, suffix, filetype;
    char buf[PATH_MAX+1]; /* Should actually be a little long but... */
    
    suffix=filetype=0;
    
    /* Set M.filename to modname w/o path */
    tmp=strlen(M.real_filename);
    for(i=tmp-1; i >= 0 && M.real_filename[i] != '/'; )
	--i;
    
    strcpy(M.filename, &M.real_filename[++i]);
    tmp=strlen(M.filename);
    
    if(tmp > 2) {
	if(!strcmp(M.filename+tmp-2, ".z") ||
	   !strcmp(M.filename+tmp-2, ".Z")) {
	    filetype=COMPRESSED_GZIP;
	    suffix=2;
	}
    }
    if(tmp > 3) {
	if(!strcmp(M.filename+tmp-3, ".gz")) {
	    filetype=COMPRESSED_GZIP;
	    suffix=3;
	}
    }
    if(tmp > 4) {
	if(!strcmp(M.filename+tmp-4, ".lha") ||
	   !strcmp(M.filename+tmp-4, ".lzh")) {
	    filetype=COMPRESSED_LHA;
	    suffix=4;
	}
	else if(!strcmp(M.filename+tmp-4, ".zip")) {
	    filetype=COMPRESSED_ZIP;
	    suffix=4;
	}
	else if(!strcmp(M.filename+tmp-4, ".arj")) {
	    filetype=COMPRESSED_ARJ;
	    suffix=4;
	}
    }
    
    M.filename[tmp-suffix]=0; /* Remove suffix */
    
    /* Make sure the file exists by opening and closing the file */
    if(!(fd=open(M.real_filename, O_RDONLY, 0))) {
	print_status("File does not exist");
	info("Unable to open file '%s'.\n", M.real_filename);
	sleep(1);
	return 0;
    }
    close(fd);

    /* Print status if we are uncompressing */
    switch(filetype) {
      case COMPRESSED_GZIP:
      case COMPRESSED_LHA:
      case COMPRESSED_ZIP:
      case COMPRESSED_ARJ:
	print_status("Uncompressing module");
	info("Uncompressing module... ");
	break;
      default:
    }

    /* Prepare uncompress command */
    switch(filetype) {
      case COMPRESSED_GZIP:
	sprintf(buf, GZIP_COMMAND "%s > " TMP_DIR "/%s 2>/dev/null "
		"< /dev/null",
		escape_name(M.real_filename, 0), escape_name(M.filename, 1));
	break;
      case COMPRESSED_LHA:
	guess_lha_filename();
	chdir(TMP_DIR);
	sprintf(buf, LHA_COMMAND "%s/%s %s >& /dev/null < /dev/null",
		workdir, escape_name(M.real_filename, 0),
		escape_name(M.filename, 1));
	break;
      case COMPRESSED_ZIP:
	guess_zip_filename();
	chdir(TMP_DIR);
	sprintf(buf, ZIP_COMMAND "%s/%s %s >& /dev/null < /dev/null",
		workdir, escape_name(M.real_filename, 0),
		escape_name(M.filename, 1));
	break;
      case COMPRESSED_ARJ:
	guess_arj_filename();
	chdir(TMP_DIR);
	sprintf(buf, ARJ_COMMAND "%s/%s >& /dev/null < /dev/null",
		workdir, escape_name(M.real_filename, 0));
	break;
      default:
    }

    /* Uncompress and open file */
    switch(filetype) {
      case COMPRESSED_GZIP:
      case COMPRESSED_LHA:
      case COMPRESSED_ZIP:
      case COMPRESSED_ARJ:
	tmp=my_system(buf);
	sprintf(buf, TMP_DIR "/%s", M.filename);
	if(tmp) {
	    unlink(buf);
	    if(tmp > 0) {
		print_status("Error uncompressing");
		sleep(1);
	    }
	    return 0;
	}
	
	/* Open uncompressed module */
	if((fd=open(buf, O_RDONLY, 0)) == -1) {
	    info("Unable to open file '%s'.\n", buf);
	    return 0;
	}
	break;
      default: /* Assume non-compressed module */
	if((fd=open(M.real_filename, O_RDONLY, 0)) == -1) {
	    print_status("File does not exist");
	    info("Unable to open file '%s'.\n", M.real_filename);
	    sleep(1);
	    return 0;
	}
    }

    /* Here and forward buf should contain the name of the temp-file */
    chmod(buf, 0777); /* a+rwx */
    
    /* Try to determine module-format from the filename if needed */
    if(!opt.format) {
	if(strlen(M.filename) > 4) {
	    if(is_modtype("mod") || is_modtype("nst"))
		opt.format=MODFORMAT_MOD;
	    else if(is_modtype("ult"))
		opt.format=MODFORMAT_ULT;
	    else if(is_modtype("mtm"))
		opt.format=MODFORMAT_MTM;
	    else if(is_modtype("s3m"))
		opt.format=MODFORMAT_S3M;
	}
	if(!opt.format)
	    opt.format=MODFORMAT_MOD; /* Default to MOD-format */
    }
    
    M.format=opt.format;
    M.sample=0;
    
    switch(opt.format) {
      case MODFORMAT_MOD:
	tmp=load_mod(fd); /* MOD */
	break;
      case MODFORMAT_ULT:
	tmp=load_ult(fd); /* ULT */
	break;
      case MODFORMAT_MTM:
	tmp=load_mtm(fd); /* MTM */
	break;
      case MODFORMAT_S3M:
	tmp=load_s3m(fd); /* S3M */
	break;
      default:
	error("Error selecting format.\n");
    }
    close(fd);
    
    if(M.nr_samples && M.sample)
	M.sample[0].valid=0;  /* Make sure sample 0 is invalid */
    
    if(filetype) { /* Remove temp-file if module was compressed */
	unlink(buf);
    }

    info("Tracks stored: %d/%d.\n", M.nr_tracks, M.nr_voices*M.nr_patterns);

    return tmp;      /* Return status from load_? */
}


int is_modtype(char *type)
{
    char buf[5], name[256];
    int i;
    
    strcpy(name, M.filename);
    for(i=0; name[i]; ++i)
	if(isalpha(name[i]) && isupper(name[i]))
	    name[i]=tolower(name[i]);

    i=strlen(name)-4;
    
    buf[0]='.';
    strcpy(buf+1, type);
    if(!strncmp(name+i, buf, 4))        /* .xxx */
	return 1;
    buf[0]='_';
    if(!strncmp(name+i, buf, 4))        /* _xxx */
	return 1;

    strcpy(buf, type);
    strcat(buf, ".");
    if(!strncmp(name, buf, 4))          /* xxx. */
	return 1;
    buf[3]='_';
    if(!strncmp(name, buf, 4))          /* xxx_ */
	return 1;
    
    return 0;
}


void guess_lha_filename(void)
{
    char buf[PATH_MAX+1], name[PATH_MAX+1];
    int size;
    FILE *fp;
    
    sprintf(buf, LHA_COMMAND_LIST "%s/%s < /dev/null",
	    workdir, M.real_filename);
    if(!(fp=popen(buf, "rb")))
	return;

    strcpy(name, M.filename); /* Default to name minus suffix */
    size=0;
    
    /* Find the largest file */
    if(fgets(buf, PATH_MAX, fp) && fgets(buf, PATH_MAX, fp)) {
	while(!feof(fp)) {
	    if(!fgets(buf, PATH_MAX, fp))
		break;
	    if(strlen(buf) <= 1 || *buf == '-')
		break;
	    
	    buf[strlen(buf)-1]=0; /* Remove lf */
	    if(atoi(&buf[18]) > size) {
		size=atoi(&buf[18]);
		strcpy(name, &buf[46]);
	    }
	}
    }
    pclose(fp);
    strcpy(M.filename, name);
}


void guess_zip_filename(void)
{
    char buf[PATH_MAX+1], name[PATH_MAX+1];
    int size;
    FILE *fp;
    
    sprintf(buf, ZIP_COMMAND_LIST "%s/%s 2> /dev/null < /dev/null",
	    workdir, M.real_filename);
    if(!(fp=popen(buf, "rb")))
	return;
    
    strcpy(name, M.filename); /* Default to name minus suffix */
    size=0;
    
    /* Find the largest file */
    if(fgets(buf, PATH_MAX, fp) && fgets(buf, PATH_MAX, fp)) {
	while(!feof(fp)) {
	    if(!fgets(buf, PATH_MAX, fp))
		break;
	    if(strlen(buf) <= 3 || !strncmp(buf, " -", 2))
		break;
	    
	    buf[strlen(buf)-1]=0; /* Remove lf */
	    if(atoi(&buf[0]) > size) {
		size=atoi(&buf[0]);
		strcpy(name, &buf[27]);
	    }
	}
    }
    pclose(fp);
    strcpy(M.filename, name);
}


void guess_arj_filename(void)
{
    char buf[PATH_MAX+1], name[PATH_MAX+1];
    int size, i;
    FILE *fp;
    
    sprintf(buf, ARJ_COMMAND_LIST "%s/%s 2> /dev/null < /dev/null",
	    workdir, M.real_filename);
    if(!(fp=popen(buf, "rb")))
	return;
    
    strcpy(name, M.filename); /* Default to name minus suffix */
    size=0;
    /* Find the largest file */
    if(fgets(buf, PATH_MAX, fp) && fgets(buf, PATH_MAX, fp) &&
       fgets(buf, PATH_MAX, fp) && fgets(buf, PATH_MAX, fp) &&
       fgets(buf, PATH_MAX, fp) && fgets(buf, PATH_MAX, fp)) {
	while(!feof(fp)) {
	    if(!fgets(buf, PATH_MAX, fp))
		break;
	    if(strlen(buf) <= 14 || !strncmp(buf, "------------ -", 14))
		break;
	    
	    buf[strlen(buf)-1]=0; /* Remove lf */
	    if(atoi(&buf[13]) > size) {
		size=atoi(&buf[13]);
		
		/* Fix filename, dos-way (8.3) + lowercase for unix unarj */
		strncpy(name, &buf[0], 12);
		for(i=11; i >= 0 && name[i] == ' '; --i)
		    ;
		name[++i]=0;
		for(i=0; name[i]; ++i)
		    if(isalpha(name[i]) && isupper(name[i]))
			name[i]=tolower(name[i]);
	    }
	}
    }
    pclose(fp);
    strcpy(M.filename, name);
}


void print_used_effects(void)
{
    int v, l;

    if(!opt.verbose)
	return;

    printf("\nEFX: ");
    l=0;
    for(v=0; v < NR_EFX; ++v)
	if(effect_used[v]) {
	    printf("%s ",effectnames[v]);
	    ++l;
	}
    printf("\n(# of EFX: %d)\n",l);
}


/* Returns the note corresponding to the supplied period. Not the closest,
 * but the same that PT finds.
 */

unsigned char period2note(unsigned int period)
{
    int i;

    if(period > periodtable[0]) {
	info("Forced to take C-0. PerDiff: %d ", period-periodtable[0]);
	return BASE_NOTE;
    }
    
    if(period < periodtable[NR_OCTAVES*12-1]) {
	info("Forced to take B-9. PerDiff: %d ",
	     period-periodtable[NR_OCTAVES*12-1]);
	return BASE_NOTE+NR_OCTAVES*12-1;
    }
    
    for(i=0; i < NR_OCTAVES*12; ++i)
	if(period >= periodtable[i])
	    return BASE_NOTE+i;
    
    assert(0); /* never reached */
    return 0;  /* removes warning */
}


void free_module(void)
{
    int i;
    
    for(i=0; i < MAX_VOICES; ++i)
	if(M.track_idx[i])
	    free(M.track_idx[i]);
    
    if(M.tracks) {
	for(i=0; i < M.nr_tracks; ++i)
	    free(M.tracks[i]);
	free(M.tracks);
    }
    
    if(opt.format == MODFORMAT_ULT && M.songtext)
	free(M.songtext);
    
    if(M.sample)
	free(M.sample);
    
    zero_resources();
}


void zero_resources(void)
{
    int v;
    
    for(v=0; v < MAX_VOICES; ++v)
	M.track_idx[v]=0;
    
    M.tracks=0;
    M.sample=0;
    M.songtext=0;
}
