/*
 * Copyright (C) 2013-2019 Robin Gareus <robin@gareus.org>
 * Copyright (C) 2014-2016 Paul Davis <paul@linuxaudiosystems.com>
 * Copyright (C) 2015 Tim Mayberry <mojofunk@gmail.com>
 *
 * 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.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

#include <ytkmm/drawingarea.h>

#include <gtkmm2ext/cairo_widget.h>
#include <gtkmm2ext/gtk_ui.h>
#include <gtkmm2ext/utils.h>
#include <gtkmm2ext/rgb_macros.h>

#include "ardour/logmeter.h"
#include <ardour/rc_configuration.h>

#include "utils.h"
#include "meter_patterns.h"
#include "ui_config.h"

#include "pbd/i18n.h"

using namespace ARDOUR;
using namespace ARDOUR_UI_UTILS;
using namespace PBD;
using namespace Gtk;
using namespace Gtkmm2ext;
using namespace std;
using namespace ArdourMeter;

static const int max_pattern_metric_size = 1026;

/* signals used by meters */

sigc::signal<void> ArdourMeter::ResetAllPeakDisplays;
sigc::signal<void,ARDOUR::Route*> ArdourMeter::ResetRoutePeakDisplays;
sigc::signal<void,std::shared_ptr<ARDOUR::RouteGroup>> ArdourMeter::ResetGroupPeakDisplays;
sigc::signal<void> ArdourMeter::RedrawMetrics;

sigc::signal<void, int, std::shared_ptr<ARDOUR::RouteGroup>, ARDOUR::MeterType> ArdourMeter::SetMeterTypeMulti;

/* pattern cache */

struct MeterMatricsMapKey {
	MeterMatricsMapKey (std::string n, MeterType t, int dt)
		: _n(n)
		, _t(t)
		, _dt(dt)
	{}
	inline bool operator<(const MeterMatricsMapKey& rhs) const {
		return (_n < rhs._n) || (_n == rhs._n && _t < rhs._t) || (_n == rhs._n && _t == rhs._t && _dt < rhs._dt);
	}
	std::string _n;
	MeterType _t;
	int _dt;
};

namespace ArdourMeter {
	typedef std::map<MeterMatricsMapKey, cairo_pattern_t*> MetricPatternMap;
}

static ArdourMeter::MetricPatternMap ticks_patterns;
static ArdourMeter::MetricPatternMap metric_patterns;


const std::string
ArdourMeter::meter_type_string (ARDOUR::MeterType mt)
{
	switch (mt) {
		case MeterPeak:
			return _("Peak (+6dBFS)");
			break;
		case MeterPeak0dB:
			return _("Peak (0dBFS)");
			break;
		case MeterKrms:
			return _("RMS + Peak");
			break;
		case MeterIEC1DIN:
			return _("IEC1/DIN");
			break;
		case MeterIEC1NOR:
			return _("IEC1/Nordic");
			break;
		case MeterIEC2BBC:
			return _("IEC2/BBC");
			break;
		case MeterIEC2EBU:
			return _("IEC2/EBU");
			break;
		case MeterK20:
			return _("K20/RMS");
			break;
		case MeterK14:
			return _("K14/RMS");
			break;
		case MeterK12:
			return _("K12/RMS");
			break;
		case MeterVU:
			return _("VU");
			break;
		default:
			assert(0);
			return _("???");
			break;
	}
}

static inline int types_to_bit (vector<ARDOUR::DataType> types) {
	int rv = 0;
	for (vector<DataType>::const_iterator i = types.begin(); i != types.end(); ++i) {
		rv |= 1 << (*i);
	}
	return rv;
}

static inline float mtr_col_and_fract(
		cairo_t* cr, Gdk::Color const * const c, const uint32_t peakcolor, const MeterType mt, const float val)
{
	float fraction = 0;

	switch (mt) {
		default:
		case MeterKrms:
		case MeterPeak:
			fraction = log_meter (val);
			if (val >= 0 || val == -9) {
				cairo_set_source_rgb (cr,
						UINT_RGBA_R_FLT(peakcolor),
						UINT_RGBA_G_FLT(peakcolor),
						UINT_RGBA_B_FLT(peakcolor));
			} else {
				cairo_set_source_rgb (cr, c->get_red_p(), c->get_green_p(), c->get_blue_p());
			}
			break;
		case MeterPeak0dB:
			fraction = log_meter0dB (val);
			if (val >= 0 || val == -9) {
				cairo_set_source_rgb (cr,
						UINT_RGBA_R_FLT(peakcolor),
						UINT_RGBA_G_FLT(peakcolor),
						UINT_RGBA_B_FLT(peakcolor));
			} else {
				cairo_set_source_rgb (cr, c->get_red_p(), c->get_green_p(), c->get_blue_p());
			}
			break;
		case MeterIEC2BBC:
		case MeterIEC2EBU:
			fraction = meter_deflect_ppm(val);
			cairo_set_source_rgb (cr, c->get_red_p(), c->get_green_p(), c->get_blue_p());
			break;
		case MeterIEC1NOR:
			fraction = meter_deflect_nordic(val);
#if 0
			if (val == -18.0) {
				cairo_set_source_rgb (cr,
						UINT_RGBA_R_FLT(peakcolor),
						UINT_RGBA_G_FLT(peakcolor),
						UINT_RGBA_B_FLT(peakcolor));
			} else {
				cairo_set_source_rgb (cr, c->get_red_p(), c->get_green_p(), c->get_blue_p());
			}
#else
			cairo_set_source_rgb (cr, c->get_red_p(), c->get_green_p(), c->get_blue_p());
#endif
			break;
		case MeterIEC1DIN:
			fraction = meter_deflect_din(val);
			if (val == -9.0 || val == -15 || val == -18) {
				cairo_set_source_rgb (cr,
						UINT_RGBA_R_FLT(peakcolor),
						UINT_RGBA_G_FLT(peakcolor),
						UINT_RGBA_B_FLT(peakcolor));
			} else {
				cairo_set_source_rgb (cr, c->get_red_p(), c->get_green_p(), c->get_blue_p());
			}
			break;
		case MeterVU:
			fraction = meter_deflect_vu(val);
			if (val >= -20.0) {
				cairo_set_source_rgb (cr,
						UINT_RGBA_R_FLT(peakcolor),
						UINT_RGBA_G_FLT(peakcolor),
						UINT_RGBA_B_FLT(peakcolor));
			} else {
				cairo_set_source_rgb (cr, c->get_red_p(), c->get_green_p(), c->get_blue_p());
			}
			break;
		case MeterK20:
			fraction = meter_deflect_k (val, 20);
			if (val >= -16.0) {
				cairo_set_source_rgb (cr, 1.0, 0.0, 0.0); // red
			} else if (val >= -20.0) {
				cairo_set_source_rgb (cr, 0.8, 0.8, 0.0); // yellow
			} else {
				cairo_set_source_rgb (cr, 0.0, 1.0, 0.0); // green
			}
			break;
		case MeterK14:
			if (val >= -10.0) {
				cairo_set_source_rgb (cr, 1.0, 0.0, 0.0); // red
			} else if (val >= -14.0) {
				cairo_set_source_rgb (cr, 0.8, 0.8, 0.0); // yellow
			} else {
				cairo_set_source_rgb (cr, 0.0, 1.0, 0.0); // green
			}
			fraction = meter_deflect_k (val, 14);
			break;
		case MeterK12:
			if (val >= -8.0) {
				cairo_set_source_rgb (cr, 1.0, 0.0, 0.0); // red
			} else if (val >= -12.0) {
				cairo_set_source_rgb (cr, 0.8, 0.8, 0.0); // yellow
			} else {
				cairo_set_source_rgb (cr, 0.0, 1.0, 0.0); // green
			}
			fraction = meter_deflect_k (val, 12);
			break;
	}
	return fraction;
}

