/* list MIDI programs */

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

#include <stdio.h>
#ifndef __WIN32__
#include <unistd.h>
#endif
#include <stdlib.h>

#ifndef NO_STRING_H
#include <string.h>
#else
#include <strings.h>
#endif

#include "timidity.h"
#include "common.h"
#include "instrum.h"
#include "playmidi.h"
#include "readmidi.h"
#include "output.h"
#include "controls.h"
#include "arc.h"
#include "wrd.h"

ChannelBitMask drumchannel_mask;


/*ARGSUSED*/
static int null_cmsg(int type, int verbosity_level, char *fmt, ...){return 0;}
static void null_proc(){}
static ControlMode null_control_mode =
{
    "", 0,
    1,0,0,
    NULL, null_proc, NULL, NULL, null_cmsg, null_proc
};

static PlayMode null_play_mode =
{
    8000, PE_MONO, 0,
    -1,
    {0,0,0,0,0},
    "", 0,
    "",
    NULL, NULL, null_proc, NULL, NULL, null_proc, NULL, NULL
};

/*ARGSUSED*/
static int  null_wrdt_open(char *wrdt_opts) { return 0; }
/*ARGSUSED*/
static void null_wrdt_apply(int cmd, int argc, int args[]) { }
static void null_wrdt_update_events(void) { }
static void null_wrdt_end(void) { }
static void null_wrdt_close(void) { }
static WRDTracer null_wrdt_mode =
{
    "No WRD trace", '-',
    0,
    null_wrdt_open,
    null_wrdt_apply,
    null_wrdt_update_events,
    NULL,
    null_wrdt_end,
    null_wrdt_close
};

PlayMode *play_mode = &null_play_mode;
ControlMode *ctl = &null_control_mode;
WRDTracer *wrdt = &null_wrdt_mode;
ToneBank *tonebank[128], *drumset[128];

int32 tonebank_start_time[128][128];
int32 drumset_start_time[128][128];
int tonebank_counter[128][128];
int drumset_counter[128][128];

int default_program[MAX_CHANNELS];
ChannelBitMask drumchannels;
ChannelBitMask default_drumchannel_mask;
ChannelBitMask default_drumchannels;

int special_tonebank = -1;
int default_tonebank = 0;
int progbase = 0;

void init_banks(void);
int list_midi_file(char *fn);
void list_instruments(void);

extern struct URL_module URL_module_file;
extern struct URL_module URL_module_dir;
#ifdef SUPPORT_SOCKET
extern struct URL_module URL_module_http;
extern struct URL_module URL_module_ftp;
extern struct URL_module URL_module_news;
extern struct URL_module URL_module_newsgroup;
#endif /* SUPPORT_SOCKET */

/*ARGSUSED*/
int midi_drumpart_change(int ch, int isdrum) { return 0; }
/*ARGSUSED*/
int get_module_type(char *fn) { return IS_OTHER_FILE; }
/*ARGSUSED*/
int load_module_file(struct timidity_file *tf, int mtype) { return 1; }
/*ARGSUSED*/
int import_wrd_file(char *fn) { return 0; }
/*ARGSUSED*/
int instrument_map(int mapID, int *set_in_out, int *elem_in_out) { return 0; }

int main(int argc, char **argv)
{
    int i;
    int nfiles;
    char **files;
    static int drums[] = DEFAULT_DRUMCHANNELS;
    extern ArchiveFileList *archive_file_list;

    memset(&drumchannels, 0, sizeof(ChannelBitMask));
    for(i = 0; drums[i] > 0; i++)
	SET_CHANNELMASK(drumchannels, drums[i] - 1);
    for(i = 0; i < MAX_CHANNELS; i++)
	default_program[i] = DEFAULT_PROGRAM;

    url_add_modules(
	&URL_module_file,
	&URL_module_dir,
#ifdef SUPPORT_SOCKET
	&URL_module_http,
	&URL_module_ftp,
	&URL_module_news,
	&URL_module_newsgroup,
#endif /* SUPPORT_SOCKET */
	NULL);

    if(argc == 1)
    {
	static char *v[2];
	argc = 2;
	v[0] = argv[0];
	v[1] = "-";
	argv = v;
    }

    nfiles = argc - 1;
    files  = argv + 1;
    program_name = argv[0];

    for(i = 0; i < 128; i++)
    {
	tonebank[i] = (ToneBank *)safe_malloc(sizeof(ToneBank));
	drumset[i] = (ToneBank *)safe_malloc(sizeof(ToneBank));
    }

    files = expand_file_archives(files, &nfiles);
    for(i = 0; i < nfiles; i++)
    {
	init_banks();
	printf("%s: " NLS, files[i]);
	list_midi_file(files[i]);
    }
    close_archive_files(archive_file_list);
    return 0;
}

