
/*
 * CINELERRA
 * Copyright (C) 1997-2014 Adam Williams <broadcast at earthling dot net>
 *
 * 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
 *
 */

#include "apatchgui.h"
#include "automation.h"
#include "floatauto.h"
#include "floatautos.h"
#include "clip.h"
#include "edl.h"
#include "edlsession.h"
#include "filexml.h"
#include "intauto.h"
#include "intautos.h"
#include "language.h"
#include "localsession.h"
#include "mainundo.h"
#include "mwindow.h"
#include "mwindowgui.h"
#include "patchbay.h"
#include "patchgui.h"
#include "mainsession.h"
#include "strack.h"
#include "theme.h"
#include "timelinepane.h"
#include "track.h"
#include "trackcanvas.h"
#include "tracks.h"
#include "vpatchgui.h"






NudgePopup::NudgePopup(MWindow *mwindow, PatchBay *patchbay)
 : BC_PopupMenu(0,
		0,
		0,
		"",
		0)
{
	this->mwindow = mwindow;
	this->patchbay = patchbay;
}

NudgePopup::~NudgePopup()
{
}


void NudgePopup::create_objects()
{
	add_item(seconds_item = new NudgePopupSeconds(this));
	add_item(native_item = new NudgePopupNative(this));
}

void NudgePopup::activate_menu(PatchGUI *gui)
{
// Set checked status
	seconds_item->set_checked(mwindow->edl->session->nudge_format ? 1 : 0);
	native_item->set_checked(mwindow->edl->session->nudge_format ? 0 : 1);

// Set native units to track format
	native_item->set_text(gui->track->data_type == TRACK_AUDIO ?
		(char*)_("Samples") :
		(char*)_("Frames"));

// Show it
	BC_PopupMenu::activate_menu();
}



NudgePopupSeconds::NudgePopupSeconds(NudgePopup *popup)
 : BC_MenuItem(_("Seconds"))
{
	this->popup = popup;
}

int NudgePopupSeconds::handle_event()
{
	popup->mwindow->edl->session->nudge_format = 1;
	popup->mwindow->gui->update_patchbay();
	return 1;
}





NudgePopupNative::NudgePopupNative(NudgePopup *popup)
 : BC_MenuItem("")
{
	this->popup = popup;
}

int NudgePopupNative::handle_event()
{
	popup->mwindow->edl->session->nudge_format = 0;
	popup->mwindow->gui->update_patchbay();
	return 1;
}









PatchBay::PatchBay(MWindow *mwindow, MWindowGUI *gui)
 : BC_SubWindow(mwindow->theme->patchbay_x,
 	mwindow->theme->patchbay_y,
	mwindow->theme->patchbay_w,
	mwindow->theme->patchbay_h)
{
	this->mwindow = mwindow;
	this->gui = gui;
	this->pane = 0;
	drag_operation = Tracks::NONE;
	for(int i = 0; i < TRANSFER_TYPES; i++) mode_icons[i] = 0;
// *** CONTEXT_HELP ***
	context_help_set_keyword("The Patchbay");
}

PatchBay::PatchBay(MWindow *mwindow,
	TimelinePane *pane,
	int x,
	int y,
	int w,
	int h)
 : BC_SubWindow(x,
 	y,
	w,
	h)
{
	this->mwindow = mwindow;
	this->gui = mwindow->gui;
	this->pane = pane;
	drag_operation = Tracks::NONE;
	for(int i = 0; i < TRANSFER_TYPES; i++) mode_icons[i] = 0;
// *** CONTEXT_HELP ***
	context_help_set_keyword("The Patchbay");
// printf("PatchBay::PatchBay %d %d %d %d %d\n",
// __LINE__,
// x,
// y,
// w,
// h);
}

PatchBay::~PatchBay()
{
	delete_all_patches();
	for(int i = 0; i < TRANSFER_TYPES; i++) delete mode_icons[i];
}


int PatchBay::delete_all_patches()
{
    patches.remove_all_objects();
    return 0;
}

void PatchBay::create_objects()
{
	draw_top_background(get_parent(), 0, 0, get_w(), get_h());
	flash(0);

// Create icons for mode types
	static const char *mode_types[] = {
		"mode_normal",
		"mode_add",
		"mode_subtract",
		"mode_multiply",
		"mode_divide",
		"mode_replace",
		"mode_max",
		"mode_min",
		"mode_darken",
		"mode_lighten",
		"mode_dst",
		"mode_dstatop",
		"mode_dstin",
		"mode_dstout",
		"mode_dstover",
		"mode_src",
		"mode_srcatop",
		"mode_srcin",
		"mode_srcout",
		"mode_srcover",
		"mode_and",
		"mode_or",
		"mode_xor",
		"mode_overlay",
		"mode_screen",
		"mode_burn",
		"mode_dodge",
		"mode_hardlight",
		"mode_softlight",
		"mode_difference",
	};
	for( int mode=0; mode<TRANSFER_TYPES; ++mode ) {
		mode_icons[mode] = new BC_Pixmap(this,
			mwindow->theme->get_image(mode_types[mode]),
			PIXMAP_ALPHA);
	}

	add_subwindow(nudge_popup = new NudgePopup(mwindow, this));
	nudge_popup->create_objects();
	update();
}

