// Copyright (c) 2005, Rodrigo Braz Monteiro
// Copyright (c) 2009-2010, Niels Martin Hansen
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
//   * Redistributions of source code must retain the above copyright notice,
//     this list of conditions and the following disclaimer.
//   * Redistributions in binary form must reproduce the above copyright notice,
//     this list of conditions and the following disclaimer in the documentation
//     and/or other materials provided with the distribution.
//   * Neither the name of the Aegisub Group nor the names of its contributors
//     may be used to endorse or promote products derived from this software
//     without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
// Aegisub Project http://www.aegisub.org/
//
// $Id$

/// @file audio_display.h
/// @see audio_display.cpp
/// @ingroup audio_ui
///

#ifndef AGI_PRE
#include <list>
#include <stdint.h>

#include <wx/gdicmn.h>
#include <wx/string.h>
#include <wx/timer.h>
#include <wx/window.h>
#endif

#include <libaegisub/scoped_ptr.h>
#include <libaegisub/signal.h>

namespace agi { struct Context; }

class AudioController;
class AudioRenderer;
class AudioRendererBitmapProvider;
class AudioKaraoke;
class AudioProvider;
class TimeRange;

// Helper classes used in implementation of the audio display
class AudioDisplayScrollbar;
class AudioDisplayTimeline;
class AudioDisplaySelection;
class AudioMarkerInteractionObject;

/// @class AudioDisplayInteractionObject
/// @brief Interface for objects on the audio display that can respond to mouse events
class AudioDisplayInteractionObject {
public:
	/// @brief The user is interacting with the object using the mouse
	/// @param event Mouse event data
	/// @return True to take mouse capture, false to release mouse capture
	///
	/// Assuming no object has the mouse capture, the audio display uses other methods
	/// in the object implementing this interface to determine whether a mouse event
	/// should go to the object. If the mouse event goes to the object, this method
	/// is called.
	///
	/// If this method returns true, the audio display takes the mouse capture and
	/// stores a pointer to the AudioDisplayInteractionObject interface for the object
	/// and redirects the next mouse event to that object.
	///
	/// If the object that has the mouse capture returns false from this method, the
	/// capture is released and regular processing is done for the next event.
	///
	/// If the object does not have mouse capture and returns false from this method,
	/// no capture is taken or released and regular processing is done for the next
	/// mouse event.
	virtual bool OnMouseEvent(wxMouseEvent &event) = 0;

	/// @brief Destructor
	///
	/// Empty virtual destructor for the cases that need it.
	virtual ~AudioDisplayInteractionObject() { }
};


/// @class AudioDisplay
/// @brief Primary view/UI for interaction with audio timing
///
/// The audio display is the common view that allows the user to interact with the active
/// timing controller. The audio display also renders audio according to the audio controller
/// and the timing controller, using an audio renderer instance.
class AudioDisplay: public wxWindow {
	agi::signal::Connection audio_open_connection;

	std::list<agi::signal::Connection> connections;
	agi::Context *context;

	/// The audio renderer manager
	agi::scoped_ptr<AudioRenderer> audio_renderer;

	/// The current audio renderer
	agi::scoped_ptr<AudioRendererBitmapProvider> audio_renderer_provider;

	/// The controller managing us
	AudioController *controller;

	/// Scrollbar helper object
	agi::scoped_ptr<AudioDisplayScrollbar> scrollbar;

	/// Timeline helper object
	agi::scoped_ptr<AudioDisplayTimeline> timeline;

	/// The interaction object for the last-dragged audio marker
	agi::scoped_ptr<AudioMarkerInteractionObject> audio_marker;


	/// Current object on display being dragged, if any
	AudioDisplayInteractionObject *dragged_object;
	/// Change the dragged object and update mouse capture
	void SetDraggedObject(AudioDisplayInteractionObject *new_obj);


	/// Timer for scrolling when markers are dragged out of the displayed area
	wxTimer scroll_timer;

	/// Leftmost pixel in the virtual audio image being displayed
	int scroll_left;

