/* GSequencer - Advanced GTK Sequencer
 * Copyright (C) 2005-2017 Joël Krähemann
 *
 * This file is part of GSequencer.
 *
 * GSequencer 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 3 of the License, or
 * (at your option) any later version.
 *
 * GSequencer 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 GSequencer.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <ags/X/ags_machine_callbacks.h>

#include <ags/libags.h>
#include <ags/libags-audio.h>
#include <ags/libags-gui.h>

#include <ags/X/ags_window.h>
#include <ags/X/ags_pad.h>
#include <ags/X/ags_automation_editor.h>
#include <ags/X/ags_notation_editor.h>
#include <ags/X/ags_machine_editor.h>
#include <ags/X/ags_connection_editor.h>
#include <ags/X/ags_midi_dialog.h>

#include <ags/X/thread/ags_gui_thread.h>

#include <ags/X/editor/ags_envelope_dialog.h>
#include <ags/X/editor/ags_machine_radio_button.h>
#include <ags/X/editor/ags_file_selection.h>

#include <ags/i18n.h>

#define AGS_RENAME_ENTRY "AgsRenameEntry"

int ags_machine_popup_rename_response_callback(GtkWidget *widget, gint response, AgsMachine *machine);
int ags_machine_popup_properties_destroy_callback(GtkWidget *widget, AgsMachine *machine);

int
ags_machine_button_press_callback(GtkWidget *handle_box, GdkEventButton *event, AgsMachine *machine)
{
  AgsWindow *window = AGS_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET(handle_box)));

  if(event->button == 3){
    gtk_menu_popup (GTK_MENU (machine->popup),
                    NULL, NULL, NULL, NULL,
                    event->button, event->time);
  }else if(event->button == 1){
    window->selected = machine;
  }

  return(0);
}

void
ags_machine_popup_move_up_activate_callback(GtkWidget *widget, AgsMachine *machine)
{
  GValue val={0,};

  g_value_init (&val, G_TYPE_INT);

  gtk_container_child_get_property(GTK_CONTAINER(GTK_WIDGET(machine)->parent),
				   GTK_WIDGET(machine),
				   "position", &val);

  if(g_value_get_int (&val) > 0){
    gtk_box_reorder_child(GTK_BOX(GTK_WIDGET(machine)->parent),
			  GTK_WIDGET(machine),
			  g_value_get_int (&val) - 1);
  }

  g_value_unset (&val);
}

void
ags_machine_popup_move_down_activate_callback(GtkWidget *widget, AgsMachine *machine)
{
  GValue val={0,};

  g_value_init (&val, G_TYPE_INT);

  gtk_container_child_get_property(GTK_CONTAINER(GTK_WIDGET(machine)->parent),
				   GTK_WIDGET(machine),
				   "position", &val);

  if(g_value_get_int (&val) < g_list_length(gtk_container_get_children((GtkContainer *) GTK_WIDGET(machine)->parent)) - 1){
    gtk_box_reorder_child(GTK_BOX(GTK_WIDGET(machine)->parent),
			  GTK_WIDGET(machine),
			  g_value_get_int (&val) + 1);
  }

  g_value_unset (&val);
}

void
ags_machine_popup_hide_activate_callback(GtkWidget *widget, AgsMachine *machine)
{
  gtk_widget_hide(GTK_BIN(gtk_container_get_children((GtkContainer *) GTK_WIDGET(machine))->data)->child);
}

void
ags_machine_popup_show_activate_callback(GtkWidget *widget, AgsMachine *machine)
{
  gtk_widget_show(GTK_BIN(gtk_container_get_children((GtkContainer *) GTK_WIDGET(machine))->data)->child);
}

void
ags_machine_popup_destroy_activate_callback(GtkWidget *widget, AgsMachine *machine)
{
  AgsWindow *window;

  AgsAudio *audio;
  
  AgsRemoveAudio *remove_audio;

  AgsMutexManager *mutex_manager;
  AgsAudioLoop *audio_loop;
  AgsGuiThread *gui_thread;
  AgsTaskThread *task_thread;

  AgsApplicationContext *application_context;

  GObject *soundcard;
  
  GList *list, *list_start;

  pthread_mutex_t *application_mutex;
  
  mutex_manager = ags_mutex_manager_get_instance();
  application_mutex = ags_mutex_manager_get_application_mutex(mutex_manager);

  window = (AgsWindow *) gtk_widget_get_toplevel((GtkWidget *) machine);

  application_context = (AgsApplicationContext *) window->application_context;
  
  /* destroy editor */
  list =
    list_start = gtk_container_get_children((GtkContainer *) window->notation_editor->machine_selector);

  list = list->next;

  while(list != NULL){
    if(AGS_IS_MACHINE_RADIO_BUTTON(list->data) && AGS_MACHINE_RADIO_BUTTON(list->data)->machine == machine){
      gtk_widget_destroy(list->data);
      break;
    }
    
    list = list->next;
  }

  g_list_free(list_start);

  /* destroy automation editor */  
  list =
    list_start = gtk_container_get_children((GtkContainer *) window->automation_window->automation_editor->machine_selector);

  list = list->next;

  while(list != NULL){
    if(AGS_IS_MACHINE_RADIO_BUTTON(list->data) && AGS_MACHINE_RADIO_BUTTON(list->data)->machine == machine){
      gtk_widget_destroy(list->data);
      break;
    }
    
    list = list->next;
  }

  g_list_free(list_start);

  /* destroy machine */
  soundcard = window->soundcard;
  
  audio = machine->audio;
  g_object_ref(audio);

  ags_connectable_disconnect(AGS_CONNECTABLE(machine));
  gtk_widget_destroy((GtkWidget *) machine);
  
  /* get audio loop */
  pthread_mutex_lock(application_mutex);

  audio_loop = (AgsAudioLoop *) application_context->main_loop;
  
  pthread_mutex_unlock(application_mutex);

  /* get task thread */
  task_thread = (AgsTaskThread *) ags_thread_find_type((AgsThread *) audio_loop,
						       AGS_TYPE_TASK_THREAD);

  gui_thread = (AgsGuiThread *) ags_thread_find_type((AgsThread *) audio_loop,
						     AGS_TYPE_GUI_THREAD);

  remove_audio = ags_remove_audio_new(soundcard,
				      audio);
  g_object_set(remove_audio,
	       "task-thread", task_thread,
	       NULL);
  
  ags_gui_thread_schedule_task(gui_thread,
			       remove_audio);
}