static void mtr_red_stripe(cairo_t* cr, float l, float w, int h, float top, float bot) {
	if (w <= 0) return;
	int t = h - floorf (h * (top));
	int b = h - floorf (h * (bot));
	cairo_set_source_rgba (cr, .75, 0, 0, 0.75);
	cairo_rectangle (cr, l, t + .5, w, b - t);
	cairo_fill (cr);
}

static void set_bg_color (Gtk::Widget& w, cairo_t* cr, MeterType type) {
	double r,g,b,a;
	switch(type) {
		case MeterVU:
			Gtkmm2ext::color_to_rgba (UIConfiguration::instance().color ("meterstrip vu bg"), r, g, b, a);
			break;
		case MeterIEC1DIN:
		case MeterIEC1NOR:
		case MeterIEC2BBC:
		case MeterIEC2EBU:
			Gtkmm2ext::color_to_rgba (UIConfiguration::instance().color ("meterstrip ppm bg"), r, g, b, a);
			break;
		case MeterK12:
		case MeterK14:
		case MeterK20:
			Gtkmm2ext::color_to_rgba (UIConfiguration::instance().color ("meterstrip dpm bg"), r, g, b, a);
			break;
		default:
			{
			Gdk::Color c = w.get_style()->get_bg (Gtk::STATE_ACTIVE);
			r = c.get_red_p();
			g = c.get_green_p();
			b = c.get_blue_p();
			}
			break;
	}
	cairo_set_source_rgb (cr, r, g, b);
}

static void set_fg_color(Gtk::Widget&, MeterType type, Gdk::Color * c) {
	double r,g,b,a;
	switch(type) {
		case MeterVU:
			Gtkmm2ext::color_to_rgba (UIConfiguration::instance().color ("meterstrip vu fg"), r, g, b, a);
			break;
		case MeterIEC1DIN:
		case MeterIEC1NOR:
		case MeterIEC2BBC:
		case MeterIEC2EBU:
			Gtkmm2ext::color_to_rgba (UIConfiguration::instance().color ("meterstrip ppm fg"), r, g, b, a);
			break;
		default:
			Gtkmm2ext::color_to_rgba (UIConfiguration::instance().color ("meterstrip dpm fg"), r, g, b, a);
			break;
	}
	c->set_rgb_p (r, g, b);
}