	/// Total width of the audio in pixels
	int pixel_audio_width;

	/// Horizontal zoom measured in millisecond per pixels
	double ms_per_pixel;

	/// Amplitude scaling ("vertical zoom") as a factor, 1.0 is neutral
	float scale_amplitude;

	/// Top of the main audio area in pixels
	int audio_top;

	/// Height of main audio area in pixels
	int audio_height;

	/// Width of the audio marker feet in pixels
	static const int foot_size = 6;

	/// Zoom level given as a number, see SetZoomLevel for details
	int zoom_level;

	/// Absolute pixel position of the tracking cursor (mouse or playback)
	int track_cursor_pos;
	/// Label to show by track cursor
	wxString track_cursor_label;
	/// Bounding rectangle last drawn track cursor label
	wxRect track_cursor_label_rect;
	/// @brief Move the tracking cursor
	/// @param new_pos   New absolute pixel position of the tracking cursor
	/// @param show_time Display timestamp by the tracking cursor?
	void SetTrackCursor(int new_pos, bool show_time);
	/// @brief Remove the tracking cursor from the display
	void RemoveTrackCursor();

	/// Previous style ranges for optimizing redraw when ranges change
	std::map<int, int> style_ranges;

	/// @brief Reload all rendering settings from Options and reset caches
	///
	/// This can be called if some rendering quality settings have been changed
	/// in Options and need to be reloaded to take effect.
	void ReloadRenderingSettings();

	/// Paint the audio data for a time range
	/// @param dc DC to paint to
	/// @param updtime Time range to repaint
	/// @param updrect Pixel range to repaint
	void PaintAudio(wxDC &dc, TimeRange updtime, wxRect updrect);

	/// Paint the markers in a time range
	/// @param dc DC to paint to
	/// @param updtime Time range to repaint
	void PaintMarkers(wxDC &dc, TimeRange updtime);

	/// Draw a single foot for a marker
	/// @param dc DC to paint to
	/// @param marker_x Position of the marker whose foot is being painted in pixels
	/// @param dir -1 for left, 1 for right
	void PaintFoot(wxDC &dc, int marker_x, int dir);

	/// Paint the labels in a time range
	/// @param dc DC to paint to
	/// @param updtime Time range to repaint
	void PaintLabels(wxDC &dc, TimeRange updtime);

	/// Paint the track cursor
	/// @param dc DC to paint to
	void PaintTrackCursor(wxDC &dc);

	/// Forward the mouse event to the appropriate child control, if any
	/// @return Was the mouse event forwarded somewhere?
	bool ForwardMouseEvent(wxMouseEvent &event);

	/// wxWidgets paint event
	void OnPaint(wxPaintEvent &event);
	/// wxWidgets mouse input event
	void OnMouseEvent(wxMouseEvent &event);
	/// wxWidgets control size changed event
	void OnSize(wxSizeEvent &event);
	/// wxWidgets input focus changed event
	void OnFocus(wxFocusEvent &event);
	/// wxWidgets keypress event
	void OnKeyDown(wxKeyEvent& event);
	void OnScrollTimer(wxTimerEvent &event);
	void OnMouseEnter(wxMouseEvent&);
	void OnMouseLeave(wxMouseEvent&);

	// AudioControllerAudioEventListener implementation
	void OnAudioOpen(AudioProvider *provider);
	void OnPlaybackPosition(int ms_position);
	void OnSelectionChanged();
	void OnStyleRangesChanged();
	void OnTimingController();
	void OnMarkerMoved();

public:
	AudioDisplay(wxWindow *parent, AudioController *controller, agi::Context *context);
	~AudioDisplay();

	/// @brief Scroll the audio display
	/// @param pixel_amount Number of pixels to scroll the view
	///
	/// A positive amount moves the display to the right, making later parts of the audio visible.
	void ScrollBy(int pixel_amount);