void
ags_machine_popup_rename_activate_callback(GtkWidget *widget, AgsMachine *machine)
{
  GtkDialog *dialog;
  GtkEntry *entry;

  if(machine->rename != NULL){
    return;
  }
  
  machine->rename =
    dialog = (GtkDialog *) gtk_dialog_new_with_buttons(i18n("rename"),
						       (GtkWindow *) gtk_widget_get_toplevel(GTK_WIDGET(machine)),
						       GTK_DIALOG_DESTROY_WITH_PARENT,
						       GTK_STOCK_OK,
						       GTK_RESPONSE_ACCEPT,
						       GTK_STOCK_CANCEL,
						       GTK_RESPONSE_REJECT,
						       NULL);

  entry = (GtkEntry *) gtk_entry_new();
  gtk_entry_set_text(entry, machine->machine_name);
  gtk_box_pack_start((GtkBox *) dialog->vbox,
		     (GtkWidget *) entry,
		     FALSE, FALSE,
		     0);

  gtk_widget_show_all((GtkWidget *) dialog);

  g_signal_connect((GObject *) dialog, "response",
		   G_CALLBACK(ags_machine_popup_rename_response_callback), (gpointer) machine);

  return;
}

int
ags_machine_popup_rename_response_callback(GtkWidget *widget, gint response, AgsMachine *machine)
{
  if(response == GTK_RESPONSE_ACCEPT){
    gchar *str;
    
    str = gtk_editable_get_chars(GTK_EDITABLE(gtk_container_get_children((GtkContainer *) GTK_DIALOG(widget)->vbox)->data),
				 0, -1);
    g_object_set(machine,
		 "machine-name", str,
		 NULL);        
  }
  
  machine->rename = NULL;
  gtk_widget_destroy(widget);

  return(0);
}

void
ags_machine_popup_properties_activate_callback(GtkWidget *widget, AgsMachine *machine)
{
  machine->properties = (GtkDialog *) ags_machine_editor_new(machine);
  g_signal_connect_after(machine->properties, "destroy",
			 G_CALLBACK(ags_machine_popup_properties_destroy_callback), machine);

  gtk_window_set_default_size((GtkWindow *) machine->properties, -1, 400);
  
  ags_connectable_connect(AGS_CONNECTABLE(machine->properties));

  ags_applicable_reset(AGS_APPLICABLE(machine->properties));

  gtk_widget_show_all((GtkWidget *) machine->properties);
}

void
ags_machine_popup_sticky_controls_toggled_callback(GtkWidget *widget, AgsMachine *machine)
{
  if(gtk_check_menu_item_get_active(widget)){
    machine->flags |= AGS_MACHINE_STICKY_CONTROLS;
  }else{
    machine->flags &= (~AGS_MACHINE_STICKY_CONTROLS);
  }
}

