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

#include "mod.h"

/*
 *  The code loading modules assumes sizeof(unsigned short) == 2, which
 *  should be no problem :-)
 */ 

/* External global variables */

SEQ_DECLAREBUF();
extern int seqfd, gus_dev;

extern struct mod_info M;
extern struct options opt;

extern int periodtable[12*5*16];
extern char *effectnames[NR_EFX];
extern char effect_used[NR_EFX];

/* Variables used while loading module */

static int expected_length, OS15;

int load_module(char *modname)
{
    unsigned char buf[MOD31_OFFSET_PATTERNDATA];
    int fd;
    int i, diff, fatalerr;

    for(i=0; i < MAX_VOICES; ++i)
	M.voice_pattern[i]=0;

    if((fd=open(modname, O_RDONLY, 0)) == -1) {
	printf("Unable to open file '%s'.\n",modname);
	return 0;
    }

    /* The following might fail if the total length of a 15 instrument
     * module is less than MOD31_OFFSET_PATTERNDATA, but that is very
     * unlikely.
     */
    if(read(fd, buf, MOD31_OFFSET_PATTERNDATA) == -1) {
	printf("Unable to read header from file.\n");
	close(fd);
	return 0;
    }

    process_header(buf);

    /* As a 15 instrument modules has no tag, a further -4 offset
     * is needed in that case.
     */
    if(lseek(fd, MOD31_OFFSET_PATTERNDATA+OS15+(OS15 ? -4 : 0), SEEK_SET)
       == -1) {
	printf("Unable to read patterndata.\n");
	close(fd);
	return 0;
    }

    if(!read_patterndata(fd)) {
	printf("Unable to read patterndata.\n");
	close(fd);
	return 0;
    }

    ioctl(seqfd, SNDCTL_SEQ_RESETSAMPLES, &gus_dev);

    if(opt.verbose >=2 ) {
	printf("\nUploading instruments to GUS... ");
	fflush(stdout);
    }

    if(opt.verbose >=4) {
	printf("\n");
    }
    
    fatalerr=0;
    for(i=1; i <= M.nr_samples; ++i) {
	if(M.sample[i].length > 4) { /* Only load non-silent samples */
	    if(!read_and_upload_sample(fd, i)) {
		fatalerr=1;
		break;
	    }
	    M.sample[i].valid=1;
	}
	else { /* Skip sample */
	    if(lseek(fd, M.sample[i].length, SEEK_CUR) == -1){
		fatalerr=1;
		break;
	    }
	}
    }
    
    if(fatalerr) {
	printf("\nShort file (%d bytes missing).\n",
	       expected_length-lseek(fd, 0, SEEK_CUR));
	close(fd);
	return 0;
    }
    
    /* Check if filelength was as expected. This is done with two 
     * statements to make sure we don't move the filepointer to the 
     * end before checking current position.
     */

    diff=-lseek(fd, 0, SEEK_CUR);
    diff+=lseek(fd, 0, SEEK_END);

    if(diff && opt.verbose)
	printf("\nFound %d garbagebytes after samples. Ignoring.\n", diff);

    close(fd);

    return 1;
}