static cairo_pattern_t*
meter_render_ticks (Gtk::Widget& w, MeterType type, vector<ARDOUR::DataType> types)
{
	Glib::RefPtr<Gdk::Window> win (w.get_window());

	bool background;
	bool tickleft, tickright;
	gint width, height;
	win->get_size (width, height);
	tickleft = w.get_name().substr(w.get_name().length() - 4) == "Left";
	tickright = w.get_name().substr(w.get_name().length() - 5) == "Right";
	background = types.size() == 0 || tickleft || tickright;

	float box_l=0;
	float box_w=0;
#define PX_SCALE(pxmin, dflt) rint(std::max((double)pxmin, (double)dflt * UIConfiguration::instance().get_ui_scale()))
	if (tickleft) {
		if (w.get_name().substr(0, 3) == "Bar") {
			box_w = PX_SCALE(2, 2);
			box_l = width - box_w;
		} else if (w.get_name().substr(0, 4) == "Mark") {
			box_w = PX_SCALE(2, 2);
			box_l = width - box_w;
			background = false;
		}
	} else if (tickright) {
		if (w.get_name().substr(0, 3) == "Bar") {
			box_l = 0; box_w = PX_SCALE(2, 2);
		} else if (w.get_name().substr(0, 4) == "Mark") {
			box_l = 0; box_w = PX_SCALE(2, 2);
			background = false;
		}
	} else {
		box_l = 0; box_w = width;
	}
#undef PX_SCALE

	cairo_surface_t* surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, width, height);
	cairo_t* cr = cairo_create (surface);

	cairo_move_to (cr, 0, 0);
	cairo_rectangle (cr, 0, 0, width, height);

	if (background) {
		/* meterbridge */
		set_bg_color(w, cr, type);
	} else {
		/* mixer */
		Gdk::Color c = w.get_style()->get_bg (Gtk::STATE_NORMAL);
		cairo_set_source_rgb (cr, c.get_red_p(), c.get_green_p(), c.get_blue_p());
	}
	cairo_fill (cr);

	height = min(max_pattern_metric_size, height);
	uint32_t peakcolor = UIConfiguration::instance().color ("meterbridge peaklabel");

	for (vector<DataType>::const_iterator i = types.begin(); i != types.end(); ++i) {

		Gdk::Color c;
		if (types.size() > 1 && (*i) == DataType::MIDI) {
			/* we're overlaying more than 1 set of marks, so use different colours */
			c = w.get_style()->get_fg (Gtk::STATE_ACTIVE);
		} else if (background) {
			set_fg_color(w, type, &c);
		} else {
			c = w.get_style()->get_fg (Gtk::STATE_NORMAL);
		}

		// tick-maker position in dBFS, line-thickness
		std::map<float,float> points;

		switch (*i) {
		case DataType::AUDIO:

			switch (type) {
				case MeterK12:
					points.insert (std::pair<float,float>(-52.0f, 1.0));
					points.insert (std::pair<float,float>(-42.0f, 1.0));
					points.insert (std::pair<float,float>(-32.0f, 1.0));
					points.insert (std::pair<float,float>(-22.0f, 1.0));
					points.insert (std::pair<float,float>(-18.0f, 1.0));
					points.insert (std::pair<float,float>(-15.0f, 1.0));
					points.insert (std::pair<float,float>(-12.0f, 1.0));
					points.insert (std::pair<float,float>( -9.0f, 1.0));
					points.insert (std::pair<float,float>( -8.0f, 0.8));
					points.insert (std::pair<float,float>( -6.0f, 1.0));
					points.insert (std::pair<float,float>( -3.0f, 1.0));
					points.insert (std::pair<float,float>(  0.0f, 1.0));
					break;
				case MeterK14:
					points.insert (std::pair<float,float>(-54.0f, 1.0));
					points.insert (std::pair<float,float>(-44.0f, 1.0));
					points.insert (std::pair<float,float>(-34.0f, 1.0));
					points.insert (std::pair<float,float>(-24.0f, 1.0));
					points.insert (std::pair<float,float>(-20.0f, 1.0));
					points.insert (std::pair<float,float>(-17.0f, 1.0));
					points.insert (std::pair<float,float>(-14.0f, 1.0)); //   0
					points.insert (std::pair<float,float>(-11.0f, 1.0)); //  +3
					points.insert (std::pair<float,float>(-10.0f, 0.8)); //  +4
					points.insert (std::pair<float,float>( -8.0f, 1.0)); //  +6
					points.insert (std::pair<float,float>( -4.0f, 1.0)); // +10
					points.insert (std::pair<float,float>(  0.0f, 1.0));
					break;
				case MeterK20:
					points.insert (std::pair<float,float>(-60.0f, 1.0));
					points.insert (std::pair<float,float>(-50.0f, 1.0));
					points.insert (std::pair<float,float>(-40.0f, 1.0));
					points.insert (std::pair<float,float>(-30.0f, 1.0));
					points.insert (std::pair<float,float>(-26.0f, 1.0));
					points.insert (std::pair<float,float>(-23.0f, 1.0));
					points.insert (std::pair<float,float>(-20.0f, 1.0)); //  0
					points.insert (std::pair<float,float>(-17.0f, 1.0));
					points.insert (std::pair<float,float>(-16.0f, 0.8));
					points.insert (std::pair<float,float>(-14.0f, 1.0)); // +6
					points.insert (std::pair<float,float>(-10.0f, 1.0));
					points.insert (std::pair<float,float>( -5.0f, 1.0));
					points.insert (std::pair<float,float>(  0.0f, 1.0)); //+20
					break;
				case MeterIEC2EBU:
					points.insert (std::pair<float,float>(-30.0f, 1.0));
					points.insert (std::pair<float,float>(-28.0f, 0.5));
					points.insert (std::pair<float,float>(-26.0f, 1.0));
					points.insert (std::pair<float,float>(-24.0f, 0.5));
					points.insert (std::pair<float,float>(-22.0f, 1.0));
					points.insert (std::pair<float,float>(-20.0f, 0.5));
					points.insert (std::pair<float,float>(-18.0f, 1.0));
					points.insert (std::pair<float,float>(-16.0f, 0.5));
					points.insert (std::pair<float,float>(-14.0f, 1.0));
					points.insert (std::pair<float,float>(-12.0f, 0.5));
					points.insert (std::pair<float,float>(-10.0f, 1.0));
					points.insert (std::pair<float,float>( -9.0f, 0.8));
					points.insert (std::pair<float,float>( -8.0f, 0.5));
					points.insert (std::pair<float,float>( -6.0f, 1.0));
					break;
				case MeterIEC2BBC:
					points.insert (std::pair<float,float>(-30.0f, 1.0));
					points.insert (std::pair<float,float>(-26.0f, 1.0));
					points.insert (std::pair<float,float>(-22.0f, 1.0));
					points.insert (std::pair<float,float>(-18.0f, 1.0));
					points.insert (std::pair<float,float>(-14.0f, 1.0));
					points.insert (std::pair<float,float>(-10.0f, 1.0));
					points.insert (std::pair<float,float>( -6.0f, 1.0));
					break;
				case MeterIEC1NOR:
					points.insert (std::pair<float,float>(-60.0f, 1.0)); // -42
					points.insert (std::pair<float,float>(-57.0f, 0.5));
					points.insert (std::pair<float,float>(-54.0f, 1.0));
					points.insert (std::pair<float,float>(-51.0f, 0.5));
					points.insert (std::pair<float,float>(-48.0f, 1.0));
					points.insert (std::pair<float,float>(-45.0f, 0.5));
					points.insert (std::pair<float,float>(-42.0f, 1.0));
					points.insert (std::pair<float,float>(-39.0f, 0.5));
					points.insert (std::pair<float,float>(-36.0f, 1.0));

					points.insert (std::pair<float,float>(-33.0f, 0.5));
					points.insert (std::pair<float,float>(-30.0f, 1.0));
					points.insert (std::pair<float,float>(-27.0f, 0.5));
					points.insert (std::pair<float,float>(-24.0f, 1.0));
					points.insert (std::pair<float,float>(-21.0f, 0.5));

					points.insert (std::pair<float,float>(-18.0f, 1.0));
					points.insert (std::pair<float,float>(-15.0f, 0.5));
					points.insert (std::pair<float,float>(-12.0f, 1.0));
					points.insert (std::pair<float,float>( -9.0f, 1.0));
					points.insert (std::pair<float,float>( -6.0f, 0.5));
					mtr_red_stripe(cr, box_l, box_w, height,
							meter_deflect_nordic(-6.0f), meter_deflect_nordic(-12.0f));
					break;
				case MeterIEC1DIN:
					points.insert (std::pair<float,float>( -3.0f, 0.5)); // "200%"
					points.insert (std::pair<float,float>( -4.0f, 1.0));
					points.insert (std::pair<float,float>( -5.0f, 0.5));
					points.insert (std::pair<float,float>( -6.0f, 0.5));
					points.insert (std::pair<float,float>( -7.0f, 0.5));
					points.insert (std::pair<float,float>( -8.0f, 0.5));
					points.insert (std::pair<float,float>( -9.0f, 1.0)); // "100%"
					points.insert (std::pair<float,float>(-10.0f, 0.5));
					points.insert (std::pair<float,float>(-11.0f, 0.5));
					points.insert (std::pair<float,float>(-12.0f, 0.5));
					points.insert (std::pair<float,float>(-13.0f, 0.5));
					points.insert (std::pair<float,float>(-14.0f, 1.0));
					points.insert (std::pair<float,float>(-15.0f, 0.8)); // "50%"
					points.insert (std::pair<float,float>(-18.0f, 0.8)); // "-9"
					points.insert (std::pair<float,float>(-19.0f, 1.0));
					points.insert (std::pair<float,float>(-24.0f, 0.5));
					points.insert (std::pair<float,float>(-29.0f, 1.0)); // "-20", "10%"
					points.insert (std::pair<float,float>(-34.0f, 0.5)); // -25
					//points.insert (std::pair<float,float>(-35.0f, 0.5)); // "5%" " -20"
					points.insert (std::pair<float,float>(-39.0f, 1.0));
					points.insert (std::pair<float,float>(-49.0f, 1.0));
					points.insert (std::pair<float,float>(-54.0f, 0.5));
					points.insert (std::pair<float,float>(-59.0f, 1.0));
					mtr_red_stripe(cr, box_l, box_w, height,
							meter_deflect_din(0.0f), meter_deflect_din(-9.0f));
					break;
				case MeterVU:
					points.insert (std::pair<float,float>(-17.0f, 1.0)); //+3 VU
					points.insert (std::pair<float,float>(-18.0f, 1.0));
					points.insert (std::pair<float,float>(-19.0f, 1.0));
					points.insert (std::pair<float,float>(-19.5f, 0.5));
					points.insert (std::pair<float,float>(-20.0f, 1.0)); // 0 VU
					points.insert (std::pair<float,float>(-20.5f, 0.5));
					points.insert (std::pair<float,float>(-21.0f, 1.0));
					points.insert (std::pair<float,float>(-22.0f, 1.0));
					points.insert (std::pair<float,float>(-23.0f, 1.0)); //-3 VU
					points.insert (std::pair<float,float>(-24.0f, 0.5));
					points.insert (std::pair<float,float>(-25.0f, 1.0));
					points.insert (std::pair<float,float>(-26.0f, 0.5));
					points.insert (std::pair<float,float>(-27.0f, 1.0)); //-7 VU
					points.insert (std::pair<float,float>(-30.0f, 1.0));
					points.insert (std::pair<float,float>(-35.0f, 0.5));
					points.insert (std::pair<float,float>(-40.0f, 1.0));
					mtr_red_stripe(cr, box_l, box_w, height,
							meter_deflect_vu(-17.0f), meter_deflect_vu(-20.0f));
					break;

				default:
					points.insert (std::pair<float,float>(  0, 1.0));
					points.insert (std::pair<float,float>(  1, 0.5));
					points.insert (std::pair<float,float>(  2, 0.5));
					points.insert (std::pair<float,float>(  3, 1.0));
					points.insert (std::pair<float,float>(  4, 0.5));
					points.insert (std::pair<float,float>(  5, 0.5));
					/* fallthrough */
				case MeterPeak0dB:
					points.insert (std::pair<float,float>(-60, 0.5));
					points.insert (std::pair<float,float>(-50, 1.0));
					points.insert (std::pair<float,float>(-40, 1.0));
					points.insert (std::pair<float,float>(-30, 1.0));
					if (UIConfiguration::instance().get_meter_line_up_level() == MeteringLineUp24) {
						points.insert (std::pair<float,float>(-24, 1.0));
					} else {
						points.insert (std::pair<float,float>(-25, 1.0));
					}
					points.insert (std::pair<float,float>(-20, 1.0));

					points.insert (std::pair<float,float>(-19, 0.5));
					points.insert (std::pair<float,float>(-18, 1.0));
					points.insert (std::pair<float,float>(-17, 0.5));
					points.insert (std::pair<float,float>(-16, 0.5));
					points.insert (std::pair<float,float>(-15, 1.0));

					points.insert (std::pair<float,float>(-14, 0.5));
					points.insert (std::pair<float,float>(-13, 0.5));
					points.insert (std::pair<float,float>(-12, 0.5));
					points.insert (std::pair<float,float>(-11, 0.5));
					points.insert (std::pair<float,float>(-10, 1.0));

					points.insert (std::pair<float,float>( -9, 1.0));
					points.insert (std::pair<float,float>( -8, 0.5));
					points.insert (std::pair<float,float>( -7, 0.5));
					points.insert (std::pair<float,float>( -6, 0.5));
					points.insert (std::pair<float,float>( -5, 1.0));
					points.insert (std::pair<float,float>( -4, 0.5));
					points.insert (std::pair<float,float>( -3, 1.0));
					points.insert (std::pair<float,float>( -2, 0.5));
					points.insert (std::pair<float,float>( -1, 0.5));
					break;
			}
			break;

		case DataType::MIDI:
			points.insert (std::pair<float,float>(  0, 1.0));
			points.insert (std::pair<float,float>( 16, 0.5));
			points.insert (std::pair<float,float>( 32, 0.5));
			points.insert (std::pair<float,float>( 48, 0.5));
			points.insert (std::pair<float,float>( 64, 1.0));
			points.insert (std::pair<float,float>( 80, 0.5));
			points.insert (std::pair<float,float>( 96, 0.5));
			points.insert (std::pair<float,float>(100, 1.0));
			points.insert (std::pair<float,float>(112, 0.5));
			points.insert (std::pair<float,float>(127, 1.0));
			break;
		}

		for (std::map<float,float>::const_iterator j = points.begin(); j != points.end(); ++j) {
			cairo_set_line_width (cr, (j->second));

			float fraction = 0;
			gint pos;

			switch (*i) {
			case DataType::AUDIO:
				fraction = mtr_col_and_fract(cr, &c, peakcolor, type, j->first);

				pos = height - (gint) floor (height * fraction);
				pos = max (pos, 1);
				cairo_move_to(cr, 0, pos + .5);
				cairo_line_to(cr, width, pos + .5);
				cairo_stroke (cr);
				break;
			case DataType::MIDI:
				fraction = (j->first) / 127.0;
				pos = 1 + height - (gint) floor (height * fraction);
				pos = min (pos, height);
				cairo_set_source_rgb (cr, c.get_red_p(), c.get_green_p(), c.get_blue_p());
				cairo_arc(cr, width * .5, pos + .5, 1.0, 0, 2 * M_PI);
				cairo_fill(cr);
				break;
			}
		}
	}

	cairo_pattern_t* pattern = cairo_pattern_create_for_surface (surface);

	cairo_destroy (cr);
	cairo_surface_destroy (surface);

	return pattern;
}