int
ags_machine_popup_properties_destroy_callback(GtkWidget *widget, AgsMachine *machine)
{
  machine->properties = NULL;

  return(0);
}

void
ags_machine_popup_copy_pattern_callback(GtkWidget *widget, AgsMachine *machine)
{
  ags_machine_copy_pattern(machine);
}

void
ags_machine_popup_paste_pattern_callback(GtkWidget *widget, AgsMachine *machine)
{
  //TODO:JK: implement me
}

void
ags_machine_popup_envelope_callback(GtkWidget *widget, AgsMachine *machine)
{
  AgsEnvelopeDialog *envelope_dialog;
  
  if(machine->envelope_dialog == NULL){
    envelope_dialog = ags_envelope_dialog_new(machine);

    if((AGS_MACHINE_IS_SEQUENCER & (machine->flags)) != 0){
      ags_envelope_dialog_add_pattern_tab(envelope_dialog);
    }
    
    machine->envelope_dialog = envelope_dialog;
    
    ags_connectable_connect(AGS_CONNECTABLE(envelope_dialog));
    ags_applicable_reset(AGS_APPLICABLE(envelope_dialog));

    gtk_widget_show_all(envelope_dialog);
  }
}

void
ags_machine_popup_connection_editor_callback(GtkWidget *widget, AgsMachine *machine)
{
  AgsConnectionEditor *connection_editor;
  
  if(machine->connection_editor == NULL){
    connection_editor = ags_connection_editor_new(machine);
    machine->connection_editor = (GtkDialog *) connection_editor;
    
    ags_connectable_connect(AGS_CONNECTABLE(connection_editor));
    ags_applicable_reset(AGS_APPLICABLE(connection_editor));

    gtk_widget_show_all((GtkWidget *) connection_editor);
  }else{
    connection_editor = (AgsConnectionEditor *) machine->connection_editor;
  }

  gtk_widget_show_all((GtkWidget *) connection_editor);
}

void
ags_machine_popup_midi_dialog_callback(GtkWidget *widget, AgsMachine *machine)
{
  AgsMidiDialog *midi_dialog;
  
  if(machine->midi_dialog == NULL){
    midi_dialog = ags_midi_dialog_new(machine);
    machine->midi_dialog = (GtkDialog *) midi_dialog;
    midi_dialog->flags |= (AGS_MIDI_DIALOG_IO_OPTIONS |
			   AGS_MIDI_DIALOG_MAPPING |
			   AGS_MIDI_DIALOG_DEVICE);

    ags_connectable_connect(AGS_CONNECTABLE(midi_dialog));
    ags_applicable_reset(AGS_APPLICABLE(midi_dialog));

    gtk_widget_show_all((GtkWidget *) midi_dialog);
  }else{
    midi_dialog = (AgsMidiDialog *) machine->midi_dialog;
  }

  gtk_widget_show_all((GtkWidget *) midi_dialog);
}

void
ags_machine_open_response_callback(GtkDialog *dialog, gint response, AgsMachine *machine)
{
  GtkFileChooserDialog *file_chooser;
  GtkCheckButton *overwrite;
  GtkCheckButton *create;
  GSList *filenames;

  file_chooser = GTK_FILE_CHOOSER_DIALOG(dialog);

  if(response == GTK_RESPONSE_ACCEPT){
    filenames = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(file_chooser));
    overwrite = g_object_get_data(G_OBJECT(dialog), "overwrite");
    create = g_object_get_data(G_OBJECT(dialog), "create");

    ags_machine_open_files(machine,
			   filenames,
			   GTK_TOGGLE_BUTTON(overwrite)->active,
			   GTK_TOGGLE_BUTTON(create)->active);
  }

  gtk_widget_destroy(GTK_WIDGET(file_chooser));
}

