/*****************************************************************************

        GRAOUMF TRACKER 2
        Author: Laurent de Soras, 1996-2016

--- Legal stuff ---

This program is free software. It comes without any warranty, to
the extent permitted by applicable law. You can redistribute it
and/or modify it under the terms of the Do What The Fuck You Want
To Public License, Version 2, as published by Sam Hocevar. See
http://sam.zoy.org/wtfpl/COPYING for more details.

*****************************************************************************/



/*\\\ FICHIERS INCLUDE \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/

#include	<assert.h>
#include	<stdio.h>
#include	<stdlib.h>
#include	<string.h>
#include	<math.h>

#include	"archi.h"
#include	"base.h"
#include	"base_ct.h"
#include	"file.h"
#include	"inst.h"
#include	"log.h"
#include	"memory.h"
#include	"mod_mod.h"
#include	"mods_ct.h"
#include	"patt.h"
#include	"Pattern.h"
#include	"player.h"
#include	"samp.h"
#include	"Sample.h"
#include	"spl_raw.h"
#include	"song.h"
#include	"splstruc.h"
#include	"Waveform.h"




/*\\\ CONSTANTES PRIVEES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/

#define	MODMOD_SONGNAME_LEN	20
#define	MODMOD_INSTRNAME_LEN	22
#define	MODMOD_SONG_LEN		128
#define	MODMOD_NBR_INSTR		31

enum
{
	MODMOD_SUBTYPE_NORMAL = 0,
	MODMOD_SUBTYPE_DT
};



/*\\\ TYPES & STRUCTURES PRIVEES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/

typedef struct
{
	char		name [MODMOD_INSTRNAME_LEN];
	UWORD		length;
	UBYTE		finetune;
	UBYTE		volume;
	UWORD		reppos;
	UWORD		replen;
} MODMOD_INSTR;

typedef struct
{
	UBYTE		length;
	UBYTE		reppos;
	UBYTE		song [MODMOD_SONG_LEN];
	char		id [4];
	/* Digital Tracker: 1 mot long qui donne le nombre de lignes */
} MODMOD_SONG;

typedef struct
{
	bool		detected_flag;
	int		nbr_tracks;
	int		nbr_lines;
	int		subtype;
} MODMOD_TEMP_INFO;



/*\\\ PROTOTYPES DES FONCTIONS PRIVEES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/



/*\\\ VARIABLES EXTERNES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/



/*\\\ VARIABLES PRIVEES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/



/*\\\ FONCTIONS \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/



/*==========================================================================*/
/*      Nom: MODMOD_save_module                                             */
/*      Description: Sauve un module en MOD.                                */
/*      Parametres en entree:                                               */
/*        - file_ptr: pointeur du fichier a sauver.                         */
/*      Retour: 0 si tout s'est bien passe.                                 */
/*==========================================================================*/

signed int	MODMOD_save_module (FILE *file_ptr)
{

	/*** A faire ***/

	return (0);
}



/*==========================================================================*/
/*      Nom: MODMOD_load_module                                             */
/*      Description: Charge un module en MOD.                               */
/*      Parametres en entree:                                               */
/*        - file_ptr: pointeur du fichier a charger.                        */
/*      Retour: 0 si tout s'est bien passe.                                 */
/*==========================================================================*/