BC_Pixmap* PatchBay::mode_to_icon(int mode)
{
	return mode_icons[mode];
}

int PatchBay::icon_to_mode(BC_Pixmap *icon)
{
	for(int i = 0; i < TRANSFER_TYPES; i++)
		if(icon == mode_icons[i]) return i;
	return TRANSFER_NORMAL;
}

void PatchBay::resize_event()
{
	reposition_window(mwindow->theme->patchbay_x,
		mwindow->theme->patchbay_y,
		mwindow->theme->patchbay_w,
		mwindow->theme->patchbay_h);
	draw_top_background(get_parent(), 0, 0, get_w(), get_h());
	update();
	flash(0);
}

void PatchBay::resize_event(int x, int y, int w, int h)
{
	reposition_window(x,
		y,
		w,
		h);
	draw_top_background(get_parent(), 0, 0, w, h);
	update();
	flash(0);
}

int PatchBay::button_press_event()
{
	int result = 0;
// Too much junk to support the wheel
	return result;
}

int PatchBay::cursor_motion_event()
{
	//int cursor_x = get_relative_cursor_x();
	int cursor_y = get_relative_cursor_y();
	int update_gui = 0;

	if(drag_operation != Tracks::NONE)
	{
		if(cursor_y >= 0 &&
			cursor_y < get_h())
		{
// Get track we're inside of
			for(Track *track = mwindow->edl->tracks->first;
				track;
				track = track->next)
			{
				if( track->is_hidden() ) continue;
				int y = track->y_pixel - mwindow->edl->local_session->track_start[pane->number];
				int h = track->vertical_span(mwindow->theme);
				if(cursor_y >= y && cursor_y < y + h)
				{
					switch(drag_operation)
					{
						case Tracks::PLAY:
							if(track->play != new_status)
							{
								track->play = new_status;
								mwindow->gui->unlock_window();
								mwindow->restart_brender();
								mwindow->sync_parameters(CHANGE_EDL);
								mwindow->gui->lock_window();
								update_gui = 1;
							}
							break;
						case Tracks::RECORD:
							if(track->armed != new_status)
							{
								track->armed = new_status;
								update_gui = 1;
							}
							break;
						case Tracks::GANG:
							if(track->ganged != new_status)
							{
								track->ganged = new_status;
								update_gui = 1;
							}
							break;
						case Tracks::DRAW:
							if(track->draw != new_status)
							{
								track->draw = new_status;
								update_gui = 1;
							}
							break;
						case Tracks::EXPAND:
							if(track->expand_view != new_status)
							{
								track->expand_view = new_status;
								mwindow->edl->tracks->update_y_pixels(mwindow->theme);
								gui->draw_trackmovement();
								update_gui = 0;
							}
							break;
						case Tracks::MUTE:
						{
							IntAuto *current = 0;
							Auto *keyframe = 0;
							double position = mwindow->edl->local_session->get_selectionstart(1);
							Autos *mute_autos = track->automation->autos[AUTOMATION_MUTE];

							current = (IntAuto*)mute_autos->get_prev_auto(PLAY_FORWARD,
								keyframe);

							if(current->value != new_status)
							{

//								mwindow->undo->update_undo_before(_("keyframe"), this);
								current = (IntAuto*)mute_autos->get_auto_for_editing(position);

								current->value = new_status;

//								mwindow->undo->update_undo_after(_("keyframe"), LOAD_AUTOMATION);

								mwindow->gui->unlock_window();
								mwindow->restart_brender();
								mwindow->sync_parameters(CHANGE_PARAMS);
								mwindow->gui->lock_window();

								if(mwindow->edl->session->auto_conf->autos[AUTOMATION_MUTE])
								{
									gui->draw_overlays(1);
								}
								update_gui = 1;
							}
							break;
						}
					}
				}
			}
		}
	}

	if(update_gui)
	{
		gui->update_patchbay();
	}
	return 0;
}

void PatchBay::set_meter_format(int mode, int min, int max)
{
	for(int i = 0; i < patches.total; i++)
	{
		PatchGUI *patchgui = patches.values[i];
		if(patchgui->data_type == TRACK_AUDIO)
		{
			APatchGUI *apatchgui = (APatchGUI*)patchgui;
			if(apatchgui->meter)
			{
				apatchgui->meter->change_format(mode, min, max);
			}
		}
	}
}

void PatchBay::update_meters(ArrayList<double> *module_levels)
{
	for(int level_number = 0, patch_number = 0;
		patch_number < patches.total && level_number < module_levels->total;
		patch_number++)
	{
		APatchGUI *patchgui = (APatchGUI*)patches.values[patch_number];

		if(patchgui->data_type == TRACK_AUDIO)
		{
			if(patchgui->meter)
			{
				double level = module_levels->values[level_number];
				patchgui->meter->update(level, level > 1);
			}

			level_number++;
		}
	}
}

