/***********************************************************************************************************************
*                                                                                                                      *
* glscopeclient                                                                                                        *
*                                                                                                                      *
* Copyright (c) 2012-2021 Andrew D. Zonenberg                                                                          *
* 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 author nor the names of any contributors may be used to endorse or promote products     *
*      derived from this software without specific prior written permission.                                           *
*                                                                                                                      *
* THIS SOFTWARE IS PROVIDED BY THE AUTHORS "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 AUTHORS BE HELD 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.                                                                                          *
*                                                                                                                      *
***********************************************************************************************************************/

/**
	@file
	@author Andrew D. Zonenberg
	@brief  Implementation of WaveformArea
 */
#include "glscopeclient.h"
#include "WaveformArea.h"
#include "OscilloscopeWindow.h"
#include <random>
#include "../../lib/scopeprotocols/scopeprotocols.h"

using namespace std;

bool WaveformArea::m_isGlewInitialized = false;

WaveformArea::WaveformArea(
	StreamDescriptor channel,
	OscilloscopeWindow* parent
	)
	: m_persistence(false)
	, m_channel(channel)
	, m_parent(parent)
	, m_pixelsPerVolt(1)
{
	SharedCtorInit();
}

/**
	@brief Semi-copy constructor, used when copying a waveform to a new group

	Note that we only clone UI settings, the GL context, GTK properties, etc are new!
 */
WaveformArea::WaveformArea(const WaveformArea* clone)
	: m_persistence(clone->m_persistence)
	, m_channel(clone->m_channel)
	, m_parent(clone->m_parent)
	, m_pixelsPerVolt(clone->m_pixelsPerVolt)
	, m_axisLabelFont(clone->m_axisLabelFont)
	, m_infoBoxFont(clone->m_infoBoxFont)
	, m_cursorLabelFont(clone->m_cursorLabelFont)
	, m_decodeFont(clone->m_decodeFont)
{
	SharedCtorInit();
}

void WaveformArea::SharedCtorInit()
{
	m_updatingContextMenu 		= false;
	m_selectedChannel			= m_channel;
	m_dragState 				= DRAG_NONE;
	m_insertionBarLocation		= INSERT_NONE;
	m_dropTarget				= NULL;
	m_padding 					= 2;
	m_overlaySpacing			= 25 * GetDPIScale();
	m_persistenceClear 			= true;
	m_firstFrame 				= false;
	m_waveformRenderData		= NULL;
	m_dragOverlayPosition		= 0;
	m_geometryDirty				= false;
	m_positionDirty				= false;
	m_mouseElementPosition		= LOC_PLOT;
	m_showPendingDecodeAsStats	= false;

	m_plotRight = 1;
	m_width		= 1;
	m_height 	= 1;

	m_decodeDialog 			= NULL;
	m_pendingDecode			= NULL;

	//Configure the OpenGL context we want
	set_has_alpha();
	set_has_depth_buffer(false);
	set_has_stencil_buffer(false);
	set_required_version(4, 2);
	set_use_es(false);

	add_events(
		Gdk::EXPOSURE_MASK |
		Gdk::POINTER_MOTION_MASK |
		Gdk::SCROLL_MASK |
		Gdk::BUTTON_PRESS_MASK |
		Gdk::BUTTON_RELEASE_MASK);

	CreateWidgets();

	m_group = NULL;

	m_channel.m_channel->AddRef();
}

WaveformArea::~WaveformArea()
{
	m_channel.m_channel->Release();

	//Need to reload the menu in case we deleted the last reference to this channel
	m_parent->RefreshChannelsMenu();

	for(auto d : m_overlays)
		OnRemoveOverlay(d);
	m_overlays.clear();

	if(m_decodeDialog)
		delete m_decodeDialog;
	if(m_pendingDecode)
		delete m_pendingDecode;

	for(auto m : m_moveExistingGroupItems)
	{
		m_moveMenu.remove(*m);
		delete m;
	}
	m_moveExistingGroupItems.clear();
}