signed int	MODMOD_load_module (FILE *file_ptr, BYTE temp_info [MODS_TEMP_INFO_LEN])
{
	int		nbr_tracks;
	int		nbr_lines;
	int		inst_cnt;
	int		pos_cnt;
	int		pat_cnt;
	int		trk_cnt;
	int		last_pattern;
	long		data_offset;
	MODMOD_INSTR	instr;
	MODMOD_SONG		song;
	char		songname_0 [MODMOD_SONGNAME_LEN+1];
	char		instrname_0 [MODMOD_INSTRNAME_LEN+1];
	SPLS_PRIMARY_INFO	sample_info [MODMOD_NBR_INSTR];
	MODMOD_NOTE	*pattern_ptr;
	MODMOD_TEMP_INFO	*format_info;

	Player &			player = Player::use_instance ();

	format_info = (MODMOD_TEMP_INFO *) temp_info;

	nbr_tracks = format_info->nbr_tracks;
	nbr_lines = format_info->nbr_lines;

	rewind (file_ptr);

/*______________________________________________
 *
 * Charge le nom de la song
 *______________________________________________
 */

	if (fread (songname_0, MODMOD_SONGNAME_LEN, 1, file_ptr) != 1)
	{
		LOG_printf ("MODMOD_load_module: Error: couldn't read song name.\n");
		return (-1);
	}
	songname_0 [MODMOD_SONGNAME_LEN] = '\0';
	SONG_set_song_name (songname_0);

/*______________________________________________
 *
 * Charge les instruments
 *______________________________________________
 */

	instrname_0 [MODMOD_INSTRNAME_LEN] = '\0';
	for (inst_cnt = 1; inst_cnt <= MODMOD_NBR_INSTR; inst_cnt ++)
	{
		if (fread (&instr, sizeof (instr), 1, file_ptr) != 1)
		{
			LOG_printf ("MODMOD_load_module: Error: couldn't read instrument # %d.\n",
			            inst_cnt);
			return (-1);
		}

		memcpy (instrname_0, instr.name, MODMOD_INSTRNAME_LEN);
		INST_set_instr_name (inst_cnt, instrname_0);
		INST_set_instr_volume (inst_cnt, instr.volume << 2);

		SAMP_set_sample_name (inst_cnt, instrname_0);
		SAMP_set_sample_freq (inst_cnt, 8363);
		SAMP_set_sample_finetune (inst_cnt, BASE_quartet_to_int (instr.finetune));
		SPLS_init_primary_info (&sample_info [inst_cnt - 1], inst_cnt);
		sample_info [inst_cnt - 1].byte_order = 0;
		sample_info [inst_cnt - 1].signed_flag = true;
		sample_info [inst_cnt - 1].nbits = 8;
		sample_info [inst_cnt - 1].tracks = 1;
		sample_info [inst_cnt - 1].length = (long) BASE_moto_word (instr.length) << 1;
		sample_info [inst_cnt - 1].repeat = (long) BASE_moto_word (instr.reppos) << 1;
		sample_info [inst_cnt - 1].replen = (long) BASE_moto_word (instr.replen) << 1;
		sample_info [inst_cnt - 1].loop_type =   (   sample_info [inst_cnt - 1].repeat != 0
                                                || sample_info [inst_cnt - 1].replen > 2)
                                             ? WaveForm_LOOP_TYPE_FWD
		                                       : WaveForm_LOOP_TYPE_NONE;
	}

/*______________________________________________
 *
 * Charge la song
 *______________________________________________
 */

	if (fread (&song, sizeof (song), 1, file_ptr) != 1)
	{
		LOG_printf ("MODMOD_load_module: Error: couldn't read song.\n");
		return (-1);
	}

	SONG_set_song_length (MODMOD_SONG_LEN);
	last_pattern = 0;
	for (pos_cnt = 0; pos_cnt < MODMOD_SONG_LEN; pos_cnt ++)
	{
		SONG_set_pattern_number (pos_cnt, song.song [pos_cnt]);
		if (pos_cnt < song.length)
		{
			last_pattern = MAX (last_pattern, song.song [pos_cnt]);
		}
	}

	if (song.reppos >= song.length)
	{
		song.reppos = 0;
	}

	SONG_set_song_length (song.length);
	SONG_set_song_repeat (song.reppos);

	data_offset =   MODMOD_SONGNAME_LEN
	              + sizeof (MODMOD_INSTR) * MODMOD_NBR_INSTR
	              + sizeof (MODMOD_SONG)
	              + sizeof (MODMOD_NOTE) * nbr_lines * nbr_tracks * (last_pattern + 1);

	if (format_info->subtype == MODMOD_SUBTYPE_DT)
	{
		data_offset += 4;
		if (FILE_fseek (file_ptr, 4, SEEK_CUR) != 0)
		{
			LOG_printf ("MODMOD_load_module: Error: couldn't set file position.\n");
			return (-1);
		}
	}

/*______________________________________________
 *
 * Charge les patterns
 *______________________________________________
 */

	/* Change le nombre de pistes */
	if (PAT_set_nbr_tracks (Pattern_TYPE_SPL, nbr_tracks))
	{
		LOG_printf ("MODMOD_load_module: Error: couldn't set number of tracks to %d.\n",
		            nbr_tracks);
		return (-1);
	}

	pattern_ptr = (MODMOD_NOTE *) MALLOC (sizeof (*pattern_ptr) * nbr_lines * nbr_tracks);
	if (pattern_ptr == NULL)
	{
		LOG_printf ("MODMOD_load_module: Error: couldn't allocate memory for pattern conversion.\n");
		return (-1);
	}

	/* Conversion de chaque pattern */
	for (pat_cnt = 0; pat_cnt <= last_pattern; pat_cnt ++)
	{
		if (PAT_set_pattern_height (pat_cnt, nbr_lines))
		{
			LOG_printf ("MODMOD_load_module: Error: couldn't set number of lines of pattern # %d to %d.\n",
			            pat_cnt, nbr_lines);
			FREE (pattern_ptr);
			return (-1);
		}

		if (fread (pattern_ptr, sizeof (*pattern_ptr) * nbr_lines * nbr_tracks, 1, file_ptr) != 1)
		{
			LOG_printf ("MODMOD_load_module: Error: couldn't read pattern # %d.\n",
			            pat_cnt);
			FREE (pattern_ptr);
			return (-1);
		}

		MODMOD_modpat_2_gtpat (pat_cnt, pattern_ptr);
	}

	FREE (pattern_ptr);

/*______________________________________________
 *
 * Charge les samples
 *______________________________________________
 */

	for (inst_cnt = 1; inst_cnt <= MODMOD_NBR_INSTR; inst_cnt ++)
	{
		sample_info [inst_cnt - 1].data_offset = data_offset;
		data_offset += sample_info [inst_cnt - 1].length;

		if (SPLRAW_load_sample (file_ptr, inst_cnt, &sample_info [inst_cnt - 1]))
		{
			LOG_printf ("MODMOD_load_module: Error: couldn't load data of sample # %d.\n",
			            inst_cnt);
			return (-1);
		}

		SAMP_set_sample_loop (inst_cnt,
		                      sample_info [inst_cnt - 1].loop_type,
		                      sample_info [inst_cnt - 1].repeat,
		                      sample_info [inst_cnt - 1].replen);
		SAMP_make_buffers (inst_cnt);
	}

	/* Initialisation des balances: L R R L L R ... */
	for (trk_cnt = 0; trk_cnt < nbr_tracks; trk_cnt ++)
	{
		player.set_track_panning (Pattern_TYPE_SPL, trk_cnt, false, (((trk_cnt + 1) >> 1) & 1) * 0xFFFFL);
	}

	/* Tempo */
	player.set_tempo (125);
	player.set_speed (6);

	return (0);
}