	/// @brief Scroll the audio display
	/// @param pixel_position Absolute pixel to put at left edge of the audio display
	///
	/// This is the principal scrolling function. All other scrolling functions eventually
	/// call this function to perform the actual scrolling.
	void ScrollPixelToLeft(int pixel_position);

	/// @brief Scroll the audio display
	/// @param pixel_position Absolute pixel to put in center of the audio display
	void ScrollPixelToCenter(int pixel_position) { ScrollPixelToLeft(pixel_position - GetClientRect().GetWidth()/2); }

	/// @brief Scroll the audio display
	/// @param ms Time in milliseconds to put at left edge of the audio display
	void ScrollTimeToLeft(int ms) { ScrollPixelToLeft(AbsoluteXFromTime(ms)); }

	/// @brief Scroll the audio display
	/// @param ms Time in milliseconds to put in center of the audio display
	void ScrollTimeToCenter(int ms)  { ScrollPixelToCenter(AbsoluteXFromTime(ms)); }

	/// @brief Scroll the audio display
	/// @param range Time range to ensure is in view
	///
	/// If the entire range is already visible inside the display, nothing is
	/// scrolled. If just one of the two endpoints is visible, the display is
	/// scrolled such that the visible endpoint stays in view but more of the
	/// rest of the range becomes visible.
	///
	/// If the entire range fits inside the display, the display is centered
	/// over the range.  For this calculation, the display is considered
	/// smaller by some margins, see below.
	///
	/// If the range does not fit within the display with margins subtracted,
	/// the start of the range is ensured visible and as much of the rest of
	/// the range is brought into view.
	///
	/// For the purpose of this function, a 5 percent margin is assumed at each
	/// end of the audio display such that a range endpoint that is ensured to
	/// be in view never gets closer to the edge of the display than the
	/// margin. The edge that is not ensured to be in view might be outside of
	/// view or might be closer to the display edge than the
	/// margin.
	void ScrollTimeRangeInView(const TimeRange &range);


	/// @brief Change the zoom level
	/// @param new_zoom_level The new zoom level to use
	///
	/// A zoom level of 0 is the default zoom level, all other levels are based
	/// on this. Negative zoom levels zoom out, positive zoom in.
	///
	/// The zoom levels generally go from +30 to -30. It is possible to zoom in
	/// more than +30.
	void SetZoomLevel(int new_zoom_level);

	/// @brief Get the zoom level
	/// @return The zoom level
	///
	/// See SetZoomLevel for a description of zoom levels.
	int GetZoomLevel() const { return zoom_level; }

	/// @brief Get a textual description of a zoom level
	/// @param level The zoom level to describe
	/// @return A translated string describing a zoom level
	///
	/// The zoom level description can tell the user details about how much audio is
	/// actually displayed.
	wxString GetZoomLevelDescription(int level) const;

	/// @brief Get the zoom factor in percent for a zoom level
	/// @param level The zoom level to get the factor of
	/// @return The zoom factor in percent
	///
	/// Positive: 125, 150, 175, 200, 225, ...
	///
	/// Negative: 90, 80, 70, 60, 50, 45, 40, 35, 30, 25, 20, 19, 18, 17, ..., 1
	///
	/// Too negative numbers get clamped.
	static int GetZoomLevelFactor(int level);

	/// @brief Set amplitude scale factor
	/// @param scale New amplitude scale factor, 1.0 is no scaling
	void SetAmplitudeScale(float scale);

	/// Get a time in milliseconds from an X coordinate relative to current scroll
	int TimeFromRelativeX(int x) const { return int((scroll_left + x) * ms_per_pixel); }
	/// Get a time in milliseconds from an absolute X coordinate
	int TimeFromAbsoluteX(int x) const { return int(x * ms_per_pixel); }
	/// Get an X coordinate relative to the current scroll from a time in milliseconds
	int RelativeXFromTime(int ms) const { return int(ms / ms_per_pixel) - scroll_left; }
	/// Get an absolute X coordinate from a time in milliseconds
	int AbsoluteXFromTime(int ms) const { return int(ms / ms_per_pixel); }
};
