/* 
 * transferFuncDialog.cpp - Transfer function editor dialog
 * Copyright (C) 2018, D Haley
 * 
 * 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 3 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, see <http://www.gnu.org/licenses/>.
 */
#ifndef TRANSFERFUNCDIALOG_H
#define TRANSFERFUNCDIALOG_H

#include <wx/wx.h>
#include <wx/image.h>
#include <wx/intl.h>
#include <wx/statline.h>

#include "common/basics.h"

#include <vector>
#include <utility>

#include <sstream>


class wxGradientStopPanel : public wxPanel
{
	//Linear colour stops, and the fractional x coordinate that we draw the gradient at
	std::vector<std::pair<float, ColourRGBAf>  > stops;

	unsigned int direction;

	DECLARE_EVENT_TABLE();
	public:
		wxGradientStopPanel(wxWindow* parent, wxWindowID id);

		//Set the stop colour
		void setStops(std::vector<std::pair<float, ColourRGBAf>  > &newStops);
		
		void setDirection(unsigned int dir){direction=dir;};

		void clearStops();

		//Draw panel content	
		void onPaint(wxPaintEvent& evt);
};

//Provides data storage, coordinate conversion and drawing
// routines for a multiple-line piecewise linear graph
// with shared X coordinates
class MultiLineGraph 
{
	private:
		//X-coordinates for line data
		std::vector<float> xData;
		//Y coordinates for each line data. Outer vector is per line, inner is per
		// x coord
		std::vector<std::vector<float > > lineYData;
		//Colour for each line
		std::vector<ColourRGBAf> lineColour;

		//Bounding box for the graph
		std::pair<float,float> boundMin,boundMax;



		//The node that is currently selected (set, node index) pairing
		std::pair<unsigned int, unsigned int> selectedNode;

		//Which lines are visible?
		std::vector<bool> visibilityMask;

		//Set the y coordinate data. 
		void setYData(const std::vector<std::vector<float > > &lineYData);
		//Check the sanity of the incoming data
		bool isSelfConsistent() const; 

	public:
		MultiLineGraph() {clearActiveNode();};

		//Set if we wish to ensure that all the line data shares the same 
		void linkGraph(bool xLink);

		void draw(wxDC &dc);

		void insertPoint(unsigned int trace, float x,float y);

		unsigned int numXPts() const {return xData.size();};

		void clear();


		void setData(const std::vector<float> &xd, 
				const std::vector<std::vector<float > > &yd,
				const std::vector<ColourRGBAf> &colour);
		
		//Obtain the value of the ith x coordinate
		float getXValue(unsigned int xOffset) const;
		
		
		//Obtain the value of the ith x coordinate
		std::vector<float> sliceYData(unsigned int xOffset) const;

		void setBounds(float minX,float minY, float maxX,float maxY);
		void getBounds(float &minX,float &minY, float &maxX,float &maxY);

		//Set the value of the node using fractional coordinates
		void setNodeByFraction(unsigned int set, unsigned int node,
				float fX,float fY);

		//return the node or line index, or -1 if no node. lineIndex is also set to -1 if no line
		unsigned int getNodeIndex(float fracX, float fracY, unsigned int &lineIndex) const;

		//Get the index of the vertical line group (sharing same X)
		unsigned int getGroupIndex(float fracX, float tolerance) const;

		//return graph coordinates for node and particular trace (set)
		void getNodeCoords(unsigned int curset, unsigned int curNode, float &x,float &y) const;
		//Convert from fractional window coords to graph coords
		void toGraph(float pX, float pY, float &gx, float &gy) const;
		
		//Convert from graph coordinates to fractional window coordinates
		void toWindowFraction(float graphX, float graphY, float &fx, float &fy) const;


		//Obtain the graph coordinates of the two adjacent nodes
		void getAdjacentNodeCoords(unsigned int curSet, 
				unsigned int curNode, float *x, float *y) const;

		//Tell the graph, for visual purposes, which node is considered "active"
		void setActiveNode(unsigned int set, unsigned int node);
		//Reset the active node to null state
		void clearActiveNode();

		//Find the y-projected point on the closest line, and return its set value, and
		// in addition, return the projected Y coordinate. return -1 on failure
		unsigned int projectToNearestLine(float graphX, float graphY, float &projY) const;

		//Insert new nodes on all lines at the specified position
		// return false if we can't interpolate the node
		bool spliceNewNodes(float graphX);

		//remove any node, except the two outer nodes
		void removeSelectedNode();

		//returned pair is (set , x-offset)
		std::pair<unsigned int, unsigned int> getSelectedNode() const { return selectedNode;}

		//Get which traces are visible to & interactive for the user
		void getVisibilityMask(std::vector<bool> &mask) const;
		//Set which traces are visible to & interactive for the user
		void setVisibilityMask(const std::vector<bool> &mask);

		//Move the given node set to a new coordinate
		void moveXData(unsigned int node, float newX);


		//!Set all Y values for the given node. Array must be the
		// same size as the number of traces
		void setYSlice(unsigned int node, float *y);