void PatchBay::reset_meters()
{
	for(int patch_number = 0;
		patch_number < patches.total;
		patch_number++)
	{
		APatchGUI *patchgui = (APatchGUI*)patches.values[patch_number];
		if(patchgui->data_type == TRACK_AUDIO && patchgui->meter)
		{
			patchgui->meter->reset_over();
		}
	}
}

void PatchBay::stop_meters()
{
	for(int patch_number = 0;
		patch_number < patches.total;
		patch_number++)
	{
		APatchGUI *patchgui = (APatchGUI*)patches.values[patch_number];
		if(patchgui->data_type == TRACK_AUDIO && patchgui->meter)
		{
			patchgui->meter->reset();
		}
	}
}


#define PATCH_X 3

int PatchBay::update()
{
	int patch_count = 0;

// Every patch has a GUI regardless of whether or not it is visible.
// Make sure GUI's are allocated for every patch and deleted for non-existant
// patches.
	for(Track *current = mwindow->edl->tracks->first; current; current = NEXT)
	{
		if( current->is_hidden() ) continue;
		PatchGUI *patchgui = 0;
		int y = current->y_pixel;
		y -= mwindow->edl->local_session->track_start[pane->number];

//printf("PatchBay::update %d %d\n", __LINE__, y);
		if(patches.total > patch_count)
		{
			if(patches.values[patch_count]->track_id != current->get_id())
			{
				delete patches.values[patch_count];

				switch(current->data_type)
				{
					case TRACK_AUDIO:
						patchgui = patches.values[patch_count] = new APatchGUI(mwindow, this, (ATrack*)current, PATCH_X, y);
						break;
					case TRACK_VIDEO:
						patchgui = patches.values[patch_count] = new VPatchGUI(mwindow, this, (VTrack*)current, PATCH_X, y);
						break;
					case TRACK_SUBTITLE:
						patchgui = patches.values[patch_count] = new SPatchGUI(mwindow, this, (STrack*)current, PATCH_X, y);
						break;
				}
				patchgui->create_objects();
			}
			else
			{
				patches.values[patch_count]->update(PATCH_X, y);
			}
		}
		else
		{
			switch(current->data_type)
			{
				case TRACK_AUDIO:
					patchgui = new APatchGUI(mwindow, this, (ATrack*)current, PATCH_X, y);
					break;
				case TRACK_VIDEO:
					patchgui = new VPatchGUI(mwindow, this, (VTrack*)current, PATCH_X, y);
					break;
				case TRACK_SUBTITLE:
					patchgui = new SPatchGUI(mwindow, this, (STrack*)current, PATCH_X, y);
					break;
			}
			patches.append(patchgui);
			patchgui->create_objects();
		}
		++patch_count;
	}

	while(patches.total > patch_count)
	{
		delete patches.values[patches.total - 1];
		patches.remove_number(patches.total - 1);
	}

	show_window(0);
	return 0;
}

void PatchBay::synchronize_faders(float dv, int data_type, Track *skip, int edge, int span)
{
	for( Track *current=mwindow->edl->tracks->first; current; current=NEXT ) {
		if( current == skip ) continue;
		if( skip && !current->armed_gang(skip) ) continue;
		if( current->data_type != data_type ) continue;
		if( !current->is_armed() ) continue;
		FloatAutos *fade_autos = (FloatAutos*)current->automation->autos[AUTOMATION_FADE];
		double position = mwindow->edl->local_session->get_selectionstart(1);
		FloatAuto *float_auto = (FloatAuto*)fade_autos->get_auto_for_editing(position);
		int64_t pos = float_auto->position;
		float_auto->bump_update(pos, dv, edge, span);
		PatchGUI *patch = get_patch_of(current);
		if( patch ) patch->update(patch->x, patch->y);
	}
}

void PatchBay::synchronize_nudge(int64_t value, Track *skip)
{
	Track *current = mwindow->edl->tracks->first;
	for( ; current; current = NEXT ) {
		if( current->data_type == skip->data_type &&
		    current->armed_gang(skip) && current->is_armed() &&
		    current != skip ) {
			current->nudge = value;
			PatchGUI *patch = get_patch_of(current);
			if(patch) patch->update(patch->x, patch->y);
		}
	}
}

PatchGUI* PatchBay::get_patch_of(Track *track)
{
	for(int i = 0; i < patches.total; i++)
	{
		if(patches.values[i]->track == track)
			return patches.values[i];
	}
	return 0;
}

int PatchBay::resize_event(int top, int bottom)
{
	reposition_window(mwindow->theme->patchbay_x,
 		mwindow->theme->patchbay_y,
		mwindow->theme->patchbay_w,
		mwindow->theme->patchbay_h);
	return 0;
}