void
ags_machine_open_extended_response_callback(GtkWidget *widget, gint response, AgsMachine *machine)
{
  GtkFileChooserDialog *file_chooser;
  AgsFileSelection *file_selection;
  GtkCheckButton *overwrite;
  GtkCheckButton *create;
  GSList *filenames;
  gchar *current_folder;
  GError *error;

  file_chooser = (GtkFileChooserDialog *) gtk_widget_get_toplevel(widget);

  if(response == GTK_RESPONSE_ACCEPT){
    filenames = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(file_chooser));
    overwrite = g_object_get_data((GObject *) widget, "overwrite");
    create = g_object_get_data((GObject *) widget, "create");

    current_folder = gtk_file_chooser_get_current_folder(GTK_FILE_CHOOSER(file_chooser));
    //TODO:JK: you need to check against recently used
    //TODO:JK: add more file types to AgsFileSelection

    /* check for supported packed audio files */
    file_selection = (AgsFileSelection *) gtk_file_chooser_get_extra_widget(GTK_FILE_CHOOSER(file_chooser));

    if(file_selection != NULL && g_strcmp0(file_selection->directory, current_folder)){
      gtk_widget_destroy(GTK_WIDGET(file_selection));

      file_selection = NULL;
    }

    if(file_selection == NULL ||
       (AGS_FILE_SELECTION_COMPLETED & (file_selection->flags)) == 0){

      if((AGS_MACHINE_ACCEPT_SOUNDFONT2 & (machine->file_input_flags)) != 0){
	GDir *current_directory;
	GList *new_entry, *old_entry;	  
	GSList *slist;
	gchar *current_filename;
	
	slist = filenames;
	new_entry = NULL;
	
	while(slist != NULL){
	  if(g_str_has_suffix(slist->data,
			      ".sf2")){
	    AgsFileSelectionEntry *entry;
	    
	    
	    entry = ags_file_selection_entry_alloc();
	    entry->filename = slist->data;
	  
	    new_entry = g_list_prepend(new_entry,
				       entry);
	  }
	  
	  slist = slist->next;
	}
	
	old_entry = NULL;
	
	if(file_selection == NULL){
	  if(new_entry != NULL){
	    file_selection = ags_file_selection_new();
	    gtk_file_chooser_set_extra_widget(GTK_FILE_CHOOSER(file_chooser),
					      GTK_WIDGET(file_selection));

	    ags_file_selection_set_entry(file_selection,
					 new_entry);
	    ags_connectable_connect(AGS_CONNECTABLE(file_selection));

	    gtk_widget_show_all(GTK_WIDGET(file_selection));

	    return;
	  }
	}else if(AGS_IS_FILE_SELECTION(file_selection)){
	  GList *really_new_entry;
	  GList *list;
	  
	  old_entry = file_selection->entry;
	  list = new_entry;
	  really_new_entry = NULL;
	  
	  /* check against existing entries */
	  if(new_entry != NULL){
	    while(list != NULL){
	      if(g_list_find(old_entry, list->data) == NULL){
		really_new_entry = g_list_prepend(really_new_entry,
						  list->data);
	      }else{
		free(list->data);
	      }
	      
	      list = list->next;
	    }
	    
	    g_list_free(new_entry);
	  }
	  
	  ags_file_selection_set_entry(file_selection,
				       really_new_entry);

	  /* adding lost files */
	  //TODO:JK: figure out if you need to copy the GSList of filenames
	  gtk_file_chooser_select_all(GTK_FILE_CHOOSER(file_chooser));
	  
	  current_directory = g_dir_open(current_folder,
					 0,
					 &error);
	  
	  while((current_filename = (gchar *) g_dir_read_name(current_directory)) != NULL){
	    if(!g_strcmp0(".", current_filename) ||
	       !g_strcmp0("..", current_filename))
	      continue;

	    if(!ags_file_selection_contains_file(file_selection,
						 current_filename) &&
	       g_slist_find(filenames, current_filename) == NULL){
	      gtk_file_chooser_unselect_filename(GTK_FILE_CHOOSER(file_chooser),
						 current_filename);
	    }
	  }
	  
	  g_dir_close(current_directory);
	  
	  return;
	}
      }
    }
    
    //TODO:JK: fix GSList filenames memory leak
    ags_machine_open_files(machine,
			   filenames,
			   overwrite->toggle_button.active,
			   create->toggle_button.active);
  }
}

void
ags_machine_play_callback(GtkWidget *toggle_button, AgsMachine *machine)
{
  if(machine == NULL){
    return;
  }

  if(GTK_TOGGLE_BUTTON(toggle_button)->active){
    if((AGS_MACHINE_BLOCK_PLAY & (machine->flags)) != 0){
      
      return;
    }

    g_message("machine: on");

    machine->flags |= AGS_MACHINE_BLOCK_PLAY;

    ags_machine_set_run_extended(machine,
				 TRUE,
				 TRUE, FALSE);

    machine->flags &= (~AGS_MACHINE_BLOCK_PLAY);
  }else{
    if((AGS_MACHINE_BLOCK_STOP & (machine->flags)) != 0){
      
      return;
    }

    g_message("machine: off");

    machine->flags |= AGS_MACHINE_BLOCK_STOP;

    ags_machine_set_run_extended(machine,
				 FALSE,
				 TRUE, FALSE);

    machine->flags &= (~AGS_MACHINE_BLOCK_STOP);
  }
}