void process_header(char *buf)
{
    char tmpch, *p, outbuf[128];
    int i, tmp;

    M.nr_voices=4;     /* Default to 4-channel 31 sample module */
    M.nr_samples=31;
    OS15=0;

    if(!strncmp((char *)&buf[MOD31_OFFSET_MAGIC], "M.K.", 4)) {
	sprintf(outbuf, "(M.K.) module.\n");
    }
    else if(!strncmp((char *)&buf[MOD31_OFFSET_MAGIC], "FLT4", 4)) {
	sprintf(outbuf, "(FLT4) module.\n");
    }
    else if(!strncmp((char *)&buf[MOD31_OFFSET_MAGIC], "4CHN", 4)) {
	sprintf(outbuf, "(4CHN) module.\n");
    }
    else if(!strncmp((char *)&buf[MOD31_OFFSET_MAGIC], "8CHN", 4)) {
	sprintf(outbuf, "(8CHN) module.\n");
	M.nr_voices=8;
    }
    else {
	sprintf(outbuf, "No known tag found. Trying old 15 instrument "
		"module.\n");
	M.nr_samples=15;
	OS15=-16*30;        /* Offset-modifier for 15 instrument module */
    }
    
    if(opt.verbose >= 3)
	printf("%s", outbuf);
    
    strncpy(M.name, &buf[MOD31_OFFSET_NAME], 20);
    M.name[20]=0;
    fix_string(M.name);
    M.songlength=buf[MOD31_OFFSET_SONGLENGTH+OS15];
    M.restartpos=buf[MOD31_OFFSET_RESTARTPOS+OS15];

    memcpy(M.patterntable, &buf[MOD31_OFFSET_PATTERNTABLE+OS15], 128);

    expected_length=0; /* Start counting expected filesize */

    p=&buf[MOD31_OFFSET_SAMPLEINFO];
    if(opt.verbose)
	printf(
"\n   #  Samplename               Size Finetune Loopstart Loopend Volume  #\n"
"--------------------------------------------------------------------------\n");

    for(i=1; i <= M.nr_samples; ++i) {
	M.sample[i].valid=0;              /* Valid isn't set to TRUE until it's
					   * sampledata has been read.
					   */
	strncpy(M.sample[i].name, p, 22);
	M.sample[i].name[22]=0;
	p+=22;
	fix_string(M.sample[i].name);

	M.sample[i].length=BE2LE(*(unsigned short *)p++)*2;

	tmpch=(*p++&0x0f);
	M.sample[i].finetune=(tmpch > 7 ? tmpch|0xf0 : tmpch);

	M.sample[i].volume=*(unsigned char *)p++;

	tmp=BE2LE(*(unsigned short *)p++)*2;
	if(tmp < 0 || tmp >=M.sample[i].length)
	    tmp=0;
	M.sample[i].repeat_start=tmp;
	
	/* -1 as the sample at repeat_end is played */
	tmp+=BE2LE(*(unsigned short *)p++)*2-1;
	
	if(tmp < M.sample[i].repeat_start+2)
	    tmp=M.sample[i].repeat_start+1; /* turn off looping */
	else if(tmp >= M.sample[i].length)
	    tmp=M.sample[i].length-1;       /* truncate loop-end */

	M.sample[i].repeat_end=tmp;

	expected_length+=M.sample[i].length;
	
	if((opt.verbose && M.sample[i].length) || (opt.verbose >= 2)) {
	    printf("%c %2d  %-22s  %5d    %2d      %5d    %5d   %3d   %2d %c\n",
		   (M.sample[i].length ? ' ' : '*'),
		   i,
		   M.sample[i].name,
		   M.sample[i].length,
		   M.sample[i].finetune,
		   M.sample[i].repeat_start,
		   M.sample[i].repeat_end,
		   M.sample[i].volume,
		   i,
		   (M.sample[i].length ? ' ' : '*'));
	}
    }
    
    p=&buf[MOD31_OFFSET_PATTERNTABLE+OS15];
    for(i=0, tmp=0; i <= 127; ++i) {
	M.patterntable[i]=p[i];
	if(p[i] > tmp)
	    tmp=p[i];
    }
    M.nr_patterns=++tmp;

    if(opt.verbose) {
	printf("\nPatterns: %3d Songlength: %3d",
	       M.nr_patterns,M.songlength);

	if(opt.verbose >= 2)
	    printf(" Restartpos: %3d\n", M.restartpos);
	else
	    printf("\n");
    }
    
    expected_length+=MOD31_OFFSET_PATTERNDATA+OS15+(OS15 ? -4 : 0)+
	M.nr_patterns*1024;
}


