/*
    Copyright (C) 2000-2006 Paul Davis 

    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., 675 Mass Ave, Cambridge, MA 02139, USA.

*/

#include <cstdlib>
#include <cmath>
#include <cassert>

#include <algorithm>
#include <string>
#include <vector>

#include <sigc++/bind.h>

#include <pbd/error.h>
#include <pbd/stl_delete.h>
#include <pbd/memento_command.h>

#include <gtkmm2ext/gtk_ui.h>
#include <gtkmm2ext/selector.h>
#include <gtkmm2ext/stop_signal.h>
#include <gtkmm2ext/bindable_button.h>
#include <gtkmm2ext/utils.h>

#include <ardour/audioplaylist.h>
#include <ardour/audio_diskstream.h>
#include <ardour/insert.h>
#include <ardour/location.h>
#include <ardour/panner.h>
#include <ardour/playlist.h>
#include <ardour/profile.h>
#include <ardour/session.h>
#include <ardour/session_playlist.h>
#include <ardour/utils.h>

#include "ardour_ui.h"
#include "audio_time_axis.h"
#include "automation_gain_line.h"
#include "automation_pan_line.h"
#include "canvas_impl.h"
#include "crossfade_view.h"
#include "enums.h"
#include "gain_automation_time_axis.h"
#include "keyboard.h"
#include "pan_automation_time_axis.h"
#include "playlist_selector.h"
#include "prompter.h"
#include "public_editor.h"
#include "audio_region_view.h"
#include "simplerect.h"
#include "audio_streamview.h"
#include "utils.h"

#include <ardour/audio_track.h>

#include "i18n.h"

using namespace ARDOUR;
using namespace PBD;
using namespace Gtk;
using namespace Editing;

AudioTimeAxisView::AudioTimeAxisView (PublicEditor& ed, Session& sess, boost::shared_ptr<Route> rt, Canvas& canvas)
	: AxisView(sess)
	, RouteTimeAxisView(ed, sess, rt, canvas)
{
	// Make sure things are sane...
	assert(!is_track() || is_audio_track());

	subplugin_menu.set_name ("ArdourContextMenu");
	gain_track = 0;
	pan_track = 0;
	waveform_item = 0;
	pan_automation_item = 0;
	gain_automation_item = 0;

	_view = new AudioStreamView (*this);

	add_gain_automation_child ();
	add_pan_automation_child ();

	ignore_toggle = false;

	if (is_audio_track())
		controls_ebox.set_name ("AudioTimeAxisViewControlsBaseUnselected");
	else // bus
		controls_ebox.set_name ("AudioBusControlsBaseUnselected");

	ensure_xml_node ();

	set_state (*xml_node);
	
	_route->panner().Changed.connect (mem_fun(*this, &AudioTimeAxisView::update_pans));

	update_control_names ();

	if (is_audio_track()) {

		/* ask for notifications of any new RegionViews */
		_view->RegionViewAdded.connect (mem_fun(*this, &AudioTimeAxisView::region_view_added));

		if (!editor.have_idled()) {
			/* first idle will do what we need */
		} else {
			first_idle ();
		} 

	} else {
		post_construct ();
	}
}

AudioTimeAxisView::~AudioTimeAxisView ()
{
}

void
AudioTimeAxisView::first_idle ()
{
	_view->attach ();
	post_construct ();
}

AudioStreamView*
AudioTimeAxisView::audio_view()
{
	return dynamic_cast<AudioStreamView*>(_view);
}

guint32
AudioTimeAxisView::show_at (double y, int& nth, Gtk::VBox *parent)
{
	ensure_xml_node ();
	xml_node->add_property ("shown_editor", "yes");
		
	return TimeAxisView::show_at (y, nth, parent);
}

void
AudioTimeAxisView::hide ()
{
	ensure_xml_node ();
	xml_node->add_property ("shown_editor", "no");

	TimeAxisView::hide ();
}

int
AudioTimeAxisView::set_state (const XMLNode& node)
{
	const XMLProperty *prop;
	int ret;

	if ((ret = TimeAxisView::set_state (node)) != 0) {
		return ret;
	}

	/* if the TimeAxisView had marked this not for display,
	   then do not allow this to override it.
	*/

	if ((prop = node.property ("marked_for_display")) == 0) {
		if ((prop = node.property ("shown_editor")) != 0) {
			if (prop->value() == "no") {
				_marked_for_display = false;
			} else {
				_marked_for_display = true;
			}
		} else {
			_marked_for_display = true;
		}
	}
	
	XMLNodeList nlist = node.children();
	XMLNodeConstIterator niter;
	XMLNode *child_node;
	
	
	show_gain_automation = false;
	show_pan_automation  = false;
	
	for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
		child_node = *niter;

		if (child_node->name() == "gain") {
			XMLProperty *prop=child_node->property ("shown");
			
			if (prop != 0) {
				if (prop->value() == "yes") {
					show_gain_automation = true;
				}
			}
			continue;
		}
		
		if (child_node->name() == "pan") {
			XMLProperty *prop=child_node->property ("shown");
			
			if (prop != 0) {
				if (prop->value() == "yes") {
					show_pan_automation = true;
				}			
			}
			continue;
		}
	}

	return 0;
}

