/* Extended Module Player
 * Copyright (C) 1996-2025 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 the COPYING
 * file for more information.
 */

#include <stdio.h>
#include <string.h>
#include <stdarg.h>

#include "common.h"

static int max_channels = -1;

void info_help(void)
{
	report(
"COMMAND KEYS SUMMARY\n"
"     Space      Pause/unpause\n"
"    q, Esc      Stop module and quit the player\n"
"    f, Right    Advance to next order\n"
"    b, Left     Return to previous order\n"
"    n, Up       Advance to next module\n"
"    p, Down     Return to previous module\n"
"    h, ?        Display available commands\n"
"    1 - 0       Mute/unmute channels\n"
"      !         Unmute all channels\n"
"      X         Display current mixer type\n"
"      a         Enable amiga mixer\n"
"      Z         Display current sequence\n"
"      z         Toggle subsong explorer mode\n"
"      l         Toggle loop mode\n"
"      m         Display module information\n"
"      i         Display combined instrument/sample list\n"
"      I         Display instrument list\n"
"      S         Display sample list\n"
"      c         Display comment, if any\n"
"      <         Play previous sequence\n"
"      >         Play next sequence\n"
);
}

void info_mod(const struct xmp_module_info *mi, int mode)
{
	int i;
	int num_seq;
	int total_time;

	report("Module name  : %s\n", mi->mod->name);
	report("Module type  : %s", mi->mod->type);

	if (mode != XMP_MODE_AUTO) {
		const struct player_mode *pm;
		for (pm = pmode; pm->name != NULL; pm++) {
			if (pm->mode == mode) {
				report(" [play as:%s]", pm->desc);
				break;
			}
		}
	}

	report("\nModule length: %d patterns\n", mi->mod->len);
	report("Patterns     : %d\n", mi->mod->pat);
	report("Instruments  : %d\n", mi->mod->ins);
	report("Samples      : %d\n", mi->mod->smp);
	report("Channels     : %d [ ", mi->mod->chn);

	for (i = 0; i < mi->mod->chn; i++) {
		if (mi->mod->xxc[i].flg & XMP_CHANNEL_SYNTH) {
			report("S ");
		} else if (mi->mod->xxc[i].flg & XMP_CHANNEL_MUTE) {
			report("- ");
		} else if (mi->mod->xxc[i].flg & XMP_CHANNEL_SURROUND) {
			report("^ ");
		} else {
			report("%x ", mi->mod->xxc[i].pan >> 4);
		}
	}
	report("]\n");

	total_time = mi->seq_data[0].duration;

	report("Duration     : %dmin%02ds", (total_time + 500) / 60000,
					((total_time + 500) / 1000) % 60);

	/* Check non-zero-length sequences */
	num_seq = 0;
	for (i = 0; i <  mi->num_sequences; i++) {
		if (mi->seq_data[i].duration > 0)
			num_seq++;
	}

	if (num_seq > 1) {
		report(" (main sequence)\n");
		for (i = 1; i < mi->num_sequences; i++) {
			int dur = mi->seq_data[i].duration;

			if (dur == 0) {
				continue;
			}

			report("               %dmin%02ds "
				"(sequence %d at position %02X)\n",
				(dur + 500) / 60000, ((dur + 500) / 1000) % 60,
				i, mi->seq_data[i].entry_point);
		}
	} else {
		report("\n");
	}
}

void info_frame_init(void)
{
	max_channels = 0;
}

#define MSG_SIZE 80
static int msg_timer = 0;
static char msg_text[MSG_SIZE];

void info_message(const char *format, ...)
{
	va_list ap;

	va_start(ap, format);
	msg_timer = 300000;
	vsnprintf(msg_text, MSG_SIZE, format, ap);
	va_end(ap);
}

static void fix_info_02x(int val, char *buf)
{
	if (val <= 0xff) {
		snprintf(buf, 3, "%02X", val);
	} else if (val <= 0xfff) {
		snprintf(buf, 3, "+%X", val >> 8);
	} else {
		strcpy(buf, "++");
	}
}