int read_patterndata(int fd)
{
    unsigned char buf[64*4*4];
    unsigned char tmp;
    unsigned int period;
    int p, l, v, pos, note, hinote, lonote;
    
    hinote=0;
    lonote=255;
    
    for(v=0; v < NR_EFX; ++v)
	effect_used[v]=0;

    for(v=0; v < M.nr_voices; ++v)
	M.voice_pattern[v]=malloc(sizeof(struct patternvoice)*M.nr_patterns);

    for(p=0; p < M.nr_patterns; ++p) {
	if(read(fd, buf, 1024) != 1024)
	    return 0;
	pos=0;
	for(l=0; l < 64; ++l) {
	    for(v=0; v < M.nr_voices; ++v) {
		M.voice_pattern[v][p].line[l].sample=
		    (buf[pos]&0xf0)|((buf[pos+2]>>4)&0x0f);
		tmp=buf[pos+2]&0x0f;
		if(tmp != 0x0e) {
		    M.voice_pattern[v][p].line[l].arg=buf[pos+3];
		}
		else {
		    tmp=0x10|((buf[pos+3]>>4)&0x0f);
		    M.voice_pattern[v][p].line[l].arg=buf[pos+3]&0x0f;
		}
		M.voice_pattern[v][p].line[l].effect=tmp;
		
		if(tmp || M.voice_pattern[v][p].line[l].arg)
		    effect_used[tmp]=1;
		
		period=((buf[pos]&0x0f)<<8)|buf[pos+1];
		M.voice_pattern[v][p].line[l].note=note=
		    (period ? period2note(period) : 0);

		if(note) {
		    hinote=MAX(hinote, note);
		    lonote=MIN(lonote, note);
		}
		pos+=4;
	    }
	}
    }

    if(!opt.low_note) { /* Determine if we should allow octave 0 and 4 */
	if(lonote < BASE_NOTE+1*12 || hinote > BASE_NOTE+4*12-1) {
	    opt.low_note=BASE_NOTE+0*12;
	    opt.high_note=BASE_NOTE+5*12-1;
	}
	else {
	    opt.low_note=BASE_NOTE+1*12;
	    opt.high_note=BASE_NOTE+4*12-1;
	}
    }
    else { /* Warn user if he made an invalid choice */
	if(opt.verbose && (hinote > opt.high_note || lonote < opt.low_note))
	    printf("Warning: Module contains notes in other octaves than"
		   " the allowed.\n         It might be played"
		   " incorrectly.\n");
    }
    
    l=0;
    if(opt.verbose >= 2) {
	printf("\nEFX: ");
	for(v=0; v < NR_EFX; ++v)
	    if(effect_used[v]) {
		printf("%s ",effectnames[v]);
		++l;
	    }
	if(opt.verbose >= 3)
	    printf("\n(# of EFX: %d)",l);
	printf("\n");
    }
    return 1;
}

/*  Samples are read from the module and uploaded to the GUS according to
 *  the information in M.sample[i]. They might be modified before uploaded
 *  to the GUS (click removal).
 */

