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

        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	"archi.h"
#include	"base.h"
#include	"base_ct.h"
#include	"edstring.h"
#include	"gtracker.h"
#include	"intrface.h"
#include	"List.h"
#include	"log.h"
#include	"mixer.h"
#include	"modstruc.h"
#include	"mpannel.h"
#include	"os.h"
#include	"patt.h"
#include	"Pattern.h"
#include	"player.h"
#include	"rsc01.h"
#include	"resource.h"
#include	"tracks.h"
#include	"UndoCellPatChgHeight.h"
#include	"UndoCellPatChgTrk.h"
#include	"UndoCellList.h"
#include	"UndoCellPatClear.h"



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



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



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



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

int	PAT_new_pattern_nbr_lines = 64;	/* Nombre de lignes par defaut sur un pattern */



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

Pattern	*PAT_pattern_ptr [GTK_NBRPATTERNS_TOTAL];



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



signed int	PAT_init (void)
{
	int		tot_nbr_tracks = 0;
	int		track_type;

	/* Verifie que le nombre de piste est bon */
	for (track_type = 0; track_type < Pattern_NBR_TYPES; track_type ++)
	{
		tot_nbr_tracks += GTK_nbr_tracks [track_type];
	}

	track_type = Pattern_NBR_TYPES - 1;
	while (   tot_nbr_tracks > GTK_NBRTRACKS_MAXI
	       && track_type >= 0)
	{
		int		min_nbr = 0;
		int		sub;

		if (track_type != Pattern_TYPE_AIN)
		{
			if (track_type == Pattern_TYPE_SPL)
			{
				min_nbr = 1;
			}
			sub = MIN (GTK_nbr_tracks [track_type] - min_nbr,
			           tot_nbr_tracks - GTK_NBRTRACKS_MAXI);
			sub = MAX (sub, 0);
			GTK_nbr_tracks [track_type] -= sub;
			tot_nbr_tracks -= sub;
		}

		-- track_type;
	}
	TRK_check_presets ();

	/* Maintenant on passe a l'initialisation proprement dite */
	for (int cnt = 0; cnt < GTK_NBRPATTERNS_TOTAL; cnt ++)
	{
		PAT_pattern_ptr [cnt] = new Pattern (cnt, GTK_nbr_tracks);
		if (PAT_pattern_ptr [cnt] == NULL)
		{
			LOG_printf ("PAT_init: couldn't create pattern # %d.\n", cnt);
			return (-1);
		}
		if (PAT_pattern_ptr [cnt]->check_ok ())
		{
			LOG_printf ("PAT_init: failed to create pattern # %d.\n", cnt);
			return (-1);
		}
	}

	return (0);
}



void	PAT_restore (void)
{
	for (int cnt = 0; cnt < GTK_NBRPATTERNS_TOTAL; cnt ++)
	{
		if (PAT_pattern_ptr [cnt] != NULL)
		{
			delete PAT_pattern_ptr [cnt];
			PAT_pattern_ptr [cnt] = NULL;
		}
	}
}



/****************************************************************************/
/*                                                                          */
/*      ROUTINES DE BASE                                                    */
/*                                                                          */
/****************************************************************************/



char	*PAT_get_pattern_name (int pattern, char *name_0)
{
	return (PAT_pattern_ptr [pattern]->get_pattern_name (name_0));
}



void	PAT_set_pattern_name (int pattern, const char *name_0)
{
	PAT_pattern_ptr [pattern]->set_pattern_name (name_0);
}



int	PAT_get_pattern_nbr_tracks (int track_type, int pattern)
{
	return (PAT_pattern_ptr [pattern]->get_pattern_nbr_tracks (track_type));
}



void	PAT_set_pattern_nbr_tracks (int track_type, int pattern, int nbr_tracks)
{
	PAT_pattern_ptr [pattern]->set_pattern_nbr_tracks (track_type, nbr_tracks);
}



int	PAT_get_pattern_height (int pattern)
{
	return (PAT_pattern_ptr [pattern]->get_pattern_height ());
}



/* Renvoie 0 si ok */
signed int	PAT_set_pattern_height (int pattern, int nbrlines)
{
	if (PAT_pattern_ptr [pattern]->set_pattern_height (nbrlines))
	{
		LOG_printf ("PAT_set_pattern_height: Error: Couldn't change height of pattern # %d.\n",
		            pattern);
		return (-1);
	}

	INTR_pattern_editor_track_ptr->check_cursor ();
	GTK_pattern_block_ptr->check_block ();

	return (0);
}