void info_frame(const struct xmp_module_info *mi, const struct xmp_frame_info *fi, struct control *ctl, int reprint)
{
	static int ord = -1, spd = -1, bpm = -1;
	char rowstr[3], numrowstr[3];
	char chnstr[3], maxchnstr[3];
	int time;
	char x;

	if (fi->virt_used > max_channels)
		max_channels = fi->virt_used;

	if (!reprint && fi->frame != 0)
		return;

	time = fi->time / 100;

	/* Show mixer type */
	x = ' ';
	if (ctl->amiga_mixer) {
		switch (ctl->mixer_type) {
		case XMP_MIXER_STANDARD:
			x = '-';
			break;
		case XMP_MIXER_A500:
			x = 'A';
			break;
		case XMP_MIXER_A500F:
			x = 'F';
			break;
		default:
			x = 'x';
		}
	}

	if (msg_timer > 0) {
		report("\r%-61.61s %c%c%c", msg_text,
			ctl->explore ? 'Z' : ' ',
			ctl->loop ? 'L' : ' ', x);
		msg_timer -= fi->frame_time * fi->speed / 6;
		if (msg_timer == 0) {
			msg_timer--;
		} else {
			goto print_time;
		}
	}

	if (msg_timer < 0) {
		reprint = 1;
		msg_timer = 0;
	}

	if (reprint || fi->pos != ord || fi->bpm != bpm || fi->speed != spd) {
		report("\rSpeed[%02X] BPM[%02X] Pos[%02X/%02X] "
			 "Pat[%02X/%02X] Row[  /  ] Chn[  /  ]      0:00:00.0",
					fi->speed, fi->bpm,
					fi->pos, mi->mod->len - 1,
					fi->pattern, mi->mod->pat - 1);
		ord = fi->pos;
		bpm = fi->bpm;
		spd = fi->speed;
	}

	fix_info_02x(fi->row, rowstr);
	fix_info_02x(fi->num_rows - 1, numrowstr);
	fix_info_02x(fi->virt_used, chnstr);
	fix_info_02x(max_channels, maxchnstr);

	report("\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"
	       "%2.2s/%2.2s] Chn[%2.2s/%2.2s] %c%c%c",
		rowstr, numrowstr, chnstr, maxchnstr,
		ctl->explore ? 'Z' : ' ', ctl->loop ? 'L' : ' ', x);

    print_time:

	if (ctl->pause) {
		report(" - PAUSED -");
	} else {
		report("%3d:%02d:%02d.%d",
			(int)(time / (60 * 600)), (int)((time / 600) % 60),
			(int)((time / 10) % 60), (int)(time % 10));
	}

	fflush(stdout);
}

static void get_sample_pan_string(char pan[4], const struct xmp_subinstrument *sub)
{
	if (sub->pan >= 0) {
		snprintf(pan, 4, "P%02x", sub->pan & 0xff);
	} else {
		strcpy(pan, "---");
	}
}

void info_ins_smp(const struct xmp_module_info *mi)
{
	int i, j;
	const struct xmp_module *mod = mi->mod;
	char pan_string[4];

	report("Instruments and samples:\n");
	report("   Instrument name                  Smp  Size  Loop  End    Vol Fine Xpo Pan\n");
	for (i = 0; i < mod->ins; i++) {
		struct xmp_instrument *ins = &mod->xxi[i];
		int has_sub = 0;

		if (strlen(ins->name) == 0 && ins->nsm == 0) {
			continue;
		}

		report("%02x %-32.32s ", i + 1, ins->name);

		for (j = 0; j < ins->nsm; j++) {
			const struct xmp_subinstrument *sub = &ins->sub[j];
			const struct xmp_sample *smp;

			/* Some instruments reference samples that don't exist
			 * (see beek-substitutionology.it). */
			if (sub->sid >= mod->smp) {
				continue;
			}
			smp = &mod->xxs[sub->sid];

			if (j > 0) {
				if (smp->len == 0) {
					continue;
				}
				report("%36.36s", " ");
			}
			get_sample_pan_string(pan_string, sub);

			has_sub = 1;
			report("[%02x] %05x%c%05x %05x%c V%02x %+04d %+03d %-3.3s\n",
				sub->sid + 1,
				smp->len,
				smp->flg & XMP_SAMPLE_16BIT ? '+' : ' ',
				smp->lps,
				smp->lpe,
				smp->flg & XMP_SAMPLE_LOOP ?
					smp->flg & XMP_SAMPLE_LOOP_BIDIR ?
						'B' : 'L' : ' ',
				sub->vol,
				sub->fin,
				sub->xpo,
				pan_string);
		}

		if (has_sub == 0) {
			report("[  ] ----- ----- -----  --- ---- --- ---\n");
		}
	}
}