void init_banks(void)
{
    int i, j;

    for(i = 0; i < 128; i++)
    {
	memset(tonebank[i], 0, sizeof(ToneBank));
	memset(drumset[i], 0, sizeof(ToneBank));

	for(j = 0; j < 128; j++)
	{
	    tonebank_start_time[i][j] = -1;
	    drumset_start_time[i][j] = -1;
	}
    }
    memset(tonebank_counter, 0, sizeof(tonebank_counter));
    memset(drumset_counter, 0, sizeof(drumset_counter));
}

void scan_midievent(MidiEvent *ev)
{
    Channel channel[MAX_CHANNELS];
    int ch;

    memset(channel, 0, sizeof(channel));
    while(ev->type != ME_EOT)
    {
	ch = ev->channel;
	switch(ev->type)
	{
	  case ME_NOTEON:
	    if(ev->b)
	    {
		/* Note on */
		int bank;
		int inst;

		bank = channel[ch].bank;
		if(ISDRUMCHANNEL(ch))
		{
		    inst = ev->a;
		    if(drumset_start_time[bank][inst] == -1)
			drumset_start_time[bank][inst] = ev->time;
		    drumset_counter[bank][inst]++;
		}
		else
		{
		    inst = channel[ch].program;
		    if(tonebank_start_time[bank][inst] == -1)
			tonebank_start_time[bank][inst] = ev->time;
		    tonebank_counter[bank][inst]++;
		}
	    }
	    break;
	  case ME_PROGRAM:
	    if(ISDRUMCHANNEL(ch))
		channel[ch].bank = ev->a;
	    else
	    {
		if(current_file_info && current_file_info->mid == 0x43) /* XG */
		    channel[ch].bank = channel[ch].bank_lsb;
		else
		    channel[ch].bank = channel[ch].bank_msb;
		channel[ch].program = ev->a;
	    }
	    break;
	  case ME_TONE_BANK_LSB:
	    channel[ch].bank_lsb = ev->a;
	    break;
	  case ME_TONE_BANK_MSB:
	    channel[ch].bank_msb = ev->a;
	    break;
	  case ME_RESET:
	    memset(channel, 0, sizeof(channel));
	    break;
	}
	ev++;
    }
}

int list_midi_file(char *fn)
{
    MidiEvent *event;
    int32 events, samples;
    struct timidity_file *tf;

    ctl->cmsg(CMSG_INFO, VERB_VERBOSE, "MIDI file: %s", fn);

    if(!(tf=open_midi_file(fn, 1, OF_VERBOSE)))
    {
	perror(fn);
	return RC_ERROR;
    }

    event = read_midi_file(tf, &events, &samples, fn);

    close_file(tf);

    if (!event)
	return RC_ERROR;

    scan_midievent(event);

    list_instruments();
    free(event);
    return 0;
}

char *time_str(int t)
{
    static char buff[32];

    t = (int)((double)t / (double)play_mode->rate + 0.5);
    sprintf(buff, "%d:%02d", t / 60, t % 60);
    return buff;
}

void list_instruments(void)
{
    int i, j;

    for(i = 0; i < 128; i++)
	for(j = 0; j < 128; j++)
	{
	    if(tonebank[i]->tone[j].instrument == MAGIC_LOAD_INSTRUMENT)
	    {
		printf("Tonebank %d %d", i, j);
		if(tonebank_start_time[i][j] != -1)
		{
		    printf(" (start at %s, %d times note on)",
			   time_str(tonebank_start_time[i][j]),
			   tonebank_counter[i][j]);
		}
		fputs(NLS, stdout);
	    }
	}
    for(i = 0; i < 128; i++)
	for(j = 0; j < 128; j++)
	{
	    if(drumset[i]->tone[j].instrument == MAGIC_LOAD_INSTRUMENT)
	    {
		printf("Drumset %d %d", i, j);
		if(drumset_start_time[i][j] != -1)
		    printf(" (start at %s, %d times note on)",
			   time_str(drumset_start_time[i][j]),
			   drumset_counter[i][j]);
		fputs(NLS, stdout);
	    }
	}
}