void
AudioTimeAxisView::build_automation_action_menu ()
{
	using namespace Menu_Helpers;

	RouteTimeAxisView::build_automation_action_menu ();

	MenuList& automation_items = automation_action_menu->items();
	
	automation_items.push_back (SeparatorElem());

	automation_items.push_back (CheckMenuElem (_("Fader"), 
						   mem_fun(*this, &AudioTimeAxisView::toggle_gain_track)));
	gain_automation_item = static_cast<CheckMenuItem*> (&automation_items.back());
	gain_automation_item->set_active(show_gain_automation);

	automation_items.push_back (CheckMenuElem (_("Pan"),
						   mem_fun(*this, &AudioTimeAxisView::toggle_pan_track)));
	pan_automation_item = static_cast<CheckMenuItem*> (&automation_items.back());
	pan_automation_item->set_active(show_pan_automation);
	
}

void
AudioTimeAxisView::append_extra_display_menu_items ()
{
	using namespace Menu_Helpers;

	MenuList& items = display_menu->items();

	// crossfade stuff
	if (!Profile->get_sae()) {
		items.push_back (MenuElem (_("Hide all crossfades"), mem_fun(*this, &AudioTimeAxisView::hide_all_xfades)));
		items.push_back (MenuElem (_("Show all crossfades"), mem_fun(*this, &AudioTimeAxisView::show_all_xfades)));
	}

	// waveform menu
	Menu *waveform_menu = manage(new Menu);
	MenuList& waveform_items = waveform_menu->items();
	waveform_menu->set_name ("ArdourContextMenu");
	
	waveform_items.push_back (CheckMenuElem (_("Show waveforms"), mem_fun(*this, &AudioTimeAxisView::toggle_waveforms)));
	waveform_item = static_cast<CheckMenuItem *> (&waveform_items.back());
	ignore_toggle = true;
	waveform_item->set_active (editor.show_waveforms());
	ignore_toggle = false;

	waveform_items.push_back (SeparatorElem());
	
	RadioMenuItem::Group group;
	
	waveform_items.push_back (RadioMenuElem (group, _("Traditional"), bind (mem_fun(*this, &AudioTimeAxisView::set_waveform_shape), Traditional)));
	traditional_item = static_cast<RadioMenuItem *> (&waveform_items.back());

	if (!Profile->get_sae()) {
		waveform_items.push_back (RadioMenuElem (group, _("Rectified"), bind (mem_fun(*this, &AudioTimeAxisView::set_waveform_shape), Rectified)));
		rectified_item = static_cast<RadioMenuItem *> (&waveform_items.back());
	} else {
		rectified_item = 0;
	}

	waveform_items.push_back (SeparatorElem());
	
	RadioMenuItem::Group group2;

	waveform_items.push_back (RadioMenuElem (group2, _("Linear"), bind (mem_fun(*this, &AudioTimeAxisView::set_waveform_scale), LinearWaveform)));
	linearscale_item = static_cast<RadioMenuItem *> (&waveform_items.back());

	waveform_items.push_back (RadioMenuElem (group2, _("Logarithmic"), bind (mem_fun(*this, &AudioTimeAxisView::set_waveform_scale), LogWaveform)));
	logscale_item = static_cast<RadioMenuItem *> (&waveform_items.back());

	// setting initial item state
	AudioStreamView* asv = audio_view();
	if (asv) {
		ignore_toggle = true;
		if (asv->get_waveform_shape() == Rectified && rectified_item) {
			rectified_item->set_active(true);
		} else {
			traditional_item->set_active(true);
		}

		if (asv->get_waveform_scale() == LogWaveform) 
			logscale_item->set_active(true);
		else linearscale_item->set_active(true);
		ignore_toggle = false;
	}

	items.push_back (MenuElem (_("Waveform"), *waveform_menu));
}

void
AudioTimeAxisView::toggle_waveforms ()
{
	AudioStreamView* asv = audio_view();
	assert(asv);

	if (asv && waveform_item && !ignore_toggle) {
		asv->set_show_waveforms (waveform_item->get_active());
	}
}