void WaveformArea::OnRemoveOverlay(StreamDescriptor filter)
{
	//Remove the render data for it
	auto it = m_overlayRenderData.find(filter);
	if(it != m_overlayRenderData.end())
		m_overlayRenderData.erase(it);

	filter.m_channel->Release();

	m_parent->GarbageCollectAnalyzers();
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Initialization

void WaveformArea::CreateWidgets()
{
	//Set up fonts
	SyncFontPreferences();

	//Delete
	auto item = Gtk::manage(new Gtk::MenuItem("Delete", false));
		item->signal_activate().connect(
			sigc::mem_fun(*this, &WaveformArea::OnHide));
		m_contextMenu.append(*item);

	//Move/copy
	m_contextMenu.append(m_moveItem);
		m_moveItem.set_label("Move waveform to");
		m_moveItem.set_submenu(m_moveMenu);
			m_moveMenu.append(m_moveNewGroupBelowItem);
				m_moveNewGroupBelowItem.set_label("Insert new group at bottom");
				m_moveNewGroupBelowItem.signal_activate().connect(
					sigc::mem_fun(*this, &WaveformArea::OnMoveNewBelow));
			m_moveMenu.append(m_moveNewGroupRightItem);
				m_moveNewGroupRightItem.set_label("Insert new group at right");
				m_moveNewGroupRightItem.signal_activate().connect(
					sigc::mem_fun(*this, &WaveformArea::OnMoveNewRight));
			m_moveMenu.append(*Gtk::manage(new Gtk::SeparatorMenuItem));
	m_contextMenu.append(m_copyItem);
		m_copyItem.set_label("Copy waveform to");
		m_copyItem.set_submenu(m_copyMenu);
			m_copyMenu.append(m_copyNewGroupBelowItem);
				m_copyNewGroupBelowItem.set_label("Insert new group at bottom");
				m_copyNewGroupBelowItem.signal_activate().connect(
					sigc::mem_fun(*this, &WaveformArea::OnCopyNewBelow));
			m_copyMenu.append(m_copyNewGroupRightItem);
				m_copyNewGroupRightItem.set_label("Insert new group at right");
				m_copyNewGroupRightItem.signal_activate().connect(
					sigc::mem_fun(*this, &WaveformArea::OnCopyNewRight));

	m_contextMenu.append(*Gtk::manage(new Gtk::SeparatorMenuItem));

	//Persistence
	m_contextMenu.append(m_persistenceItem);
		m_persistenceItem.set_label("Persistence");
		m_persistenceItem.signal_activate().connect(
			sigc::mem_fun(*this, &WaveformArea::OnTogglePersistence));

	m_contextMenu.append(*Gtk::manage(new Gtk::SeparatorMenuItem));

	//Cursor
	m_contextMenu.append(m_cursorItem);
		m_cursorItem.set_label("Cursor");
		m_cursorItem.set_submenu(m_cursorMenu);
			m_cursorMenu.append(m_cursorNoneItem);
				m_cursorNoneItem.set_label("None");
				m_cursorNoneItem.set_group(m_cursorGroup);
				m_cursorNoneItem.signal_activate().connect(
					sigc::bind<WaveformGroup::CursorConfig, Gtk::RadioMenuItem*>(
						sigc::mem_fun(*this, &WaveformArea::OnCursorConfig),
						WaveformGroup::CURSOR_NONE,
						&m_cursorNoneItem));
			m_cursorMenu.append(m_cursorSingleVerticalItem);
				m_cursorSingleVerticalItem.set_label("Vertical (single)");
				m_cursorSingleVerticalItem.set_group(m_cursorGroup);
				m_cursorSingleVerticalItem.signal_activate().connect(
					sigc::bind<WaveformGroup::CursorConfig, Gtk::RadioMenuItem*>(
						sigc::mem_fun(*this, &WaveformArea::OnCursorConfig),
						WaveformGroup::CURSOR_X_SINGLE,
						&m_cursorSingleVerticalItem));
			m_cursorMenu.append(m_cursorDualVerticalItem);
				m_cursorDualVerticalItem.set_label("Vertical (dual)");
				m_cursorDualVerticalItem.set_group(m_cursorGroup);
				m_cursorDualVerticalItem.signal_activate().connect(
					sigc::bind<WaveformGroup::CursorConfig, Gtk::RadioMenuItem*>(
						sigc::mem_fun(*this, &WaveformArea::OnCursorConfig),
						WaveformGroup::CURSOR_X_DUAL,
						&m_cursorDualVerticalItem));
			m_cursorMenu.append(m_cursorSingleHorizontalItem);
				m_cursorSingleHorizontalItem.set_label("Horizontal (single)");
				m_cursorSingleHorizontalItem.set_group(m_cursorGroup);
				m_cursorSingleHorizontalItem.signal_activate().connect(
					sigc::bind<WaveformGroup::CursorConfig, Gtk::RadioMenuItem*>(
						sigc::mem_fun(*this, &WaveformArea::OnCursorConfig),
						WaveformGroup::CURSOR_Y_SINGLE,
						&m_cursorSingleHorizontalItem));
			m_cursorMenu.append(m_cursorDualHorizontalItem);
				m_cursorDualHorizontalItem.set_label("Horizontal (dual)");
				m_cursorDualHorizontalItem.set_group(m_cursorGroup);
				m_cursorDualHorizontalItem.signal_activate().connect(
					sigc::bind<WaveformGroup::CursorConfig, Gtk::RadioMenuItem*>(
						sigc::mem_fun(*this, &WaveformArea::OnCursorConfig),
						WaveformGroup::CURSOR_Y_DUAL,
						&m_cursorDualHorizontalItem));

	m_contextMenu.append(*Gtk::manage(new Gtk::SeparatorMenuItem));

	//Coupling
	m_contextMenu.append(m_couplingItem);
		m_couplingItem.set_label("Coupling");
		m_couplingItem.set_submenu(m_couplingMenu);
			m_ac1MCouplingItem.set_label("AC 1M");
				m_ac1MCouplingItem.set_group(m_couplingGroup);
				m_ac1MCouplingItem.signal_activate().connect(
					sigc::bind<OscilloscopeChannel::CouplingType, Gtk::RadioMenuItem*>(
						sigc::mem_fun(*this, &WaveformArea::OnCoupling),
						OscilloscopeChannel::COUPLE_AC_1M, &m_ac1MCouplingItem));
				m_couplingMenu.append(m_ac1MCouplingItem);
			m_dc1MCouplingItem.set_label("DC 1M");
				m_dc1MCouplingItem.set_group(m_couplingGroup);
				m_dc1MCouplingItem.signal_activate().connect(
					sigc::bind<OscilloscopeChannel::CouplingType, Gtk::RadioMenuItem*>(
						sigc::mem_fun(*this, &WaveformArea::OnCoupling),
						OscilloscopeChannel::COUPLE_DC_1M, &m_dc1MCouplingItem));
				m_couplingMenu.append(m_dc1MCouplingItem);
			m_dc50CouplingItem.set_label("DC 50Ω");
				m_dc50CouplingItem.set_group(m_couplingGroup);
				m_dc50CouplingItem.signal_activate().connect(
					sigc::bind<OscilloscopeChannel::CouplingType, Gtk::RadioMenuItem*>(
						sigc::mem_fun(*this, &WaveformArea::OnCoupling),
						OscilloscopeChannel::COUPLE_DC_50, &m_dc50CouplingItem));
				m_couplingMenu.append(m_dc50CouplingItem);
			m_ac50CouplingItem.set_label("AC 50Ω");
				m_ac50CouplingItem.set_group(m_couplingGroup);
				m_ac50CouplingItem.signal_activate().connect(
					sigc::bind<OscilloscopeChannel::CouplingType, Gtk::RadioMenuItem*>(
						sigc::mem_fun(*this, &WaveformArea::OnCoupling),
						OscilloscopeChannel::COUPLE_AC_50, &m_ac50CouplingItem));
				m_couplingMenu.append(m_ac50CouplingItem);
			m_gndCouplingItem.set_label("GND");
				m_gndCouplingItem.set_group(m_couplingGroup);
				m_gndCouplingItem.signal_activate().connect(
					sigc::bind<OscilloscopeChannel::CouplingType, Gtk::RadioMenuItem*>(
						sigc::mem_fun(*this, &WaveformArea::OnCoupling),
						OscilloscopeChannel::COUPLE_GND, &m_gndCouplingItem));
				m_couplingMenu.append(m_gndCouplingItem);

	m_contextMenu.append(*Gtk::manage(new Gtk::SeparatorMenuItem));

	//Decode
	m_contextMenu.append(m_decodeAlphabeticalItem);
		m_decodeAlphabeticalItem.set_label("Alphabetical");
		m_decodeAlphabeticalItem.set_submenu(m_decodeAlphabeticalMenu);
	m_contextMenu.append(m_decodeBusItem);
		m_decodeBusItem.set_label("Buses");
		m_decodeBusItem.set_submenu(m_decodeBusMenu);
	m_contextMenu.append(m_decodeClockItem);
		m_decodeClockItem.set_label("Clocking");
		m_decodeClockItem.set_submenu(m_decodeClockMenu);
	m_contextMenu.append(m_decodeMathItem);
		m_decodeMathItem.set_label("Math");
		m_decodeMathItem.set_submenu(m_decodeMathMenu);
	m_contextMenu.append(m_decodeMeasurementItem);
		m_decodeMeasurementItem.set_label("Measurement");
		m_decodeMeasurementItem.set_submenu(m_decodeMeasurementMenu);
	m_contextMenu.append(m_decodeMemoryItem);
		m_decodeMemoryItem.set_label("Memory");
		m_decodeMemoryItem.set_submenu(m_decodeMemoryMenu);
	m_contextMenu.append(m_decodeMiscItem);
		m_decodeMiscItem.set_label("Misc");
		m_decodeMiscItem.set_submenu(m_decodeMiscMenu);
	m_contextMenu.append(m_decodePowerItem);
		m_decodePowerItem.set_label("Power");
		m_decodePowerItem.set_submenu(m_decodePowerMenu);
	m_contextMenu.append(m_decodeRFItem);
		m_decodeRFItem.set_label("RF");
		m_decodeRFItem.set_submenu(m_decodeRFMenu);
	m_contextMenu.append(m_decodeSerialItem);
		m_decodeSerialItem.set_label("Serial");
		m_decodeSerialItem.set_submenu(m_decodeSerialMenu);
	m_contextMenu.append(m_decodeSignalIntegrityItem);
		m_decodeSignalIntegrityItem.set_label("Signal Integrity");
		m_decodeSignalIntegrityItem.set_submenu(m_decodeSignalIntegrityMenu);


		vector<string> names;
		Filter::EnumProtocols(names);
		for(auto p : names)
		{
			item = Gtk::manage(new Gtk::MenuItem(p, false));
			item->signal_activate().connect(
				sigc::bind<string, bool>(sigc::mem_fun(*this, &WaveformArea::OnProtocolDecode), p, false));

			//Create a test decode and see where it goes
			auto d = Filter::CreateFilter(p, "");
			switch(d->GetCategory())
			{
				case Filter::CAT_ANALYSIS:
					m_decodeSignalIntegrityMenu.append(*item);
					break;

				case Filter::CAT_BUS:
					m_decodeBusMenu.append(*item);
					break;

				case Filter::CAT_CLOCK:
					m_decodeClockMenu.append(*item);
					break;

				case Filter::CAT_POWER:
					m_decodePowerMenu.append(*item);
					break;

				case Filter::CAT_RF:
					m_decodeRFMenu.append(*item);
					break;

				//Measurements need some special processing
				case Filter::CAT_MEASUREMENT:

					//Scalar measurement? Just add this item
					if(d->IsScalarOutput())
						m_decodeMeasurementMenu.append(*item);

					//Vector measurements have two possible displays (graph and statistics)
					else
					{
						auto childmenu = Gtk::manage(new Gtk::Menu);
						childmenu->append(*item);
						item->set_label("Graph");

						//Summarize
						item = Gtk::manage(new Gtk::MenuItem("Stats", false));
						item->signal_activate().connect(
							sigc::bind<string, bool>(sigc::mem_fun(*this, &WaveformArea::OnProtocolDecode), p, true));
						childmenu->append(*item);

						auto m = Gtk::manage(new Gtk::MenuItem(p, false));
						m->set_submenu(*childmenu);
						m_decodeMeasurementMenu.append(*m);
					}

					break;

				case Filter::CAT_MATH:
					m_decodeMathMenu.append(*item);
					break;

				case Filter::CAT_MEMORY:
					m_decodeMemoryMenu.append(*item);
					break;

				case Filter::CAT_SERIAL:
					m_decodeSerialMenu.append(*item);
					break;

				default:
				case Filter::CAT_MISC:
					m_decodeMiscMenu.append(*item);
					break;
			}
			delete d;

			//Make a second menu item and put on the alphabetical list
			item = Gtk::manage(new Gtk::MenuItem(p, false));
			item->signal_activate().connect(
				sigc::bind<string>(sigc::mem_fun(*this, &WaveformArea::OnProtocolDecode), p, false));
			m_decodeAlphabeticalMenu.append(*item);
		}

	m_contextMenu.append(*Gtk::manage(new Gtk::SeparatorMenuItem));

	//Statistics
	m_contextMenu.append(m_statisticsItem);
		m_statisticsItem.set_label("Statistics");
		m_statisticsItem.signal_activate().connect(
			sigc::mem_fun(*this, &WaveformArea::OnStatistics));


	m_contextMenu.show_all();
}

void WaveformArea::on_realize()
{
	//Let the base class create the GL context, then select it
	Gtk::GLArea::on_realize();
	make_current();

	//Apply DPI scaling to Pango fonts since we are not using Cairo scaling
	auto c = get_pango_context();
	c->set_resolution(c->get_resolution() * get_window()->get_scale_factor());

	//Set up GLEW
	if(!m_isGlewInitialized)
	{
		//Check if GL was initialized OK
		if(has_error())
		{
			//doesn't seem to be any way to get this error without throwing it??
			try
			{
				throw_if_error();
			}
			catch(Glib::Error& gerr)
			{
				string err =
					"glscopeclient was unable to initialize OpenGL and cannot continue.\n"
					"This probably indicates a problem with your graphics card drivers.\n"
					"\n"
					"GL error: ";
				err += gerr.what();

				Gtk::MessageDialog dlg(
					err,
					false,
					Gtk::MESSAGE_ERROR,
					Gtk::BUTTONS_OK,
					true
					);

				dlg.run();
				exit(1);
			}
		}

		//Print some debug info
		auto context = get_context();
		if(!context)
			LogFatal("context is null but we don't have an error set in GTK\n");
		int major, minor;
		context->get_version(major, minor);
		string profile = "compatibility";
		if(context->is_legacy())
			profile = "legacy";
		else if(context->get_forward_compatible())
			profile = "core";
		string type = "";
		if(context->get_use_es())
			type = " ES";
		LogDebug("Context: OpenGL%s %d.%d %s profile\n",
			type.c_str(),
			major, minor,
			profile.c_str());
		{
			LogIndenter li;
			LogDebug("GL_VENDOR                   = %s\n", glGetString(GL_VENDOR));
			LogDebug("GL_RENDERER                 = %s\n", glGetString(GL_RENDERER));
			LogDebug("GL_VERSION                  = %s\n", glGetString(GL_VERSION));
			LogDebug("GL_SHADING_LANGUAGE_VERSION = %s\n", glGetString(GL_SHADING_LANGUAGE_VERSION));
			LogDebug("Initial GL error code       = %d\n", glGetError());
		}

		//Initialize GLEW
		GLenum glewResult = glewInit();
		if (glewResult != GLEW_OK)
		{
			string err =
				"glscopeclient was unable to initialize GLEW and cannot continue.\n"
				"This probably indicates a problem with your graphics card drivers.\n"
				"\n"
				"GLEW error: ";
			err += (const char*)glewGetErrorString(glewResult);

			Gtk::MessageDialog dlg(
				err,
				false,
				Gtk::MESSAGE_ERROR,
				Gtk::BUTTONS_OK,
				true
				);

			dlg.run();
			exit(1);
		}

		if(GLEW_ARB_gpu_shader_int64)
		{
			LogDebug("    GL_ARB_gpu_shader_int64     = supported\n");
			if(g_noglint64)
				LogDebug("    but not being used because --noglint64 argument was passed\n");
		}
		else
			LogDebug("    GL_ARB_gpu_shader_int64     = not supported\n");

		//Check for GL 4.2 (required for glBindImageTexture)
		if(!GLEW_VERSION_4_2)
		{
			string err =
				"Your graphics card or driver does not appear to support OpenGL 4.2.\n"
				"\n"
				"Unfortunately, glscopeclient cannot run on your system.\n";

			Gtk::MessageDialog dlg(
				err,
				false,
				Gtk::MESSAGE_ERROR,
				Gtk::BUTTONS_OK,
				true
				);

			dlg.run();
			exit(1);
		}

		//Make sure we have the required extensions
		if(	!GLEW_EXT_blend_equation_separate ||
			!GLEW_EXT_framebuffer_object ||
			!GLEW_ARB_vertex_array_object ||
			!GLEW_ARB_shader_storage_buffer_object ||
			!GLEW_ARB_arrays_of_arrays ||
			!GLEW_ARB_compute_shader)
		{
			string err =
				"Your graphics card or driver does not appear to support one or more of the following required extensions:\n"
				"* GL_ARB_arrays_of_arrays\n"
				"* GL_ARB_compute_shader\n"
				"* GL_ARB_shader_storage_buffer_object\n"
				"* GL_ARB_vertex_array_object\n"
				"* GL_EXT_blend_equation_separate\n"
				"* GL_EXT_framebuffer_object\n"
				"\n"
				"Unfortunately, glscopeclient cannot run on your system.\n";

			Gtk::MessageDialog dlg(
				err,
				false,
				Gtk::MESSAGE_ERROR,
				Gtk::BUTTONS_OK,
				true
				);

			dlg.run();
			exit(1);
		}

		m_isGlewInitialized = true;
	}

	//We're about to draw the first frame after realization.
	//This means we need to save some configuration (like the current FBO) that GTK doesn't tell us directly
	m_firstFrame = true;

	//Create waveform render data for our main trace
	m_waveformRenderData = new WaveformRenderData(m_channel, this);

	//Set stuff up for each rendering pass
	InitializeWaveformPass();
	InitializeColormapPass();
	InitializeCairoPass();
	InitializeEyePass();
	InitializeSpectrogramPass();
}

void WaveformArea::on_unrealize()
{
	make_current();

	CleanupGLHandles();

	Gtk::GLArea::on_unrealize();
}

void WaveformArea::CleanupGLHandles()
{
	//Clean up old shaders
	m_histogramWaveformComputeProgram.Destroy();
	m_digitalWaveformComputeProgram.Destroy();
	m_analogWaveformComputeProgram.Destroy();
	m_denseAnalogWaveformComputeProgram.Destroy();
	m_colormapProgram.Destroy();
	m_eyeProgram.Destroy();
	m_spectrogramProgram.Destroy();
	m_cairoProgram.Destroy();

	//Clean up old VAOs
	m_colormapVAO.Destroy();
	m_cairoVAO.Destroy();
	m_eyeVAO.Destroy();
	m_spectrogramVAO.Destroy();

	//Clean up old VBOs
	m_colormapVBO.Destroy();
	m_cairoVBO.Destroy();
	m_eyeVBO.Destroy();
	m_spectrogramVBO.Destroy();

	//Clean up old textures
	m_cairoTexture.Destroy();
	m_cairoTextureOver.Destroy();
	for(auto& e : m_eyeColorRamp)
		e.Destroy();

	delete m_waveformRenderData;
	m_waveformRenderData = NULL;
	for(auto it : m_overlayRenderData)
		delete it.second;
	m_overlayRenderData.clear();

	//Detach the FBO so we don't destroy it!!
	//GTK manages this, and it might be used by more than one waveform area within the application.
	m_windowFramebuffer.Detach();
}

void WaveformArea::InitializeWaveformPass()
{
	//Load all of the compute shaders
	ComputeShader hwc;
	ComputeShader dwc;
	ComputeShader awc;
	ComputeShader adwc;
	if(GLEW_ARB_gpu_shader_int64 && !g_noglint64)
	{
		if(!hwc.Load(
			"#version 420",
			"shaders/waveform-compute-head.glsl",
			"shaders/waveform-compute-histogram.glsl",
			"shaders/waveform-compute-core.glsl",
			NULL))
			LogFatal("failed to load histogram waveform compute shader, aborting\n");
		if(!dwc.Load(
			"#version 420",
			"shaders/waveform-compute-head.glsl",
			"shaders/waveform-compute-digital.glsl",
			"shaders/waveform-compute-core.glsl",
			NULL))
			LogFatal("failed to load digital waveform compute shader, aborting\n");
		if(!awc.Load(
			"#version 420",
			"shaders/waveform-compute-head.glsl",
			"shaders/waveform-compute-analog.glsl",
			"shaders/waveform-compute-core.glsl",
			NULL))
			LogFatal("failed to load analog waveform compute shader, aborting\n");
		if(!adwc.Load(
			"#version 420",
			"#define DENSE_PACK",
			"shaders/waveform-compute-head.glsl",
			"shaders/waveform-compute-analog.glsl",
			"shaders/waveform-compute-core.glsl",
			NULL))
			LogFatal("failed to load dense analog waveform compute shader, aborting\n");
	}
	else
	{
		if(!hwc.Load(
			"#version 420",
			"shaders/waveform-compute-head-noint64.glsl",
			"shaders/waveform-compute-histogram.glsl",
			"shaders/waveform-compute-core.glsl",
			NULL))
			LogFatal("failed to load histogram waveform compute shader, aborting\n");
		if(!dwc.Load(
			"#version 420",
			"shaders/waveform-compute-head-noint64.glsl",
			"shaders/waveform-compute-digital.glsl",
			"shaders/waveform-compute-core.glsl",
			NULL))
			LogFatal("failed to load digital waveform compute shader, aborting\n");
		if(!awc.Load(
			"#version 420",
			"shaders/waveform-compute-head-noint64.glsl",
			"shaders/waveform-compute-analog.glsl",
			"shaders/waveform-compute-core.glsl",
			NULL))
			LogFatal("failed to load analog waveform compute shader, aborting\n");
		if(!adwc.Load(
			"#version 420",
			"#define DENSE_PACK",
			"shaders/waveform-compute-head-noint64.glsl",
			"shaders/waveform-compute-analog.glsl",
			"shaders/waveform-compute-core.glsl",
			NULL))
			LogFatal("failed to load dense analog waveform compute shader, aborting\n");
	}

	//Link them
	m_histogramWaveformComputeProgram.Add(hwc);
	if(!m_histogramWaveformComputeProgram.Link())
		LogFatal("failed to link histogram waveform shader program, aborting\n");

	m_digitalWaveformComputeProgram.Add(dwc);
	if(!m_digitalWaveformComputeProgram.Link())
		LogFatal("failed to link digital waveform shader program, aborting\n");

	m_analogWaveformComputeProgram.Add(awc);
	if(!m_analogWaveformComputeProgram.Link())
		LogFatal("failed to link analog waveform shader program, aborting\n");

	m_denseAnalogWaveformComputeProgram.Add(adwc);
	if(!m_denseAnalogWaveformComputeProgram.Link())
		LogFatal("failed to link dense analog waveform shader program, aborting\n");
}

void WaveformArea::InitializeColormapPass()
{
	//Set up shaders
	VertexShader cvs;
	FragmentShader cfs;
	if(!cvs.Load("shaders/colormap-vertex.glsl", NULL) || !cfs.Load("shaders/colormap-fragment.glsl", NULL))
		LogFatal("failed to load colormap shaders, aborting\n");

	m_colormapProgram.Add(cvs);
	m_colormapProgram.Add(cfs);
	if(!m_colormapProgram.Link())
		LogFatal("failed to link shader program, aborting\n");

	//Create the VAO/VBO for a fullscreen polygon
	float verts[8] =
	{
		-1, -1,
		 1, -1,
		 1,  1,
		-1,  1
	};
	m_colormapVBO.Bind();
	glBufferData(GL_ARRAY_BUFFER, sizeof(verts), verts, GL_STATIC_DRAW);

	m_colormapVAO.Bind();
	m_colormapProgram.EnableVertexArray("vert");
	m_colormapProgram.SetVertexAttribPointer("vert", 2, 0);
}

void WaveformArea::InitializeEyePass()
{
	//Set up shaders
	VertexShader cvs;
	FragmentShader cfs;
	if(!cvs.Load("shaders/eye-vertex.glsl", NULL) || !cfs.Load("shaders/eye-fragment.glsl", NULL))
		LogFatal("failed to load eye shaders, aborting\n");

	m_eyeProgram.Add(cvs);
	m_eyeProgram.Add(cfs);
	if(!m_eyeProgram.Link())
		LogFatal("failed to link shader program, aborting\n");

	//Create the VAO/VBO for a fullscreen polygon
	float verts[8] =
	{
		-1, -1,
		 1, -1,
		 1,  1,
		-1,  1
	};
	m_eyeVBO.Bind();
	glBufferData(GL_ARRAY_BUFFER, sizeof(verts), verts, GL_STATIC_DRAW);

	m_eyeVAO.Bind();
	m_eyeProgram.EnableVertexArray("vert");
	m_eyeProgram.SetVertexAttribPointer("vert", 2, 0);

	//Load the eye color ramp
	char tmp[1024];
	const char* fnames[OscilloscopeWindow::NUM_EYE_COLORS];
	fnames[OscilloscopeWindow::EYE_CRT] = "gradients/eye-gradient-crt.rgba";
	fnames[OscilloscopeWindow::EYE_IRONBOW] = "gradients/eye-gradient-ironbow.rgba";
	fnames[OscilloscopeWindow::EYE_KRAIN] = "gradients/eye-gradient-krain.rgba";
	fnames[OscilloscopeWindow::EYE_RAINBOW] = "gradients/eye-gradient-rainbow.rgba";
	fnames[OscilloscopeWindow::EYE_GRAYSCALE] = "gradients/eye-gradient-grayscale.rgba";
	fnames[OscilloscopeWindow::EYE_VIRIDIS] = "gradients/eye-gradient-viridis.rgba";
	for(int i=0; i<OscilloscopeWindow::NUM_EYE_COLORS; i++)
	{
		string path = FindDataFile(fnames[i]);
		FILE* fp = fopen(path.c_str(), "r");
		if(!fp)
			LogFatal("fail to open eye gradient");
		fread(tmp, 1, 1024, fp);
		fclose(fp);

		m_eyeColorRamp[i].Bind();
		ResetTextureFiltering();
		m_eyeColorRamp[i].SetData(256, 1, tmp, GL_RGBA);
	}
}

void WaveformArea::InitializeSpectrogramPass()
{
	//Set up shaders
	VertexShader cvs;
	FragmentShader cfs;
	if(!cvs.Load("shaders/spectrogram-vertex.glsl", NULL) || !cfs.Load("shaders/spectrogram-fragment.glsl", NULL))
		LogFatal("failed to load spectrogram shaders, aborting\n");

	m_spectrogramProgram.Add(cvs);
	m_spectrogramProgram.Add(cfs);
	if(!m_spectrogramProgram.Link())
		LogFatal("failed to link shader program, aborting\n");

	//Create the VAO/VBO for a fullscreen polygon
	float verts[8] =
	{
		-1, -1,
		 1, -1,
		 1,  1,
		-1,  1
	};
	m_spectrogramVBO.Bind();
	glBufferData(GL_ARRAY_BUFFER, sizeof(verts), verts, GL_STATIC_DRAW);

	m_spectrogramVAO.Bind();
	m_spectrogramProgram.EnableVertexArray("vert");
	m_spectrogramProgram.SetVertexAttribPointer("vert", 2, 0);
}

void WaveformArea::InitializeCairoPass()
{
	//Set up shaders
	VertexShader cvs;
	FragmentShader cfs;
	if(!cvs.Load("shaders/cairo-vertex.glsl", NULL) || !cfs.Load("shaders/cairo-fragment.glsl", NULL))
		LogFatal("failed to load cairo shaders, aborting\n");

	m_cairoProgram.Add(cvs);
	m_cairoProgram.Add(cfs);
	if(!m_cairoProgram.Link())
		LogFatal("failed to link shader program, aborting\n");

	//Create the VAO/VBO for a fullscreen polygon
	float verts[8] =
	{
		-1, -1,
		 1, -1,
		 1,  1,
		-1,  1
	};
	m_cairoVBO.Bind();
	glBufferData(GL_ARRAY_BUFFER, sizeof(verts), verts, GL_STATIC_DRAW);

	m_cairoVAO.Bind();
	m_cairoProgram.EnableVertexArray("vert");
	m_cairoProgram.SetVertexAttribPointer("vert", 2, 0);
}

bool WaveformArea::IsWaterfall()
{
	auto fall = dynamic_cast<Waterfall*>(m_channel.m_channel);
	return (fall != NULL);
}

bool WaveformArea::IsDigital()
{
	return (m_channel.m_channel->GetType() == OscilloscopeChannel::CHANNEL_TYPE_DIGITAL);
}

bool WaveformArea::IsAnalog()
{
	return (m_channel.m_channel->GetType() == OscilloscopeChannel::CHANNEL_TYPE_ANALOG);
}

bool WaveformArea::IsEye()
{
	return (m_channel.m_channel->GetType() == OscilloscopeChannel::CHANNEL_TYPE_EYE);
}

bool WaveformArea::IsSpectrogram()
{
	return (m_channel.m_channel->GetType() == OscilloscopeChannel::CHANNEL_TYPE_SPECTROGRAM);
}

bool WaveformArea::IsEyeOrBathtub()
{
	//TODO: this should really be "is fixed two UI wide plot"
	auto bath = dynamic_cast<HorizontalBathtub*>(m_channel.m_channel);
	return IsEye() || (bath != NULL);
}

bool WaveformArea::IsTime()
{
	return (m_channel.m_channel->GetYAxisUnits().GetType() == Unit::UNIT_FS);
}