/*==========================================================================*/
/*      Nom: MODMOD_detect_format                                           */
/*      Description: Teste si un module est au format MOD.                  */
/*      Parametres en entree:                                               */
/*        - header_ptr: les premiers octets du module.                      */
/*        - header_length: taille du header sus-cite.                       */
/*      Parametres en entree/sortie:                                        */
/*        - file_ptr: pointeur sur le fichier du module. Il n'y a pas       */
/*                    besoin de sauver la position.                         */
/*      Retour: true si le module est un MOD (ou false si c'est un MOD a    */
/*              15 instruments).                                            */
/*==========================================================================*/

bool	MODMOD_detect_format (FILE *file_ptr, const void *header_ptr, long header_length, BYTE temp_info [MODS_TEMP_INFO_LEN])
{
	MODMOD_INSTR	*instr_ptr;
	MODMOD_SONG		*song_ptr;
	char		temp_0 [7+1];
	int		nbr_channels;
	MODMOD_TEMP_INFO	*format_info;

	format_info = (MODMOD_TEMP_INFO *) temp_info;

	if (header_length <   MODMOD_SONGNAME_LEN
	                    + sizeof (MODMOD_INSTR) * MODMOD_NBR_INSTR
	                    + sizeof (MODMOD_SONG))
	{
		return (false);
	}

	instr_ptr = (MODMOD_INSTR *) ((char *)header_ptr + MODMOD_SONGNAME_LEN);
	song_ptr = (MODMOD_SONG *) (instr_ptr + MODMOD_NBR_INSTR);

	BASE_lpoke (temp_0, BASE_lpeek (&song_ptr->id));
	temp_0 [4] = '\0';

	format_info->subtype = MODMOD_SUBTYPE_NORMAL;
	format_info->nbr_tracks = 4;
	format_info->nbr_lines = 64;
	format_info->detected_flag = false;

	/* Modules standard 4 voies */
	if (   BASE_compare_id4 (&song_ptr->id, "M.K.")
	    || BASE_compare_id4 (&song_ptr->id, "M&K&")
	    || BASE_compare_id4 (&song_ptr->id, "M!K!")
	    || BASE_compare_id4 (&song_ptr->id, "RASP"))
	{
		format_info->detected_flag = true;

		return (true);
	}

	/* Modules CD81 */
	if (BASE_compare_id4 (&song_ptr->id, "CD81"))
	{
		format_info->nbr_tracks = 8;
		format_info->detected_flag = true;

		return (true);
	}

	/* Modules FLTx */
	nbr_channels = atoi (temp_0 + 3);
	if (   song_ptr->id [0] == 'F'
	    && song_ptr->id [1] == 'L'
	    && song_ptr->id [2] == 'T'
		 && nbr_channels > 0)
	{
		format_info->detected_flag = true;
		format_info->nbr_tracks = nbr_channels;

		return (true);
	}

	/* Modules FAxx */
	nbr_channels = atoi (temp_0 + 2);
	if (   song_ptr->id [0] == 'F'
	    && song_ptr->id [1] == 'A'
		 && nbr_channels > 0)
	{
		format_info->detected_flag = true;
		format_info->subtype = MODMOD_SUBTYPE_DT;
		format_info->nbr_tracks = nbr_channels;
		format_info->nbr_lines = (int) BASE_lpeek_moto (&song_ptr->id [4]);

		return (true);
	}

	/* Modules xCHN ou xxCH */
	nbr_channels = atoi (temp_0);
	if (nbr_channels > 0)
	{
		sprintf (temp_0, "%dCHN", nbr_channels);
		if (BASE_compare_id4 (&song_ptr->id, temp_0))
		{
			format_info->detected_flag = true;
			format_info->nbr_tracks = nbr_channels;

			return (true);
		}
	}

	return (false);
}