int read_and_upload_sample(int fd, int i)
{
    int t, delta, base, skip, span;
    char looped;
    struct patch_info *p;

    /* +1 for click-removal */
    p=(struct patch_info *)malloc(sizeof(struct patch_info)+
				  M.sample[i].length+1);
    p->key=GUS_PATCH;
    p->device_no=gus_dev;
    p->instr_no=i;
    p->len=M.sample[i].length;

    /* PAL = 8287 Hz ; NTSC = 8363 Hz */
    p->base_freq=(opt.ntsc_samples ? 8363 : 8287);

    p->base_note=261632;            /* C-2 */
    p->high_note=INT_MAX;
    p->low_note=0;
    p->panning=0;
    p->detuning=0;
    p->volume=64;           /* M.sample[i].volume; */

    if(M.sample[i].repeat_end > M.sample[i].repeat_start+1)
	looped=1;
    else
	looped=0;
    
    /* Read first 4 bytes and try to determine if we should skip the first
     * 0, 2 or 4 bytes (amiga modules have a couple of 0-bytes at the start
     * of samples to handle looping; 4 on older modules and 2 on newer ones).
     * This will shorten samples that start with zeroes, but 1-4 samples
     * short won't make any difference on most sounds.
     * We never skip zeroes that are included in loops though (this is a
     * special protection for looped samples that often are looped and
     * sound very strange if the looptime is shortened).
     */
    if(read(fd, p->data, 4) == 4) {
	skip=0;
	if(!p->data[0] && !p->data[1]) {
	    skip=2;
	    if(!p->data[2] && !p->data[3])
		skip=4;
	}
	
	if(looped)
	    skip=MIN(skip, M.sample[i].repeat_start);
	
	lseek(fd, skip-4, SEEK_CUR);
	p->len-=skip;
	M.sample[i].length=p->len;
	M.sample[i].repeat_start=MAX(0, M.sample[i].repeat_start-skip);
	M.sample[i].repeat_end=MAX(0, M.sample[i].repeat_end-skip);
    }
    else
	return 0;  /* Sample too short */
    
    if(opt.verbose >= 4) {
	printf("%-22s: ", M.sample[i].name);
	for(t=0; t < skip; ++t)
	    printf("%4d ", (int)p->data[t]);
    }
    
    if(looped) {
	p->loop_start=M.sample[i].repeat_start;
	p->loop_end=M.sample[i].repeat_end;
	p->mode=WAVE_LOOPING;
    }
    else {
	p->mode=0;
    }
    
    /* Some modules seems to be 4 or 8 bytes short. Probably a
     * moduleripper that didn't do it's job too well.
     * Quick'n dirty solution: allow the last sample to be a bit short.
     */
    if((t=read(fd, p->data, p->len)) != p->len) {
	if(p->len-t <= 8) {
	    if(opt.verbose)
		printf("(Sample %d was %d bytes short) ", i, p->len-t);
	    p->len=t;
	    M.sample[i].length=t;
	    if(M.sample[i].repeat_end >=t) {
		M.sample[i].repeat_end=t-1;
		p->loop_end=t-1;
	    }
	}
	else
	    return 0;
    }
    
    if(opt.verbose >= 4) {
	for(t=0; t < 10-skip; ++t)
	    printf("%4d ", (int)p->data[t]);
	printf("(%d)\n", skip);
    }

    /* Modify the sample after loop_end so the GF1 on the GUS
     * interpolates correctly. Add 1 to patchlength if needed.
     */
    if(looped) {
	p->data[p->loop_end+1]=p->data[p->loop_start];
	if(p->loop_end+1 == p->len)
	    p->len++;
    }
    
    /* Attempt to minimize clicking/popping on looped samples by
     * connecting samples at the loopingpoint with a straight line.
     * Samples outside the loop will never be modified.
     */
    if(opt.click_removal && looped) {
	span=opt.click_removal+1;
	if(p->loop_end+1-span < p->loop_start)
	    span=p->loop_end-p->loop_start+1;
	base=p->data[p->loop_end+1-span];
	delta=p->data[p->loop_end+1]-base;
	for(t=1; t<span; ++t)
	    p->data[p->loop_end+1-span+t]=(char)(base+(delta*t)/span);
    }
    
    SEQ_WRPATCH(p, sizeof(struct patch_info)+p->len);
    
    free(p);
    return 1;
}


/* 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]) {
	if(opt.verbose >= 2)
	    printf("Forced to take C-0. PerDiff: %d ", period-periodtable[0]);
	return BASE_NOTE;
    }
    
    if(period < periodtable[5*12-1]) {
	if(opt.verbose >= 2)
	    printf("Forced to take B-4. PerDiff: %d ",
		   period-periodtable[5*12-1]);
	return BASE_NOTE+5*12-1;
    }
    
    for(i=0; i < 5*12; ++i)
	if(period >= periodtable[i])
	    return BASE_NOTE+i;
    
    printf("If you are reading this, something is very wrong...\n");
    return BASE_NOTE+5*12-1;  /* Return B-4 */
}


void fix_string(char *s)
{
    while(*s) {
	*s=(isprint(*s) ? *s : '?');
	s++;
    }
}


void free_module(void)
{
    int i;
    
    for(i=0; i < MAX_VOICES; ++i)
	if(M.voice_pattern[i])
	    free(M.voice_pattern[i]);
}