int	PAT_get_current_pattern_height (void)
{
	return (PAT_get_pattern_height (GTK_get_current_pattern_number ()));
}



signed int	PAT_set_current_pattern_height (int height)
{
	return (PAT_set_pattern_height (GTK_get_current_pattern_number (), height));
}



/*==========================================================================*/
/*      Nom: PAT_set_nbr_tracks                                             */
/*      Description: Fixe un nouveau nombre de pistes pour un type de       */
/*                   pattern donne. Le player est arrete puis redemarre     */
/*                   s'il etait en marche.                                  */
/*      Parametres en entree:                                               */
/*        - track_type: type de pattern.                                    */
/*        - nbr_tracks: nouveau nombre de pistes.                           */
/*      Retour: 0 si tout s'est bien passe.                                 */
/*==========================================================================*/

signed int	PAT_set_nbr_tracks (int track_type, int nbr_tracks)
{
	int		old_nbr_tracks;
	int		pattern;
	int		scan_track;
	int		src_track;
	int		nbr_s_tracks;
	int		track_type_2;
	int		track_type_count;
	int		track;
	Player::TrackInfoSrc::Source	*track_conf_ptr;
	int		track_type_list [2] =
	{
		Pattern_TYPE_FX,
		GTK_TRACK_TYPE_AOU
	};

	old_nbr_tracks = GTK_nbr_tracks [track_type];

	/* Rien ne change, on se casse */
	if (nbr_tracks == old_nbr_tracks)
	{
		return (0);
	}

	{
		/* On arrete le player pour operer en toute tranquilite */
		std::lock_guard <std::mutex>	lock (GTK_mutex);

		Player &			player = Player::use_instance ();

		/* Si augmentation du nombre de pistes, on rajoute deja
			les descripteurs suplementaires pour le player. */
		player.add_new_track (track_type, nbr_tracks - old_nbr_tracks);

		/* On va mettre a jour les pistes Audio Out et FX */
		for (track_type_count = 0; track_type_count < 2; track_type_count ++)
		{
			track_type_2 = track_type_list [track_type_count];

			for (scan_track = 0; scan_track < GTK_nbr_tracks [track_type_2]; scan_track ++)
			{
				player.get_track_nbr_s_tracks (
					track_type_2, scan_track, &nbr_s_tracks, &track_conf_ptr
				);

				/* Suppression de pistes */
				if (nbr_tracks < old_nbr_tracks)
				{
					for (src_track = 0; src_track < nbr_s_tracks; src_track ++)
					{
						if (track_conf_ptr [src_track].conf.track_type == track_type)
						{
							if (track_conf_ptr [src_track].conf.track_nbr >= nbr_tracks)
							{
								if (nbr_s_tracks > 1)
								{
									if (src_track + 1 < nbr_s_tracks)
									{
										for (int index = src_track; index < nbr_s_tracks - 1; ++index)
										{
											track_conf_ptr [index] = track_conf_ptr [index + 1];
										}
									}
									src_track --;
									nbr_s_tracks --;
								}
								else
								{
									track_conf_ptr [0].conf.track_type = Pattern_TYPE_SPL;
									track_conf_ptr [0].conf.wet_flag = true;
									track_conf_ptr [0].conf.track_nbr = 0;
									track_conf_ptr [0].conf.inpvol = 0x1000;
									track_conf_ptr [0].conf.inppan = 0x8000U;
									track_conf_ptr [0].meter.clear_buffers ();
									nbr_s_tracks = 1;
								}
							}
						}
					}
				}

				/* Ajout des nouvelles pistes sur la premiere piste Audio Out */
				else if (   track_type_2 == GTK_TRACK_TYPE_AOU
				         && scan_track == 0)
				{
					for (track = 0; track < nbr_tracks - old_nbr_tracks; track ++)
					{
						src_track = nbr_s_tracks + track;
						track_conf_ptr [src_track].conf.track_type = track_type;
						track_conf_ptr [src_track].conf.wet_flag = true;
						track_conf_ptr [src_track].conf.track_nbr = old_nbr_tracks + track;
						track_conf_ptr [src_track].conf.cr.old_vol [0] = 0;
						track_conf_ptr [src_track].conf.cr.old_vol [1] = 0;
						/* Ces deux lignes n'ont pas d'importance car les pistes Audio Out n'ont
							pas de reglage de volume ou de balance pour les pistes d'entree. */
						track_conf_ptr [src_track].conf.inpvol = 0x1000;
						track_conf_ptr [src_track].conf.inppan = 0x8000U;
						track_conf_ptr [src_track].meter.set_sample_freq (MIX_replay_freq);
						track_conf_ptr [src_track].meter.clear_buffers ();
					}

					nbr_s_tracks += nbr_tracks - old_nbr_tracks;
				}

				Player::TrackInfo &	track_info =
					player._track_info [player._track_info_list [track_type_2] [scan_track]];
				if (track_type_2 == Pattern_TYPE_FX)
				{
					track_info.mix.src.nbr_s_tracks = nbr_s_tracks;
				}
				else
				{
					track_info.mix.src.nbr_s_tracks = nbr_s_tracks;
				}

			}
		}

		/* Pour chaque pattern, y compris les bidons ! */
		for (pattern = 0; pattern < GTK_NBRPATTERNS_TOTAL; pattern ++)
		{
			if (PAT_pattern_ptr [pattern]->set_nbr_tracks (track_type, nbr_tracks))
			{
				LOG_printf ("PAT_set_nbr_tracks: Error: Couldn't change number of tracks of %s pattern # %d.\n",
					         MODS_track_type_name_0_ptr [track_type], pattern);
				return (-1);
			}
		}

		GTK_nbr_tracks [track_type] = nbr_tracks;
	}	// Mutex released

	/* Ajuste les donnees de l'interface pour prendre
	   en compte les modifications */
	TRK_init_presets ();
	INTR_pattern_editor_track_ptr->check_cursor ();
	GTK_pattern_block_ptr->check_block ();

	return (0);
}