void
AudioTimeAxisView::set_show_waveforms (bool yn)
{
	AudioStreamView* asv = audio_view();
	assert(asv);

	if (waveform_item) {
		waveform_item->set_active (yn);
	} else {
		asv->set_show_waveforms (yn);
	}
}

void
AudioTimeAxisView::set_show_waveforms_recording (bool yn)
{
	AudioStreamView* asv = audio_view();

	if (asv) {
		asv->set_show_waveforms_recording (yn);
	}
}

void
AudioTimeAxisView::set_waveform_shape (WaveformShape shape)
{
	AudioStreamView* asv = audio_view();

	if (asv && !ignore_toggle) {
		asv->set_waveform_shape (shape);
	}

	map_frozen ();
}	

void
AudioTimeAxisView::set_waveform_scale (WaveformScale scale)
{
	AudioStreamView* asv = audio_view();

	if (asv && !ignore_toggle) {
		asv->set_waveform_scale (scale);
	}

	map_frozen ();
}	

void
AudioTimeAxisView::add_gain_automation_child ()
{
	XMLProperty* prop;
	AutomationLine* line;

	gain_track = new GainAutomationTimeAxisView (_session,
						     _route,
						     editor,
						     *this,
						     parent_canvas,
						     _("gain"),
						     _route->gain_automation_curve());
	
	line = new AutomationGainLine ("automation gain",
				       _session,
				       *gain_track,
				       *gain_track->canvas_display,
				       _route->gain_automation_curve());

	line->set_line_color (ARDOUR_UI::config()->canvasvar_AutomationLine.get());
	

	gain_track->add_line (*line);

	add_child (gain_track);

	gain_track->Hiding.connect (mem_fun(*this, &AudioTimeAxisView::gain_hidden));

	bool hideit = true;
	
	XMLNode* node;

	if ((node = gain_track->get_state_node()) != 0) {
		if  ((prop = node->property ("shown")) != 0) {
			if (prop->value() == "yes") {
				hideit = false;
			}
		} 
	}

	if (hideit) {
		gain_track->hide ();
	}
}

void
AudioTimeAxisView::add_pan_automation_child ()
{
	XMLProperty* prop;

	pan_track = new PanAutomationTimeAxisView (_session, _route, editor, *this, parent_canvas, _("pan"));

	update_pans ();
	
	add_child (pan_track);

	pan_track->Hiding.connect (mem_fun(*this, &AudioTimeAxisView::pan_hidden));

	ensure_xml_node ();
	bool hideit = true;
	
	XMLNode* node;

	if ((node = pan_track->get_state_node()) != 0) {
		if ((prop = node->property ("shown")) != 0) {
			if (prop->value() == "yes") {
				hideit = false;
			}
		} 
	}

	if (hideit) {
		pan_track->hide ();
	}
}

void
AudioTimeAxisView::update_pans ()
{
	Panner::iterator p;
	
	pan_track->clear_lines ();
	
	/* we don't draw lines for "greater than stereo" panning.
	 */

	if (_route->n_outputs() > 2) {
		return;
	}

	for (p = _route->panner().begin(); p != _route->panner().end(); ++p) {

		AutomationLine* line;

		line = new AutomationPanLine ("automation pan", _session, *pan_track,
					      *pan_track->canvas_display, 
					      (*p)->automation());

		if (p == _route->panner().begin()) {
			line->set_line_color (ARDOUR_UI::config()->canvasvar_AutomationLine.get());
		} else {
			line->set_line_color (ARDOUR_UI::config()->canvasvar_AutomationLine.get());
		}

		pan_track->add_line (*line);
	}
}
		
void
AudioTimeAxisView::toggle_gain_track ()
{
	bool showit = gain_automation_item->get_active();

	if (showit != gain_track->marked_for_display()) {
		if (showit) {
			gain_track->set_marked_for_display (true);
			gain_track->canvas_display->show();
			gain_track->canvas_background->show();
			gain_track->get_state_node()->add_property ("shown", X_("yes"));
		} else {
			gain_track->set_marked_for_display (false);
			gain_track->hide ();
			gain_track->get_state_node()->add_property ("shown", X_("no"));
		}

		/* now trigger a redisplay */
		
		if (!no_redraw) {
			 _route->gui_changed (X_("visible_tracks"), (void *) 0); /* EMIT_SIGNAL */
		}
	}
}

void
AudioTimeAxisView::gain_hidden ()
{
	gain_track->get_state_node()->add_property (X_("shown"), X_("no"));

	if (gain_automation_item && !_hidden) {
		gain_automation_item->set_active (false);
	}

	 _route->gui_changed ("visible_tracks", (void *) 0); /* EMIT_SIGNAL */
}