void
ags_machine_resize_audio_channels_callback(AgsMachine *machine,
					   guint audio_channels, guint audio_channels_old,
					   gpointer data)
{
  AgsWindow *window;
  
  GList *pad_list;
  GList *line_list;
  
  guint i;
  
  window = (AgsWindow *) gtk_widget_get_toplevel((GtkWidget *) machine);

  /* resize */
  if((AGS_MACHINE_CONNECTED & (machine->flags)) != 0){
    if(audio_channels > audio_channels_old){
      if(machine->input != NULL){
	pad_list = gtk_container_get_children(GTK_CONTAINER(machine->input));
      
	while(pad_list != NULL){
	  line_list = gtk_container_get_children((GtkContainer *) AGS_PAD(pad_list->data)->expander_set);
	  line_list = g_list_nth(line_list,
				 audio_channels_old);
	
	  for(i = 0; i < audio_channels - audio_channels_old; i++){
	    ags_connectable_connect(AGS_CONNECTABLE(line_list->data));

	    line_list = line_list->next;
	  }
	
	  pad_list = pad_list->next;
	}
      }

      if(machine->output != NULL){
	pad_list = gtk_container_get_children(GTK_CONTAINER(machine->output));
      
	while(pad_list != NULL){
	  line_list = gtk_container_get_children((GtkContainer *) AGS_PAD(pad_list->data)->expander_set);
	  line_list = g_list_nth(line_list,
				 audio_channels_old);
	
	  for(i = 0; i < audio_channels - audio_channels_old; i++){
	    ags_connectable_connect(AGS_CONNECTABLE(line_list->data));

	    line_list = line_list->next;
	  }
	  
	  pad_list = pad_list->next;
	}
      }
    }
  }
}

void
ags_machine_resize_pads_callback(AgsMachine *machine, GType channel_type,
				 guint pads, guint pads_old,
				 gpointer data)
{
  AgsWindow *window;

  GList *pad_list;
  
  window = (AgsWindow *) gtk_widget_get_toplevel((GtkWidget *) machine);

  /* resize */
  if((AGS_MACHINE_CONNECTED & (machine->flags)) != 0){
    if(pads > pads_old){
      if(g_type_is_a(channel_type,
		     AGS_TYPE_INPUT)){
	if(machine->input != NULL){
	  pad_list = gtk_container_get_children(GTK_CONTAINER(machine->input));
	  pad_list = g_list_nth(pad_list,
				pads_old);
      
	  while(pad_list != NULL){
	    ags_connectable_connect(AGS_CONNECTABLE(pad_list->data));
	
	    pad_list = pad_list->next;
	  }
	}
      }

      if(g_type_is_a(channel_type,
		     AGS_TYPE_OUTPUT)){
	if(machine->output != NULL){
	  pad_list = gtk_container_get_children(GTK_CONTAINER(machine->output));
	  pad_list = g_list_nth(pad_list,
				pads_old);
      
	  while(pad_list != NULL){
	    ags_connectable_connect(AGS_CONNECTABLE(pad_list->data));
	
	    pad_list = pad_list->next;
	  }
	}
      }
    }
  }
}

void
ags_machine_done_callback(AgsMachine *machine,
			  AgsRecallID *recall_id,
			  gpointer data)
{
  AgsMutexManager *mutex_manager;

  gboolean reset_active;

  pthread_mutex_t *application_mutex;
  pthread_mutex_t *audio_mutex;
  
  if((AGS_MACHINE_BLOCK_STOP & (machine->flags)) != 0){
    return;
  }
  
  machine->flags |= AGS_MACHINE_BLOCK_STOP;

  mutex_manager = ags_mutex_manager_get_instance();
  application_mutex = ags_mutex_manager_get_application_mutex(mutex_manager);

  /* get audio mutex */
  pthread_mutex_lock(application_mutex);
    
  audio_mutex = ags_mutex_manager_lookup(mutex_manager,
					 (GObject *) machine->audio);
  
  pthread_mutex_unlock(application_mutex);

  /* play button - check reset active */
  pthread_mutex_lock(audio_mutex);
  
  reset_active = ((AGS_RECALL_ID_SEQUENCER & (recall_id->flags)) != 0) ? TRUE: FALSE;

  pthread_mutex_unlock(audio_mutex);
  
  if(reset_active){
    gtk_toggle_button_set_active(machine->play, FALSE);
  }
  
  machine->flags &= (~AGS_MACHINE_BLOCK_STOP);
}