static cairo_pattern_t*
meter_render_metrics (Gtk::Widget& w, MeterType type, vector<DataType> types)
{
	Glib::RefPtr<Gdk::Window> win (w.get_window());

	bool tickleft, tickright;
	bool background;
	int overlay_midi = 1;
	gint width, height;
	win->get_size (width, height);

	tickleft = w.get_name().substr(w.get_name().length() - 4) == "Left";
	tickright = w.get_name().substr(w.get_name().length() - 5) == "Right";
	background = types.size() == 0 || tickleft || tickright;

	if (!tickleft && !tickright) {
		tickright = true;
	}

#ifdef NO_OVERSAMPLE
	cairo_surface_t* surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, width, height);
	cairo_t* cr = cairo_create (surface);
#else
	cairo_surface_t* surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, width*2, height*2);
	cairo_t* cr = cairo_create (surface);
	cairo_scale(cr, 2.0, 2.0);
#endif
	Glib::RefPtr<Pango::Layout> layout = Pango::Layout::create(w.get_pango_context());

	Pango::AttrList audio_font_attributes;
	Pango::AttrList midi_font_attributes;
	Pango::AttrList unit_font_attributes;

	Pango::AttrFontDesc* font_attr;
	Pango::FontDescription font;

	font = Pango::FontDescription (UIConfiguration::instance().get_SmallMonospaceFont());