void
AudioTimeAxisView::toggle_pan_track ()
{
	bool showit = pan_automation_item->get_active();

	if (showit != pan_track->marked_for_display()) {
		if (showit) {
			pan_track->set_marked_for_display (true);
			pan_track->canvas_display->show();
			pan_track->canvas_background->show();
			pan_track->get_state_node()->add_property ("shown", X_("yes"));
		} else {
			pan_track->set_marked_for_display (false);
			pan_track->hide ();
			pan_track->get_state_node()->add_property ("shown", X_("no"));
		}

		/* now trigger a redisplay */
		
		if (!no_redraw) {
			 _route->gui_changed ("visible_tracks", (void *) 0); /* EMIT_SIGNAL */
		}
	}
}

void
AudioTimeAxisView::pan_hidden ()
{
	pan_track->get_state_node()->add_property ("shown", "no");

	if (pan_automation_item && !_hidden) {
		pan_automation_item->set_active (false);
	}

	 _route->gui_changed ("visible_tracks", (void *) 0); /* EMIT_SIGNAL */
}

void
AudioTimeAxisView::show_all_automation ()
{
	no_redraw = true;

	pan_automation_item->set_active (true);
	gain_automation_item->set_active (true);
	
	RouteTimeAxisView::show_all_automation ();

	no_redraw = false;

	 _route->gui_changed ("visible_tracks", (void *) 0); /* EMIT_SIGNAL */
}

void
AudioTimeAxisView::show_existing_automation ()
{
	no_redraw = true;

	pan_automation_item->set_active (true);
	gain_automation_item->set_active (true);

	RouteTimeAxisView::show_existing_automation ();

	no_redraw = false;

	 _route->gui_changed ("visible_tracks", (void *) 0); /* EMIT_SIGNAL */
}

void
AudioTimeAxisView::hide_all_automation ()
{
	no_redraw = true;

	pan_automation_item->set_active (false);
	gain_automation_item->set_active (false);

	RouteTimeAxisView::hide_all_automation();

	no_redraw = false;
	 _route->gui_changed ("visible_tracks", (void *) 0); /* EMIT_SIGNAL */
}

void
AudioTimeAxisView::show_all_xfades ()
{
	AudioStreamView* asv = audio_view();

	if (asv) {
		asv->show_all_xfades ();
	}
}

void
AudioTimeAxisView::hide_all_xfades ()
{
	AudioStreamView* asv = audio_view();
	
	if (asv) {
		asv->hide_all_xfades ();
	}
}

void
AudioTimeAxisView::hide_dependent_views (TimeAxisViewItem& tavi)
{
	AudioStreamView* asv = audio_view();
	AudioRegionView* rv;

	if (asv && (rv = dynamic_cast<AudioRegionView*>(&tavi)) != 0) {
		asv->hide_xfades_involving (*rv);
	}
}

void
AudioTimeAxisView::reveal_dependent_views (TimeAxisViewItem& tavi)
{
	AudioStreamView* asv = audio_view();
	AudioRegionView* rv;

	if (asv && (rv = dynamic_cast<AudioRegionView*>(&tavi)) != 0) {
		asv->reveal_xfades_involving (*rv);
	}
}

void
AudioTimeAxisView::route_active_changed ()
{
	RouteTimeAxisView::route_active_changed ();
	update_control_names ();
}


/**
 *    Set up the names of the controls so that they are coloured
 *    correctly depending on whether this route is inactive or
 *    selected.
 */

void
AudioTimeAxisView::update_control_names ()
{
	if (is_audio_track()) {
		if (_route->active()) {
			controls_base_selected_name = "AudioTrackControlsBaseSelected";
			controls_base_unselected_name = "AudioTrackControlsBaseUnselected";
		} else {
			controls_base_selected_name = "AudioTrackControlsBaseInactiveSelected";
			controls_base_unselected_name = "AudioTrackControlsBaseInactiveUnselected";
		}
	} else {
		if (_route->active()) {
			controls_base_selected_name = "BusControlsBaseSelected";
			controls_base_unselected_name = "BusControlsBaseUnselected";
		} else {
			controls_base_selected_name = "BusControlsBaseInactiveSelected";
			controls_base_unselected_name = "BusControlsBaseInactiveUnselected";
		}
	}

	if (get_selected()) {
		controls_ebox.set_name (controls_base_selected_name);
	} else {
		controls_ebox.set_name (controls_base_unselected_name);
	}
}

XMLNode* 
AudioTimeAxisView::get_child_xml_node (const string & childname)
{
	return RouteUI::get_child_xml_node (childname);
}

