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

GRAOUMF TRACKER 2

Copyright (c) 1996 - 2002 Laurent de Soras

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

Contact the author : laurent@ohmforce.com
More information about this license : http://www.gnu.org/licenses/gpl.html

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



/*\\\ 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	"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;
	PLAY_SOURCE_TRACK_CONF_BLOCK	*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 */
	GTK_mutex.wait ();

	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].track_type == track_type)
					{
						if (track_conf_ptr [src_track].track_nbr >= nbr_tracks)
						{
							if (nbr_s_tracks > 1)
							{
								if (src_track + 1 < nbr_s_tracks)
								{
									memmove (track_conf_ptr + src_track,
												track_conf_ptr + src_track + 1,
												  (nbr_s_tracks - src_track - 1)
												* sizeof (*track_conf_ptr));
								}
								src_track --;
								nbr_s_tracks --;
							}
							else
							{
								track_conf_ptr [0].track_type = Pattern_TYPE_SPL;
								track_conf_ptr [0].wet_flag = true;
								track_conf_ptr [0].track_nbr = 0;
								track_conf_ptr [0].inpvol = 0x1000;
								track_conf_ptr [0].inppan = 0x8000U;
								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].track_type = track_type;
					track_conf_ptr [src_track].wet_flag = true;
					track_conf_ptr [src_track].track_nbr = old_nbr_tracks + track;
					track_conf_ptr [src_track].cr.old_vol [0] = 0;
					track_conf_ptr [src_track].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].inpvol = 0x1000;
					track_conf_ptr [src_track].inppan = 0x8000U;
				}

				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))
		{
			GTK_mutex.signal ();
			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;

	GTK_mutex.signal ();

	/* 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:                                                             */
/*==========================================================================*/