/*==========================================================================*/
/*      Nom: MODMOD_modpat_2_gtpat                                          */
/*      Description: Convertit un pattern ProTracker en pattern GT.         */
/*                   Le format du pattern doit deja etre fixe; seules les   */
/*                   donnees sont converties.                               */
/*      Parametres en entree:                                               */
/*        - pattern: numero du pattern a convertir.                         */
/*        - pattern_ptr: pointeur sur les donnees brutes du pattern a       */
/*          convertir.                                                      */
/*==========================================================================*/

void	MODMOD_modpat_2_gtpat (int pattern, const MODMOD_NOTE *pattern_ptr)
{
	assert (pattern_ptr != NULL);

	const int	nbr_lines = PAT_get_pattern_height (pattern);
	const int	nbr_tracks = PAT_get_pattern_nbr_tracks (Pattern_TYPE_SPL, pattern);
	long			index = 0;

	for (int line_cnt = 0; line_cnt < nbr_lines; line_cnt ++)
	{
		for (int trk_cnt = 0; trk_cnt < nbr_tracks; trk_cnt ++)
		{
			MODS_GT2_SPL_NOTE *const	note_ptr =
				PAT_get_spl_note_adr_pat (pattern, line_cnt, trk_cnt);

			/* Note */
			const int	period =
				  (((int) pattern_ptr [index].data [0] & 0xF) << 8)
				+ pattern_ptr [index].data [1];
			note_ptr->note = 0;
			if (period != 0)
			{
				note_ptr->note = Sample_REF_C2 - (int) floor (log ((double)period / 0x1AC) * 12 / LN2 + 0.5);
			}

			/* Instrument */
			note_ptr->instr =   (pattern_ptr [index].data [0] & 0xF0)
			                  + (pattern_ptr [index].data [2] >> 4);

			/* Effet */
			const int	effect =
				  (((int) pattern_ptr [index].data [2] & 0xF) << 8)
				+ pattern_ptr [index].data [3];
			note_ptr->fxnum = 0;
			note_ptr->fxparam = 0;
			note_ptr->volume = 0;
			MODMOD_modcmd_2_gtcmd (*note_ptr, effect);

			index ++;
		}
	}
}