void	*PAT_get_note_adr_pat (int track_type, int pattern, int line, int track)
{
	return (PAT_pattern_ptr [pattern]->get_note_adr_pat (track_type, line, track));
}



MODS_GT2_SPL_NOTE	*PAT_get_spl_note_adr_pat (int pattern, int line, int track)
{
	return (PAT_pattern_ptr [pattern]->get_spl_note_adr_pat (line, track));
}



MODS_GT2_AIN_NOTE	*PAT_get_ain_note_adr_pat (int pattern, int line, int track)
{
	return (PAT_pattern_ptr [pattern]->get_ain_note_adr_pat (line, track));
}



MODS_GT2_FX_NOTE	*PAT_get_fx_note_adr_pat (int pattern, int line, int track)
{
	return (PAT_pattern_ptr [pattern]->get_fx_note_adr_pat (line, track));
}



MODS_GT2_MID_NOTE	*PAT_get_mid_note_adr_pat (int pattern, int line, int track)
{
	return (PAT_pattern_ptr [pattern]->get_mid_note_adr_pat (line, track));
}



UWORD	PAT_get_track_command (int pattern, int track_type, int line, int track)
{
	return (PAT_pattern_ptr [pattern]->get_track_command (track_type, line, track));
}



void	PAT_set_track_command (int pattern, int track_type, int line, int track, UWORD command)
{
	PAT_pattern_ptr [pattern]->set_track_command (track_type, line, track, command);
}



ULWORD	PAT_get_fx_command (int pattern, int line, int track)
{
	return (PAT_pattern_ptr [pattern]->get_fx_command (line, track));
}



void	PAT_set_fx_command (int pattern, int line, int track, ULWORD command)
{
	PAT_pattern_ptr [pattern]->set_fx_command (line, track, command);
}



void	PAT_clear_note (int track_type, int pattern, int line, int track)
{
	void		*data_ptr;

	data_ptr = PAT_get_note_adr_pat (track_type, pattern, line, track);
	assert (data_ptr != NULL);
	memset (data_ptr, 0, Pattern::NOTE_SIZE [track_type]);
}



signed int	PAT_clear_pattern (int pattern)
{
	const Player &	player = Player::use_instance ();

	if (player.get_play_mode () != Player::MODE_STOP)
	{
		GTK_stop_all ();
	}

	const int		cur_pat = GTK_get_current_pattern_number ();
	if (cur_pat == pattern)
	{
		GTK_set_line_position (0);
	}

	PAT_set_pattern_name (pattern, "");

	if (PAT_set_pattern_height (pattern, 1))
	{
		LOG_printf ("PAT_clear_pattern: Error: couldn't set height of pattern # %d to 1 line.\n",
			         pattern);
		return (-1);	/* Erreur lors du redimentionnement du pattern */
	}

	for (int track_type = 0; track_type < Pattern_NBR_TYPES; track_type ++)
	{
		for (int track = 0; track < GTK_nbr_tracks [track_type]; track ++)
		{
			PAT_clear_note (track_type, pattern, 0, track);
		}
	}

	return (0);
}