#ifdef __APPLE__
	const double fixfontsize = 1.0;
#else
	// counter-act global font-scaling.
	const double fixfontsize = std::min(1.0, 0.9 / sqrtf(UIConfiguration::instance().get_ui_scale()));
#endif

	font.set_weight (Pango::WEIGHT_NORMAL);
	font.set_size (8.0 * fixfontsize * PANGO_SCALE);
	font_attr = new Pango::AttrFontDesc (Pango::Attribute::create_attr_font_desc (font));
	audio_font_attributes.change (*font_attr);
	delete font_attr;

	font.set_weight (Pango::WEIGHT_ULTRALIGHT);
	font.set_stretch (Pango::STRETCH_ULTRA_CONDENSED);
	font.set_size (7.0 * fixfontsize * PANGO_SCALE);
	font_attr = new Pango::AttrFontDesc (Pango::Attribute::create_attr_font_desc (font));
	midi_font_attributes.change (*font_attr);
	delete font_attr;

	font.set_size (6.0 * fixfontsize * PANGO_SCALE);
	font_attr = new Pango::AttrFontDesc (Pango::Attribute::create_attr_font_desc (font));
	unit_font_attributes.change (*font_attr);
	delete font_attr;

	cairo_move_to (cr, 0, 0);
	cairo_rectangle (cr, 0, 0, width, height);
	if (background) {
		/* meterbridge */
		set_bg_color(w, cr, type);
	} else {
		/* mixer */
		Gdk::Color c = w.get_style()->get_bg (Gtk::STATE_NORMAL);
		cairo_set_source_rgb (cr, c.get_red_p(), c.get_green_p(), c.get_blue_p());
	}
	cairo_fill (cr);

	cairo_set_line_width (cr, 1.0);

	height = min(max_pattern_metric_size, height);
	uint32_t peakcolor = UIConfiguration::instance().color ("meterbridge peaklabel");
	Gdk::Color c; // default text color

	for (vector<DataType>::const_iterator i = types.begin(); i != types.end(); ++i) {

		if (types.size() > 1 && (*i) == DataType::MIDI && overlay_midi == 0) {
			continue;
		}

		if (types.size() > 1 && (*i) == DataType::MIDI) {
			/* we're overlaying more than 1 set of marks, so use different colours */
			c = w.get_style()->get_fg (Gtk::STATE_ACTIVE);
		} else if (background) {
			set_fg_color(w, type, &c);
		} else {
			c = w.get_style()->get_fg (Gtk::STATE_NORMAL);
		}

		std::map<float,string> points; // map: label-pos in dBFS, label-text

		switch (*i) {
		case DataType::AUDIO:
			layout->set_attributes (audio_font_attributes);
			if (type == MeterPeak0dB) {
				overlay_midi = 4;
			}
			switch (type) {
				case MeterK12:
					overlay_midi = 0;
					points.insert (std::pair<float,string>(-52.0f, "-40"));
					points.insert (std::pair<float,string>(-42.0f, "-30"));
					points.insert (std::pair<float,string>(-32.0f, "-20"));
					points.insert (std::pair<float,string>(-22.0f, "-10"));
					points.insert (std::pair<float,string>(-18.0f,  "-6"));
					points.insert (std::pair<float,string>(-15.0f,  "-3"));
					points.insert (std::pair<float,string>(-12.0f,  " 0"));
					points.insert (std::pair<float,string>( -9.0f,  "+3"));
					points.insert (std::pair<float,string>( -6.0f,  "+6"));
					points.insert (std::pair<float,string>( -3.0f,  "+9"));
					points.insert (std::pair<float,string>(  0.0f, "+12"));
					break;
				case MeterK14:
					overlay_midi = 0;
					points.insert (std::pair<float,string>(-54.0f, "-40"));
					points.insert (std::pair<float,string>(-44.0f, "-30"));
					points.insert (std::pair<float,string>(-34.0f, "-20"));
					points.insert (std::pair<float,string>(-24.0f, "-10"));
					points.insert (std::pair<float,string>(-20.0f,  "-6"));
					points.insert (std::pair<float,string>(-17.0f,  "-3"));
					points.insert (std::pair<float,string>(-14.0f,  " 0"));
					points.insert (std::pair<float,string>(-11.0f,  "+3"));
					points.insert (std::pair<float,string>( -8.0f,  "+6"));
					points.insert (std::pair<float,string>( -4.0f, "+10"));
					points.insert (std::pair<float,string>(  0.0f, "+14"));
					break;
				case MeterK20:
					overlay_midi = 0;
					points.insert (std::pair<float,string>(-60.0f, "-40"));
					points.insert (std::pair<float,string>(-50.0f, "-30"));
					points.insert (std::pair<float,string>(-40.0f, "-20"));
					points.insert (std::pair<float,string>(-30.0f, "-10"));
					points.insert (std::pair<float,string>(-26.0f,  "-6"));
					points.insert (std::pair<float,string>(-23.0f,  "-3"));
					points.insert (std::pair<float,string>(-20.0f,  " 0"));
					points.insert (std::pair<float,string>(-17.0f,  "+3"));
					points.insert (std::pair<float,string>(-14.0f,  "+6"));
					points.insert (std::pair<float,string>(-10.0f, "+10"));
					points.insert (std::pair<float,string>( -5.0f, "+15"));
					points.insert (std::pair<float,string>(  0.0f, "+20"));
					break;
				case MeterPeak:
					/* fallthrough */
				case MeterKrms:
					/* fallthrough */
				default:
					points.insert (std::pair<float,string>(  3.0f, "+3"));
					/* fallthrough */
				case MeterPeak0dB:
					points.insert (std::pair<float,string>(-50.0f, "-50"));
					points.insert (std::pair<float,string>(-40.0f, "-40"));
					points.insert (std::pair<float,string>(-30.0f, "-30"));
					points.insert (std::pair<float,string>(-20.0f, "-20"));
					if (types.size() == 1) {
						if (UIConfiguration::instance().get_meter_line_up_level() == MeteringLineUp24) {
							points.insert (std::pair<float,string>(-24.0f, "-24"));
						} else {
							points.insert (std::pair<float,string>(-25.0f, "-25"));
						}
						points.insert (std::pair<float,string>(-15.0f, "-15"));
					}
					points.insert (std::pair<float,string>(-18.0f, "-18"));
					points.insert (std::pair<float,string>(-10.0f, "-10"));
					points.insert (std::pair<float,string>( -5.0f, "-5"));
					points.insert (std::pair<float,string>( -3.0f, "-3"));
					points.insert (std::pair<float,string>(  0.0f, "+0"));
					break;

				case MeterIEC2EBU:
					overlay_midi = 3;
					points.insert (std::pair<float,string>(-30.0f, "-12"));
					points.insert (std::pair<float,string>(-26.0f, "-8"));
					points.insert (std::pair<float,string>(-22.0f, "-4"));
					points.insert (std::pair<float,string>(-18.0f, "TST"));
					points.insert (std::pair<float,string>(-14.0f, "+4"));
					points.insert (std::pair<float,string>(-10.0f, "+8"));
					points.insert (std::pair<float,string>( -6.0f, "+12"));
					break;

				case MeterIEC2BBC:
					overlay_midi = 3;
					points.insert (std::pair<float,string>(-30.0f, " 1 "));
					points.insert (std::pair<float,string>(-26.0f, " 2 "));
					points.insert (std::pair<float,string>(-22.0f, " 3 "));
					points.insert (std::pair<float,string>(-18.0f, " 4 "));
					points.insert (std::pair<float,string>(-14.0f, " 5 "));
					points.insert (std::pair<float,string>(-10.0f, " 6 "));
					points.insert (std::pair<float,string>( -6.0f, " 7 "));
					break;

				case MeterIEC1NOR:
					overlay_midi = 0;
					//points.insert (std::pair<float,string>(-60.0f, "-42"));
					points.insert (std::pair<float,string>(-54.0f, "-36"));
					points.insert (std::pair<float,string>(-48.0f, "-30"));
					points.insert (std::pair<float,string>(-42.0f, "-24"));
					points.insert (std::pair<float,string>(-36.0f, "-18"));

					//points.insert (std::pair<float,string>(-33.0f, "-15"));
					points.insert (std::pair<float,string>(-30.0f, "-12"));
					//points.insert (std::pair<float,string>(-27.0f, "-9"));
					points.insert (std::pair<float,string>(-24.0f, "-6"));
					//points.insert (std::pair<float,string>(-21.0f, "-3"));

					points.insert (std::pair<float,string>(-18.0f, "TST"));
					//points.insert (std::pair<float,string>(-15.0f, "+3"));
					points.insert (std::pair<float,string>(-12.0f, "+6"));
					points.insert (std::pair<float,string>( -9.0f, "+9"));
					//points.insert (std::pair<float,string>( -6.0f, "+12"));
					break;

				case MeterIEC1DIN:
					overlay_midi = 2;
					//points.insert (std::pair<float,string>( -3.0f, "200%"));
					points.insert (std::pair<float,string>( -4.0f, "+5"));
					points.insert (std::pair<float,string>( -9.0f, "0")); // "100%";
					points.insert (std::pair<float,string>(-14.0f, "-5"));
					//points.insert (std::pair<float,string>(-15.0f, "50%"));
					//points.insert (std::pair<float,string>(-18.0f, "-9"));
					points.insert (std::pair<float,string>(-19.0f, "-10")); // "30%"
					points.insert (std::pair<float,string>(-29.0f, "-20")); // "10%"
					//points.insert (std::pair<float,string>(-35.0f, "5%")); // "5%"
					points.insert (std::pair<float,string>(-39.0f, "-30"));
					//points.insert (std::pair<float,string>(-49.0f, "1%"));
					points.insert (std::pair<float,string>(-59.0f, "-50"));
					break;

				case MeterVU:
					overlay_midi = 0;
					points.insert (std::pair<float,string>(-17.0f, "+3"));
					points.insert (std::pair<float,string>(-18.0f, "+2"));
					points.insert (std::pair<float,string>(-19.0f, "+1"));
					points.insert (std::pair<float,string>(-20.0f, " 0"));
					points.insert (std::pair<float,string>(-21.0f, "-1"));
					points.insert (std::pair<float,string>(-22.0f, "-2"));
					points.insert (std::pair<float,string>(-23.0f, "-3"));
					points.insert (std::pair<float,string>(-25.0f, "-5"));
					points.insert (std::pair<float,string>(-27.0f, "-7"));
					points.insert (std::pair<float,string>(-30.0f, "-10"));
					points.insert (std::pair<float,string>(-40.0f, "-20"));
					break;
			}
			break;
		case DataType::MIDI:
			layout->set_attributes (midi_font_attributes);
			if (types.size() == 1) {
				points.insert (std::pair<float,string>(  0, "0"));
				points.insert (std::pair<float,string>( 16,  "16"));
				points.insert (std::pair<float,string>( 32,  "32"));
				points.insert (std::pair<float,string>( 48,  "48"));
				points.insert (std::pair<float,string>( 64,  "64"));
				points.insert (std::pair<float,string>( 80,  "80"));
				points.insert (std::pair<float,string>( 96,  "96"));
				points.insert (std::pair<float,string>(100, "100"));
				points.insert (std::pair<float,string>(112, "112"));
				points.insert (std::pair<float,string>(127, "127"));
			} else {
				switch (overlay_midi) {
					case 1:
						/* labels that don't overlay with dBFS */
						points.insert (std::pair<float,string>(  0, "0"));
						points.insert (std::pair<float,string>( 24, "24"));
						points.insert (std::pair<float,string>( 48, "48"));
						points.insert (std::pair<float,string>( 72, "72"));
						points.insert (std::pair<float,string>(127, "127"));
						break;
					case 2:
						/* labels that don't overlay with DIN */
						points.insert (std::pair<float,string>(  0, "0"));
						points.insert (std::pair<float,string>( 16,  "16"));
						points.insert (std::pair<float,string>( 40,  "40"));
						points.insert (std::pair<float,string>( 64,  "64"));
						points.insert (std::pair<float,string>(112, "112"));
						points.insert (std::pair<float,string>(127, "127"));
						break;
					case 3:
						/* labels that don't overlay with BBC nor EBU*/
						points.insert (std::pair<float,string>(  0, "0"));
						points.insert (std::pair<float,string>( 16, "16"));
						points.insert (std::pair<float,string>( 56, "56"));
						points.insert (std::pair<float,string>( 72, "72"));
						points.insert (std::pair<float,string>(112, "112"));
						points.insert (std::pair<float,string>(127, "127"));
						break;
					case 4:
						/* labels that don't overlay with 0dBFS*/
						points.insert (std::pair<float,string>(  0, "0"));
						points.insert (std::pair<float,string>( 16, "16"));
						points.insert (std::pair<float,string>( 48, "48"));
						points.insert (std::pair<float,string>( 84, "84"));
						points.insert (std::pair<float,string>(100, "100"));
					default:
						break;
				}
			}
			break;
		}

		gint pos = -1;

		for (std::map<float,string>::const_iterator j = points.begin(); j != points.end(); ++j) {
			float fraction = 0;
			bool align_center = background; // this is true for meterbridge meters w/ fixed background
			switch (*i) {
				case DataType::AUDIO:
					fraction = mtr_col_and_fract(cr, &c, peakcolor, type, j->first);

					pos = height - (gint) floor (height * fraction);
					pos = max (pos, 1);
					if (tickleft) {
						cairo_move_to(cr, width-1.5, pos + .5);
						cairo_line_to(cr, width, pos + .5);
						cairo_stroke (cr);
					} else if (tickright) {
						cairo_move_to(cr, 0, pos + .5);
						cairo_line_to(cr, 1.5, pos + .5);
						cairo_stroke (cr);
					}
					break;
				case DataType::MIDI:
					align_center = false; // don't bleed into legend
					fraction = (j->first) / 127.0;
					pos = 1 + height - (gint) lrintf (height * fraction);
					pos = min (pos, height);
					break;
			}

			if (pos < 0) continue;

			layout->set_text(j->second.c_str());

			int tw, th;
			layout->get_pixel_size(tw, th);

			int p = pos - (th / 2) - 1;
			p = min (p, height - th);
			p = max (p, 0);

			if (align_center) {
				cairo_move_to (cr, (width-tw)/2.0, p);
			} else {
				cairo_move_to (cr, width-3-tw, p);
			}

			cairo_set_line_width(cr, 0.12);
			cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 1.0);
			pango_cairo_layout_path(cr, layout->gobj());
			cairo_stroke_preserve (cr);
			cairo_set_line_width(cr, 1.0);

			if ((*i) == DataType::AUDIO) {
				mtr_col_and_fract(cr, &c, peakcolor, type, j->first);
			} else {
				cairo_set_source_rgb (cr, c.get_red_p(), c.get_green_p(), c.get_blue_p());
			}

			pango_cairo_show_layout (cr, layout->gobj());
			cairo_new_path(cr);
		}
	}

	// add legend
	if (types.size() == 1 || overlay_midi == 0) {
		int tw, th;
		layout->set_attributes (unit_font_attributes);
		switch (types.at(0)) {
			case DataType::AUDIO:
				switch (type) {
					case MeterK20:
						layout->set_text("K20");
						break;
					case MeterK14:
						layout->set_text("K14");
						break;
					case MeterK12:
						layout->set_text("K12");
						break;
					default:
					case MeterPeak:
					case MeterPeak0dB:
					case MeterKrms:
						layout->set_text("dBFS");
						break;
					case MeterIEC2EBU:
						layout->set_text("EBU");
						break;
					case MeterIEC2BBC:
						layout->set_text("BBC");
						break;
					case MeterIEC1DIN:
						layout->set_text("DIN");
						break;
					case MeterIEC1NOR:
						layout->set_text("NOR");
						break;
					case MeterVU:
						layout->set_text("VU");
						break;
				}
				layout->get_pixel_size(tw, th);
				break;
			case DataType::MIDI:
				layout->set_text("mid");
				layout->get_pixel_size(tw, th);
				break;
			default: // DataType::NIL;
				layout->set_text("");
				tw = th = 0;
				assert (0); // not reached
				break;
		}
		if (!background) {
			c = w.get_style()->get_fg (Gtk::STATE_ACTIVE);
		}
		cairo_set_source_rgb (cr, c.get_red_p(), c.get_green_p(), c.get_blue_p());
		if (tickleft) {
			cairo_move_to (cr, width - 2 - tw, height - th - 0.5);
		} else {
			cairo_move_to (cr, 2, height - th - 0.5);
		}
		pango_cairo_show_layout (cr, layout->gobj());
	}

	cairo_pattern_t* pattern = cairo_pattern_create_for_surface (surface);

	cairo_destroy (cr);
	cairo_surface_destroy (surface);

	return pattern;
}