void info_instruments(const struct xmp_module_info *mi)
{
	int i, j;
	const struct xmp_module *mod = mi->mod;
	char pan_string[4];

	report("Instruments:\n");
	report("   Instrument name                  Vl Fade Env Ns Sub  Gv Vl Fine Xpo Pan Sm\n");
	for (i = 0; i < mod->ins; i++) {
		const struct xmp_instrument *ins = &mod->xxi[i];
		int has_sub = 0;

		if (strlen(ins->name) == 0 && ins->nsm == 0) {
			continue;
		}

		report("%02x %-32.32s %02x %04x %c%c%c %02x ",
			i + 1, ins->name, ins->vol, ins->rls,
			ins->aei.flg & XMP_ENVELOPE_ON ? 'A' : '-',
			ins->fei.flg & XMP_ENVELOPE_ON ? 'F' : '-',
			ins->pei.flg & XMP_ENVELOPE_ON ? 'P' : '-',
			ins->nsm
		);

		for (j = 0; j < ins->nsm; j++) {
			const struct xmp_subinstrument *sub = &ins->sub[j];
			const struct xmp_sample *smp;

			if (j > 0) {
				/* Some instruments reference samples that don't exist
				 * (see beek-substitutionology.it). */
				if (sub->sid >= mod->smp) {
					continue;
				}
				smp = &mod->xxs[sub->sid];

				if (smp->len == 0) {
					continue;
				}
				report("%51.51s", " ");
			}
			get_sample_pan_string(pan_string, sub);

			has_sub = 1;
			report("[%02x] %02x %02x %+04d %+03d %-3.3s %02x\n",
				j + 1,
				sub->gvl,
				sub->vol,
				sub->fin,
				sub->xpo,
				pan_string,
				sub->sid);
		}

		if (has_sub == 0) {
			report("[  ] -- -- ---- --- --- --\n");
		}
	}
}

void info_samples(const struct xmp_module_info *mi)
{
	int i;
	const struct xmp_module *mod = mi->mod;

	report("Samples:\n");
	report("   Sample name                      Length Start  End    Flags\n");
	for (i = 0; i < mod->smp; i++) {
		const struct xmp_sample *smp = &mod->xxs[i];

		if (strlen(smp->name) == 0 && smp->len == 0) {
			continue;
		}

		report("%02x %-32.32s %06x %06x %06x %s %s %s\n",
			i + 1, smp->name,
			smp->len,
			smp->lps,
			smp->lpe,
			smp->flg & XMP_SAMPLE_16BIT ? "16" : "--",
			smp->flg & XMP_SAMPLE_LOOP  ? "L"  : "-",
			smp->flg & XMP_SAMPLE_LOOP_BIDIR ? "B" : "-");
	}
}

void info_comment(const struct xmp_module_info *mi)
{
	char *c = mi->comment;

	if (mi->comment == NULL) {
		report("No comment.\n");
		return;
	}

	while (*c != 0) {
		report("> ");
		do {
			if (*c == 0)
				break;
			report("%c", *c);
		} while (*c++ != '\n');
	}
	report("\n\n");
}
