/*
  Copyright (C) 2014 Waves Audio Ltd.

  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.

  $Id: fastmeter.h 570 2006-06-07 21:21:21Z sampo $
*/


#include <iostream>

#include "pbd/stacktrace.h"

#include "gtkmm2ext/fader.h"
#include "gtkmm2ext/keyboard.h"
#include "gtkmm2ext/rgb_macros.h"
#include "gtkmm2ext/utils.h"
#include "pbd/failed_constructor.h"
#include "pbd/file_utils.h"
#include "ardour/filesystem_paths.h"

using namespace Gtkmm2ext;
using namespace Gtk;
using namespace std;

#define CORNER_RADIUS 4
#define CORNER_SIZE   2
#define CORNER_OFFSET 1
#define FADER_RESERVE 5


static void get_closest_point_on_line(double xa, double ya, double xb, double yb, double xp, double yp, double& xl, double& yl )
{
	// Storing vector A->B
	double a_to_b_x = xb - xa;
	double a_to_b_y = yb - ya;

	// Storing vector A->P
	double a_to_p_x = xp - xa;
	double a_to_p_y = yp - ya;


	// Basically finding the squared magnitude
	// of a_to_b
	double atb2 = a_to_b_x * a_to_b_x + a_to_b_y * a_to_b_y;

	// The dot product of a_to_p and a_to_b
	double atp_dot_atb = a_to_p_x * a_to_b_x + a_to_p_y * a_to_b_y;

	// The normalized "distance" from a to
	// your closest point
	double t = atp_dot_atb / atb2;

	// Add the distance to A, moving
	// towards B
	double x = xa + a_to_b_x * t;
	double y = ya + a_to_b_y * t;

	if ((xa != xb)) {
		if ((x < xa) && (x < xb)) {
			if (xa < xb) {
				x = xa;
				y = ya;
			} else {
				x = xb;
				y = yb;
			}
		} else if ((x > xa) && (x > xb)) {
			if (xb > xa) {
				x = xb;
				y = yb;
			} else {
				x = xa;
				y = ya;
			}
		}
	} else {
		if ((y < ya) && (y < yb)) {
			if (ya < yb) {
				x = xa;
				y = ya;
			} else {
				x = xb;
				y = yb;
			}
		} else if ((y > ya) && (y > yb)) {
			if (yb > ya) {
				x = xb;
				y = yb;
			} else {
				x = xa;
				y = ya;
			}
		}
	}

	xl = x;
	yl = y;
}

Fader::Fader (Gtk::Adjustment& adj,
              const Glib::RefPtr<Gdk::Pixbuf>& face_pixbuf,
              const Glib::RefPtr<Gdk::Pixbuf>& active_face_pixbuf,
              const Glib::RefPtr<Gdk::Pixbuf>& underlay_pixbuf,
              const Glib::RefPtr<Gdk::Pixbuf>& handle_pixbuf,
              const Glib::RefPtr<Gdk::Pixbuf>& active_handle_pixbuf,
              int min_pos_x,
              int min_pos_y,
              int max_pos_x,
              int max_pos_y,
              bool read_only)
	: adjustment (adj)
	, _face_pixbuf (face_pixbuf)
	, _active_face_pixbuf (active_face_pixbuf)
	, _underlay_pixbuf (underlay_pixbuf)
	, _handle_pixbuf (handle_pixbuf)
	, _active_handle_pixbuf (active_handle_pixbuf)
	, _min_pos_x (min_pos_x)
	, _min_pos_y (min_pos_y)
	, _max_pos_x (max_pos_x)
	, _max_pos_y (max_pos_y)
	, _grab_window (0)
	, _touch_cursor (0)
	, _dragging (false)
	, _default_value (adjustment.get_value())
	, _read_only (read_only)
{
	update_unity_position ();

	if (!_read_only) {
		add_events (Gdk::BUTTON_PRESS_MASK|Gdk::BUTTON_RELEASE_MASK|Gdk::POINTER_MOTION_MASK|Gdk::SCROLL_MASK|Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK);
	}

	adjustment.signal_value_changed().connect (mem_fun (*this, &Fader::adjustment_changed));
	adjustment.signal_changed().connect (mem_fun (*this, &Fader::adjustment_changed));
	CairoWidget::set_size_request(_face_pixbuf->get_width(), _face_pixbuf->get_height());
}