/*==========================================================================*/
/*      Nom: MODMOD_modcmd_2_gtcmd                                          */
/*      Description: Convertit un effet Protracker en effet GT.             */
/*      Parametres en entree:                                               */
/*        - cmd: commande a convertir.                                      */
/*        - allow_nul_param_flag: true si les commandes a parametre nul     */
/*                                doivent etre prises en compte.            */
/*      Parametres en sortie:                                               */
/*      Parametres en entree/sortie:                                        */
/*        - gt2_note: note qui recoit l'effet converti. Si l'effet ne peut  */
/*ù                   etre converti, la zone correspondante est laissee     */
/*                    telle quelle.                                         */
/*==========================================================================*/

void	MODMOD_modcmd_2_gtcmd (MODS_GT2_SPL_NOTE &gt2_note, int cmd, bool allow_nul_param_flag)
{
	int		param;

	param = cmd & 0xFF;
	switch (cmd >> 8)
	{

	/* Arpeggio */
	case	0x0:
		if (param != 0)
		{
			gt2_note.fxnum = 0x10;
			gt2_note.fxparam = param;
		}
		break;

	/* Porta up */
	case	0x1:
		if (param != 0 || allow_nul_param_flag)
		{
			gt2_note.fxnum = 0x01;
			gt2_note.fxparam = param;
		}
		break;

	/* Porta down */
	case	0x2:
		if (param != 0 || allow_nul_param_flag)
		{
			gt2_note.fxnum = 0x02;
			gt2_note.fxparam = param;
		}
		break;

	/* Tone porta */
	case	0x3:
		gt2_note.fxnum = 0x03;
		gt2_note.fxparam = param;
		break;

	/* Vibrato */
	case	0x4:
		gt2_note.fxnum = 0x04;
		gt2_note.fxparam = param;
		break;

	/* Volume slide + tone porta */
	case	0x5:
		param = (param >> 4) - (param & 0xF);
		if (param < 0)
		{
			gt2_note.fxnum = 0x19;
			param = -param;
		}
		else if (param > 0 || allow_nul_param_flag)
		{
			gt2_note.fxnum = 0x18;
		}
		else
		{
			gt2_note.fxnum = 0x03;
		}
		gt2_note.fxparam = param << 2;
		break;

	/* Volume slide + vibrato */
	case	0x6:
		param = (param >> 4) - (param & 0xF);
		if (param < 0)
		{
			gt2_note.fxnum = 0x1D;
			param = -param;
		}
		else if (param > 0 || allow_nul_param_flag)
		{
			gt2_note.fxnum = 0x1C;
		}
		else
		{
			gt2_note.fxnum = 0x04;
		}
		gt2_note.fxparam = param << 2;
		break;

	/* Tremolo */
	case	0x7:
		gt2_note.fxnum = 0x07;
		gt2_note.fxparam = param;
		break;

	/* Balance */
	case	0x8:
		gt2_note.fxnum = 0x40 + (param >> 4);
		gt2_note.fxparam = (param & 0xF) << 4;
		break;

	/* Part of sample */
	case	0x9:
		gt2_note.fxnum = 0x90;
		gt2_note.fxparam = param;
		break;

	/* Volume slide */
	case	0xA:
		param = (param >> 4) - (param & 0xF);
		if (param > 0)
		{
			gt2_note.fxnum = 0x14;
		}
		else if (param < 0 || allow_nul_param_flag)
		{
			gt2_note.fxnum = 0x15;
			param = -param;
		}
		gt2_note.fxparam = param << 2;
		break;

	/* Jump to position */
	case	0xB:
		gt2_note.fxnum = 0x0B;
		gt2_note.fxparam = param;
		break;

	/* Set volume */
	case	0xC:
		if (   param >= 0x40
		    || param == 0
		    || gt2_note.volume != 0)
		{
			gt2_note.fxnum = 0x20 + (param >> 6);
			gt2_note.fxparam = param << 2;
		}
		else
		{
			gt2_note.volume = param + 0x10;
		}
		break;

	/* Break pattern */
	case	0xD:
		gt2_note.fxnum = 0x0D;
		gt2_note.fxparam = (param >> 4) * 10 + (param & 0xF);
		break;

	/* Effets etendus */
	case	0xE:
		param &= 0xF;
		switch ((cmd >> 4) & 0xF)
		{
		/* Fine porta up */
		case	0x1:
			gt2_note.fxnum = 0x11;
			gt2_note.fxparam = param;
			break;

		/* Fine porta down */
		case	0x2:
			gt2_note.fxnum = 0x12;
			gt2_note.fxparam = param;
			break;

		/* Set vibrato waveform */
		case	0x4:
			gt2_note.fxnum = 0x0C;
			gt2_note.fxparam = param;
			break;

		/* Set finetune */
		case	0x5:
			gt2_note.fxnum = 0x08;
			if (param >= 0x8)
			{
				gt2_note.fxparam = 0x10 - param;
			}
			else
			{
				gt2_note.fxparam = param << 4;
			}
			break;

		/* Pattern loop */
		case	0x6:
			gt2_note.fxnum = 0xB1;
			gt2_note.fxparam = param;
			break;

		/* Set tremolo wave */
		case	0x7:
			gt2_note.fxnum = 0x0E;
			gt2_note.fxparam = param;
			break;

		/* Balance */
		case	0x8:
			gt2_note.fxnum = 0x40 + param;
			gt2_note.fxparam = 0;
			break;

		/* Retrig sample */
		case	0x9:
			gt2_note.fxnum = 0x70 + param;
			gt2_note.fxparam = 0;
			break;

		/* Fine volume up */
		case	0xA:
			gt2_note.fxnum = 0xA4;
			gt2_note.fxparam = param << 2;
			break;

		/* Fine volume down */
		case	0xB:
			gt2_note.fxnum = 0xA5;
			gt2_note.fxparam = param << 2;
			break;

		/* Note cut */
		case	0xC:
			gt2_note.fxnum = 0x0A;
			gt2_note.fxparam = param;
			break;

		/* Note delay */
		case	0xD:
			gt2_note.fxnum = 0x09;
			gt2_note.fxparam = param;
			break;

		/* Pattern delay */
		case	0xE:
			gt2_note.fxnum = 0xAA;
			gt2_note.fxparam = param;
			break;

		}
		break;

	/* Set speed/tempo */
	case	0xF:
		if (param < 0x20)
		{
			gt2_note.fxnum = 0xA8;
		}
		else
		{
			gt2_note.fxnum = 0x0F;
		}
		gt2_note.fxparam = param;
		break;

	}
}



/****************************************************************************/
/*                                                                          */
/*                                                                          */
/*                                                                          */
/****************************************************************************/



/*==========================================================================*/
/*      Nom:                                                                */
/*      Description:                                                        */
/*      Parametres en entree:                                               */
/*      Parametres en sortie:                                               */
/*      Parametres en entree/sortie:                                        */
/*      Retour:                                                             */
/*==========================================================================*/