		//!Obtain the X data points that we have available
		void getXData(std::vector<float> &f) const;
		//Obtain the Y data points that we have available. Outer vector is per line
		// inner is per X point (getXData should return same size)
		void getYData(std::vector<std::vector<float> > &f) const;
};

//Provides interactivity for a shared-x-coordinate graph
class wxMultiLineGraphPanel : public wxPanel
{
	protected:
		//The current drag status
		unsigned int dragMode;
		//The node that has the drag status applied, or -1
		unsigned int curNode;
		//The group of nodes that has the drag status set, or -1
		unsigned int curSet;

		//The window coordinates of the two adjacent nodes
		// so we can draw a "tie-line" whilst dragging
		// negative if invalid
		float graphTieX[2],graphTieY[2];

		//Fractional coordinates of the currently hovered-over node
		// or negative if not valid
		float mouseHover[2];

		//last mouse cursor position, or -1 on fail
		// used when drawing node/bar drag
		unsigned int lastMouseX,lastMouseY;

		//Link gradient drawing areas so we can update them as needed
		wxGradientStopPanel *horizGradientPanel,*vertGradientPanel;

		//Link the colour picking button so we can enable it
		wxButton *colourPickButton;

		DECLARE_EVENT_TABLE();
	public:
		wxMultiLineGraphPanel(wxWindow* parent, wxWindowID id);

		MultiLineGraph graph;

		void setHorizGradientPanel(wxGradientStopPanel *gPanel);
		void setVertGradientPanel(wxGradientStopPanel *gPanel);
		void setColourPickButton(wxButton *btn);


		void setTransferFunction(const std::vector<std::pair<float,ColourRGBAf> > &t);
		//TODO : Refactor me. This forces the frame to be self consistent with the graph data
		void update();


		void OnEraseBackground(wxEraseEvent& event);

		//!If appropriate, remove the current node
		void removeCurrentNode();

		void mouseMove(wxMouseEvent& event);
		void mouseDown(wxMouseEvent& event);
		void mouseReleased(wxMouseEvent& event);
		void mouseLeftWindow(wxMouseEvent& event);
		void mouseLeftClick(wxMouseEvent& event);
		void onPaint(wxPaintEvent& evt);
		void onResize(wxSizeEvent& evt);
};

//!This dialog allows a user to edit a "transfer function", which are a series of colour stops
// that create a colour scale
class TransferEditorDialog: public wxDialog{
	public:
		// begin wxGlade: TransferEditorDialog::ids
		// end wxGlade

		TransferEditorDialog(wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos=wxDefaultPosition, const wxSize& size=wxDefaultSize, long style=wxDEFAULT_FRAME_STYLE);

		//FIXME: Expose through function, rather than making public
		wxMultiLineGraphPanel* graphPanel;

		std::vector<std::pair<float,ColourRGBAf> > getTransferFunction() const;

		bool finished() {return completeOK;}

	private:
		bool completeOK;

		//Set the initial graph up, using some default values
		void initDefault();
		// begin wxGlade: TransferEditorDialog::methods
		void set_properties();
		void do_layout();
		// end wxGlade

	protected:
		// begin wxGlade: TransferEditorDialog::attributes
		wxGradientStopPanel* leftColourBar;
		wxStaticText* textLabelMax;
		wxStaticText* labelMin;
		wxGradientStopPanel* bottomColourBar;
		wxStaticText* labelTraces;
		wxCheckBox* checkRed_or_Hue;
		wxCheckBox* checkGreen_or_Sat;
		wxCheckBox* checkBlue_or_value;
		wxCheckBox* checkOpacity;
		wxButton* colourPickButton;
		wxButton* nodeRemoveButton;
		wxStaticLine* static_line_1;
		wxStaticText* lblFunction;
		wxButton* buttonLoad;
		wxButton* buttonSave;
		wxButton* buttonOK;
		wxButton* buttonCancel;
		// end wxGlade

		DECLARE_EVENT_TABLE();

	public:
		void OnBtnSave(wxCommandEvent &event); // wxGlade: <event_handler>
		void OnBtnLoad(wxCommandEvent &event); // wxGlade: <event_handler>
		void OnCheckRed(wxCommandEvent &event); // wxGlade: <event_handler>
		void OnCheckGreen(wxCommandEvent &event); // wxGlade: <event_handler>
		void OnCheckBlue(wxCommandEvent &event); // wxGlade: <event_handler>
		void OnCheckOpacity(wxCommandEvent &event); // wxGlade: <event_handler>
		void OnNodeColourButton(wxCommandEvent &event); // wxGlade: <event_handler>
		void OnNodeRemoveButton(wxCommandEvent &event); // wxGlade: <event_handler>
		void OnButtonLoad(wxCommandEvent &event); // wxGlade: <event_handler>
		void OnButtonSave(wxCommandEvent &event); // wxGlade: <event_handler>
		void OnOK(wxCommandEvent &event); // wxGlade: <event_handler>
		void OnCancel(wxCommandEvent &event); // wxGlade: <event_handler>

}; // wxGlade: end class


#endif // TRANSFERFUNCDIALOG_H
// -*- C++ -*-