Fader::~Fader ()
{
	if (_touch_cursor) {
		delete _touch_cursor;
	}
}

void
Fader::get_image_scales (double &x_scale, double &y_scale)
{
	int pbwidth = _face_pixbuf->get_width ();
	int pbheight = _face_pixbuf->get_height ();
	int width = get_width ();
	int height = get_height ();

	if ((width != pbwidth) || (height != pbheight)) {
		x_scale = double (width) / double (pbwidth);
		if (x_scale == 0.0) {
			x_scale = 1.0;
		}
		y_scale = double (height) / double (pbheight);
		if (y_scale == 0.0) {
			y_scale = 1.0;
		}
	} else {
		x_scale = y_scale = 1.0;
	}
}

void
Fader::set_touch_cursor (const Glib::RefPtr<Gdk::Pixbuf>& touch_cursor)
{
	_touch_cursor = new Gdk::Cursor (Gdk::Display::get_default(), touch_cursor, 12, 12);
}

void
Fader::render (cairo_t* cr, cairo_rectangle_t*)
{

	double xscale = 1.0;
	double yscale = 1.0;

	get_image_scales (xscale, yscale);

	cairo_matrix_t matrix;
	cairo_get_matrix (cr, &matrix);
	cairo_matrix_scale (&matrix, xscale, yscale);
	cairo_set_matrix (cr, &matrix);

	get_handle_position (_last_drawn_x, _last_drawn_y);

	if (_underlay_pixbuf != 0) {
		gdk_cairo_set_source_pixbuf (cr,
		                             _underlay_pixbuf->gobj(),
		                             (_last_drawn_x - (int)((_underlay_pixbuf->get_width() * xscale) / 2.0 + 0.5)) / xscale,
		                             (_last_drawn_y - (int)((_underlay_pixbuf->get_height() * yscale) / 2.0 + 0.5)) / yscale);
		cairo_paint (cr);
	}

	gdk_cairo_set_source_pixbuf (cr,
	                             ((get_state () == Gtk::STATE_ACTIVE) && (_active_face_pixbuf != 0)) ?
	                             _active_face_pixbuf->gobj() :
	                             _face_pixbuf->gobj(),
	                             0,
	                             0);
	cairo_paint (cr);

	const Glib::RefPtr<Gdk::Pixbuf> handle_pixbuf (_dragging ? _active_handle_pixbuf : _handle_pixbuf);
	gdk_cairo_set_source_pixbuf (cr,
	                             handle_pixbuf->gobj(),
	                             (_last_drawn_x - (int)((handle_pixbuf->get_width() * xscale) / 2.0 + 0.5)) / xscale,
	                             (_last_drawn_y - (int)((handle_pixbuf->get_height() * yscale) / 2.0 + 0.5)) / yscale);
	cairo_paint (cr);
}

void
Fader::on_size_request (GtkRequisition* req)
{
	req->width = _face_pixbuf->get_width();
	req->height = _face_pixbuf->get_height();
}

void
Fader::on_size_allocate (Gtk::Allocation& alloc)
{
	CairoWidget::on_size_allocate(alloc);
	update_unity_position ();
}

bool
Fader::on_button_press_event (GdkEventButton* ev)
{
	focus_handler(this);

	if (_read_only) {
		return false;
	}

	if (ev->type != GDK_BUTTON_PRESS) {
		return false;
	}

	if (ev->button != 1 && ev->button != 2) {
		return false;
	}

	if (_touch_cursor) {
		get_window()->set_cursor (*_touch_cursor);
	}

	_grab_start_mouse_x = ev->x;
	_grab_start_mouse_y = ev->y;
	get_handle_position (_grab_start_handle_x, _grab_start_handle_y);

	double xscale = 1.0;
	double yscale = 1.0;

	get_image_scales (xscale, yscale);

	double hw = _handle_pixbuf->get_width() * xscale;
	double hh = _handle_pixbuf->get_height() * yscale;

	if ((ev->x < (_grab_start_handle_x - hw/2)) || (ev->x > (_grab_start_handle_x + hw/2)) || (ev->y < (_grab_start_handle_y - hh/2)) || (ev->y > (_grab_start_handle_y + hh/2))) {
		return false;
	}

	double ev_pos_x;
	double ev_pos_y;

	get_closest_point_on_line(_min_pos_x, _min_pos_y,
	                          _max_pos_x, _max_pos_y,
	                          ev->x, ev->y,
	                          ev_pos_x, ev_pos_y );
	add_modal_grab ();

	_grab_window = ev->window;
	_dragging = true;

	gdk_pointer_grab(ev->window,false,
	                 GdkEventMask (Gdk::POINTER_MOTION_MASK | Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK),
	                 NULL,
	                 NULL,
	                 ev->time);

	queue_draw();

	return true;
}