gint
ArdourMeter::meter_expose_ticks (GdkEventExpose *ev, MeterType type, std::vector<ARDOUR::DataType> types, Gtk::DrawingArea *mta)
{
	Glib::RefPtr<Gdk::Window> win (mta->get_window());
	cairo_t* cr;

	cr = gdk_cairo_create (win->gobj());

	/* clip to expose area */

	gdk_cairo_rectangle (cr, &ev->area);
	cairo_clip (cr);

	cairo_pattern_t* pattern;
	const MeterMatricsMapKey key (mta->get_name(), type, types_to_bit(types));
	MetricPatternMap::iterator i = ticks_patterns.find (key);

	if (i == ticks_patterns.end()) {
		pattern = meter_render_ticks (*mta, type, types);
		ticks_patterns[key] = pattern;
	} else {
		pattern = i->second;
	}

	cairo_move_to (cr, 0, 0);
	cairo_set_source (cr, pattern);

	gint width, height;
	win->get_size (width, height);

	cairo_rectangle (cr, 0, 0, width, height);
	cairo_fill (cr);

	cairo_destroy (cr);

	return true;
}

gint
ArdourMeter::meter_expose_metrics (GdkEventExpose *ev, MeterType type, std::vector<ARDOUR::DataType> types, Gtk::DrawingArea *mma)
{
	Glib::RefPtr<Gdk::Window> win (mma->get_window());
	cairo_t* cr;

	cr = gdk_cairo_create (win->gobj());

	/* clip to expose area */

	gdk_cairo_rectangle (cr, &ev->area);
	cairo_clip (cr);

	cairo_pattern_t* pattern;
	const MeterMatricsMapKey key (mma->get_name(), type, types_to_bit(types));
	MetricPatternMap::iterator i = metric_patterns.find (key);

	if (i == metric_patterns.end()) {
		pattern = meter_render_metrics (*mma, type, types);
		metric_patterns[key] = pattern;
	} else {
		pattern = i->second;
	}

	cairo_move_to (cr, 0, 0);

	gint width, height;
	win->get_size (width, height);
#ifdef NO_OVERSAMPLE
	cairo_set_source (cr, pattern);
	cairo_rectangle (cr, 0, 0, width, height);
#else
	cairo_scale(cr, 0.5, 0.5);
	cairo_set_antialias(cr, CAIRO_ANTIALIAS_BEST);
	cairo_set_source (cr, pattern);
	cairo_rectangle (cr, 0, 0, width * 2., height * 2.);
#endif

	cairo_fill (cr);

	cairo_destroy (cr);

	return true;
}

void
ArdourMeter::meter_clear_pattern_cache(int which) {
	MetricPatternMap::iterator i = metric_patterns.begin();
	MetricPatternMap::iterator j = ticks_patterns.begin();

	while (i != metric_patterns.end()) {
		int m = 4;
		MeterMatricsMapKey const * const key = &(i->first);
		std::string n = key->_n;
		if (n.substr(n.length() - 4) == "Left")  { m = 1; }
		if (n.substr(n.length() - 5) == "Right") { m = 2; }
		if (which & m) {
			cairo_pattern_destroy(i->second);
			metric_patterns.erase(i++);
		} else {
			++i;
		}
	}

	while (j != ticks_patterns.end()) {
		int m = 4;
		MeterMatricsMapKey const * const key = &(j->first);
		std::string n = key->_n;
		if (n.substr(n.length() - 4) == "Left")  { m = 1; }
		if (n.substr(n.length() - 5) == "Right") { m = 2; }
		if (which & m) {
			cairo_pattern_destroy(j->second);
			ticks_patterns.erase(j++);
		} else {
			++j;
		}
	}
	RedrawMetrics(); /* EMIT SIGNAL */
}