/*==========================================================================*/
/*      Nom: PAT_clear_patterns                                             */
/*      Description: Efface tous les patterns et les noms de piste          */
/*      Retour: 0 si tout s'est bien passe.                                 */
/*==========================================================================*/

signed int	PAT_clear_patterns (void)
{
	int				ret_val = 0;

	/* Efface les patterns */
	for (int pattern = PAT_NBRPATTERNS_MAXI - 1
	;	pattern >= 0 && ret_val == 0
	;	--pattern)
	{
		ret_val = PAT_clear_pattern (pattern);
	}

	/* Efface les noms des pistes */
	Player &			player = Player::use_instance ();
	if (ret_val == 0)
	{
		for (int track_type = 0; track_type < GTK_NBR_MIX_TRACK_TYPE; track_type ++)
		{
			for (int track = 0; track < GTK_nbr_tracks [track_type]; track ++)
			{
				player.set_track_name (track_type, track, "");
			}
		}
	}

	return (ret_val);
}



long	PAT_estimate_memory_usage (int pattern)
{
	return (PAT_pattern_ptr [pattern]->estimate_memory_usage ());
}



/****************************************************************************/
/*                                                                          */
/*      MODIFICATIONS AVEC L'INTERFACE                                      */
/*                                                                          */
/****************************************************************************/



/*==========================================================================*/
/*      Nom: PAT_set_pattern_height_intr                                    */
/*      Description: Change le nombre de lignes du pattern courant de       */
/*                   differentes manieres. L'interface est ici mise en jeu. */
/*      Parametres en entree:                                               */
/*        - type: 0 = Le parametre est absolu,                              */
/*                1 = Le parametre est relatif,                             */
/*                2 = Entree au clavier.                                    */
/*        - value: le parametre en question.                                */
/*==========================================================================*/

void	PAT_set_pattern_height_intr (int type, signed int value)
{
	int		pattern;
	int		track;
	int		old_nbr_lines;
	int		nbr_lines;
	int		current_line;
	int		track_type;
	char		nbr3_0 [3+1];
	char		text_0 [99+1];
	List		undo_list;

	pattern = GTK_get_current_pattern_number ();
	nbr_lines = PAT_get_pattern_height (pattern);
	old_nbr_lines = nbr_lines;

	switch (type)
	{
	/* Position relative */
	case	INTR_CHGTYPE_REL:
		nbr_lines += value;
		break;

	/* Entree au clavier */
	case	INTR_CHGTYPE_KBD:
		sprintf (nbr3_0, INTR_base_lines_3, nbr_lines);
		EDIT_edit_string_base (nbr3_0, RSC_OBJ_MP_VAR_LINE_VAL, 3, INTR_base_lines);
		nbr_lines = (int) strtol (nbr3_0, NULL, INTR_base_lines);
		break;

	/* Position absolue  */
	case	INTR_CHGTYPE_ABS:
		nbr_lines = value;
		break;
	}

	nbr_lines = MIN (nbr_lines, GTK_NBRLINES_MAXI);
	nbr_lines = MAX (nbr_lines, 1);
	current_line = GTK_get_line_position ();
	if (nbr_lines <= current_line)
	{
		GTK_set_line_position (nbr_lines - 1);
	}

	/* Sauvegarde des donnees pour l'undo */
	if (nbr_lines < old_nbr_lines)
	{
		for (track_type = 0; track_type < Pattern_NBR_TYPES; track_type ++)
		{
			for (track = 0; track < GTK_nbr_tracks [track_type]; track ++)
			{
				undo_list.add (&UndoCellPatClear (track_type, pattern,
				                                  nbr_lines, track,
				                                  old_nbr_lines - nbr_lines));
			}
		}
	}
	if (nbr_lines != old_nbr_lines)
	{
		sprintf (text_0, "Pattern height changed to %d lines", nbr_lines);
		undo_list.add (&UndoCellPatChgHeight (pattern, old_nbr_lines));
		GTK_undo_list_ptr->add_action (UndoCellList (undo_list), text_0);
	}

	if (PAT_set_pattern_height (pattern, nbr_lines))
	{

		/*** Faire quelque chose si erreur ***/

		LOG_printf ("PAT_set_pattern_height_intr: Error: couldn't change pattern height.\n");
	}
	MPAN_display_var_icons (true, true);
	INTR_pattern_editor_track_ptr->refresh_dynamic (true);
}