bool
Fader::on_button_release_event (GdkEventButton* ev)
{
	if (_read_only) {
		return false;
	}

	if (_touch_cursor) {
		get_window()->set_cursor ();
	}

	if (_dragging) { //temp
		remove_modal_grab();
		_dragging = false;
		gdk_pointer_ungrab (GDK_CURRENT_TIME);
		queue_draw ();
	}
	return false;
}

bool
Fader::on_scroll_event (GdkEventScroll* ev)
{
	if (_read_only) {
		return false;
	}

	int step_factor = 1;

	switch (ev->direction) {
	case GDK_SCROLL_RIGHT:
	case GDK_SCROLL_UP:
#ifdef __APPLE__
		if ( ev->state & GDK_SHIFT_MASK ) {
			step_factor = -1;
		} else {
			step_factor = 1;
		}
#else
		step_factor = 1;
#endif
		break;
	case GDK_SCROLL_LEFT:
	case GDK_SCROLL_DOWN:
#ifdef __APPLE__
		if ( ev->state & GDK_SHIFT_MASK ) {
			step_factor = 1;
		} else {
			step_factor = -1;
		}
#else
		step_factor = -1;
#endif
		break;
	default:
		return false;
	}
	adjustment.set_value (adjustment.get_value() + step_factor * (adjustment.get_step_increment() ));
	return true;
}

bool
Fader::on_motion_notify_event (GdkEventMotion* ev)
{
	if (_read_only) {
		return false;
	}

	if (_dragging) {
		double ev_pos_x;
		double ev_pos_y;

		if (ev->window != _grab_window) {
			_grab_window = ev->window;
			return true;
		}

		get_closest_point_on_line(_min_pos_x, _min_pos_y,
		                          _max_pos_x, _max_pos_y,
		                          _grab_start_handle_x + (ev->x - _grab_start_mouse_x), _grab_start_handle_y + (ev->y - _grab_start_mouse_y),
		                          ev_pos_x, ev_pos_y );

		double const fract = sqrt((ev_pos_x - _min_pos_x) * (ev_pos_x - _min_pos_x) +
		                          (ev_pos_y - _min_pos_y) * (ev_pos_y - _min_pos_y)) /
			sqrt((double)((_max_pos_x - _min_pos_x) * (_max_pos_x - _min_pos_x) +
			              (_max_pos_y - _min_pos_y) * (_max_pos_y - _min_pos_y)));

		adjustment.set_value (adjustment.get_lower() + (adjustment.get_upper() - adjustment.get_lower()) * fract);
	}
	return true;
}

void
Fader::adjustment_changed ()
{
	double handle_x;
	double handle_y;
	get_handle_position (handle_x, handle_y);

	if ((handle_x != _last_drawn_x) || (handle_y != _last_drawn_y)) {
		queue_draw ();
	}
}

/** @return pixel offset of the current value from the right or bottom of the fader */
void
Fader::get_handle_position (double& x, double& y)
{
	double fract = (adjustment.get_value () - adjustment.get_lower()) / ((adjustment.get_upper() - adjustment.get_lower()));

	x = (int)(_min_pos_x + (_max_pos_x - _min_pos_x) * fract);
	y = (int)(_min_pos_y + (_max_pos_y - _min_pos_y) * fract);
}

bool
Fader::on_enter_notify_event (GdkEventCrossing*)
{
	_hovering = true;
	Keyboard::magic_widget_grab_focus ();
	queue_draw ();
	return false;
}

bool
Fader::on_leave_notify_event (GdkEventCrossing*)
{
	if (_read_only) {
		return false;
	}

	if (!_dragging) {
		_hovering = false;
		Keyboard::magic_widget_drop_focus();
		queue_draw ();
	}
	return false;
}

void
Fader::set_default_value (float d)
{
	_default_value = d;
	update_unity_position ();
}

void
Fader::update_unity_position ()
{
}