/*==========================================================================*/
/*      Nom: PAT_set_nbr_tracks_intr                                        */
/*      Description: Change le nombre de pistes pour un type de pattern de  */
/*                   differentes manieres. L'interface est ici mise en jeu. */
/*      Parametres en entree:                                               */
/*        - type: 0 = Le parametre est absolu,                              */
/*                1 = Le parametre est relatif,                             */
/*                2 = Entree au clavier.                                    */
/*        - value: le parametre en question.                                */
/*        - track_type: type de pattern dont on veut changer le nombre de   */
/*                      pistes.                                             */
/*==========================================================================*/

void	PAT_set_nbr_tracks_intr (int type, signed int value, int track_type)
{
	int		pattern;
	int		track;
	int		new_value;
	int		old_value;
	int		track_type_2;
	int		total;
	int		min_value;
	char		nbr2_0 [2+1];
	char		text_0 [99+1];
	int		object_list [Pattern_NBR_TYPES] =
	{
		RSC_OBJ_MP_SUBM_TOOLS_TRK_NUM_SPL_VAL,
		RSC_OBJ_MP_SUBM_TOOLS_TRK_NUM_AIN_VAL,
		RSC_OBJ_MP_SUBM_TOOLS_TRK_NUM_FX_VAL,
		RSC_OBJ_MP_SUBM_TOOLS_TRK_NUM_MID_VAL
	};
	signed int	ret_val;
	List		undo_list;

	if (track_type == Pattern_TYPE_AIN)
	{
		return;
	}

	new_value = GTK_nbr_tracks [track_type];
	old_value = new_value;

	switch (type)
	{
	/* Position relative */
	case	INTR_CHGTYPE_REL:
		new_value += value;
		break;

	/* Entree au clavier */
	case	INTR_CHGTYPE_KBD:
		sprintf (nbr2_0, "%2d", new_value);
		EDIT_edit_string_base (nbr2_0, object_list [track_type], 2, 10);
		new_value = (int) strtol (nbr2_0, NULL, 10);
		break;

	/* Position absolue  */
	case	INTR_CHGTYPE_ABS:
		new_value = value;
		break;
	}

	total = 0;
	for (track_type_2 = 0; track_type_2 < Pattern_NBR_TYPES; track_type_2 ++)
	{
		if (track_type_2 != track_type)
		{
			total += GTK_nbr_tracks [track_type_2];
		}
	}
	min_value = (track_type == Pattern_TYPE_SPL) ? 1 : 0;
	new_value = MIN (new_value, GTK_NBRTRACKS_MAXI - total);
	new_value = MAX (new_value, min_value);

	/* Confirmation si effacement de pistes */
	if (new_value < old_value)
	{
		if (INTR_dialog_box ("CHANGE NUMBER OF TRACKS", "Delete tracks \?", "OK\nCancel", 0, 1) != 0)
		{
			INTR_pattern_editor_menu_ptr->refresh ();
			return;
		}
	}

	/* Pas de changement */
	else if (new_value == old_value)
	{
		INTR_pattern_editor_menu_ptr->refresh ();
		return;
	}

	INTR_graph_ptr->mouse_bee ();

	/* Sauvegarde des donnees pour l'undo */
	if (new_value < old_value)
	{
		for (pattern = 0; pattern < PAT_NBRPATTERNS_MAXI; pattern ++)	// On ne sauve pas les patterns bidons
		{
			for (track = new_value; track < old_value; track ++)
			{
				undo_list.add (&UndoCellPatClear (track_type, pattern, 0, track,
				                                  PAT_get_pattern_height (pattern)));
			}
		}
	}
	sprintf (text_0, "Number of tracks changed to %d", new_value);
	undo_list.add (&UndoCellPatChgTrk (track_type, old_value));
	GTK_undo_list_ptr->add_action (UndoCellList (undo_list), text_0);

	/* Change le nombre de pistes */
	INTR_stop_song ();
	ret_val = PAT_set_nbr_tracks (track_type, new_value);
	INTR_graph_ptr->mouse_arrow ();
	if (ret_val)
	{
		INTR_dialog_box ("CHANGE NUMBER OF TRACKS", "Error reported.", "Cancel", 0, 0);
	}

	TRK_display_mute ();
	RSC_display_object (RSC_OBJ_MP_TRK_DATA);
	INTR_pattern_editor_track_ptr->redraw ();
	TRK_display_presets ();
	if (track_type == TRK_preset_data [TRK_preset_nbr].track_type)
	{
		INTR_pattern_editor_menu_ptr->refresh ();
	}
	else
	{
		INTR_pattern_editor_menu_ptr->redraw ();
	}
}



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



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