/*
 * Copyright (c) 2001-2006 MUSIC TECHNOLOGY GROUP (MTG)
 *                         UNIVERSITAT POMPEU FABRA
 *
 *
 * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */

#include "CLAMGL.hxx"
#include "vmLocatorRenderer.hxx"
#include "vmPlotCanvas.hxx"

namespace CLAM
{
	namespace VM
	{
		Locator::Locator()
			: mUpdatingLocator(false)
			, mLocatorPos(0.0)
			, mRegion(0.0,0.0)
			, mLocatorColor(0,0,0)
			, mRegionColor(185,185,185)
			, mEditionMode(Idle)
		{
		}

		Locator::~Locator()
		{
		}

		void Locator::SetLocatorColor(const QColor& c)
		{
			mLocatorColor = c;
		}

		void Locator::SetRegionColor(const QColor& c)
		{
			mRegionColor = c;
		}

		void Locator::updateLocator(double value)
		{
			if(!mUpdatingLocator) mUpdatingLocator = true;
			mLocatorPos = value;
			_container->needUpdate();
		}

		void Locator::updateLocator(double value, bool flag)
		{
			mUpdatingLocator = false;
			mLocatorPos = (flag) ? value : mRegion.min;
			double zoom_ref;
			if(mRegion.min < mRegion.max)
			{
				if(flag)
				{
					mRegion.min = mLocatorPos;
					emit selectedRegion(mRegion.min,mRegion.max);
				}
				zoom_ref = mRegion.min+mRegion.Span()/2.0;
			}
			else
			{
				if(flag) 
				{
					mRegion.min = mRegion.max = mLocatorPos;
					emit selectedRegion(mLocatorPos,mXRange.max);
				}
				zoom_ref = mLocatorPos;
			}
			_container->setHZoomPivot(zoom_ref);
			_container->needUpdate();
		}

		void Locator::updateRegion(double begin, double end, bool isEnd)
		{
			if(begin==end)
			{
				mLocatorPos = begin;
				mRegion.min = mRegion.max = mLocatorPos;
				_container->setHZoomPivot(mLocatorPos);
			}
			else
			{
				mRegion.min = begin;
				mRegion.max = end;
				(isEnd) ? mLocatorPos = end : mLocatorPos = begin;
				_container->setHZoomPivot(mRegion.min+mRegion.Span()/2.0);
			}
			_container->needUpdate();
		}

		void Locator::Render()
		{
			if(!mEnabled) return;
			DrawRegion(DrawRegionMode());
			DrawLocator();
		}

		void Locator::SetXRange(double xmin, double xmax)
		{
			Renderer2D::SetXRange(xmin,xmax);
			mLocatorPos = mXRange.min;
		}

		void Locator::MousePressEvent(double x, double y)
		{
			if(!mCatchEvents) return;
			if(!mEnabled) return;
			if(mUpdatingLocator) return;
			if(x == mRegion.min && x == mRegion.max) return;		
			if(mKeyboard.key_shift && mKeyboard.key_r)
			{
				if(mRegion.min != mRegion.max)
				{
					double ref = mRegion.min+mRegion.Span()/2.0;
					if(x <= ref) 
					{
						mRegion.min = x;  
						mLocatorPos = mRegion.min;
					}
					else
					{
						mRegion.max = x;
						mLocatorPos = mRegion.max;
					}
				}
				else
				{
					if(x < mRegion.min)  
					{
						mRegion.min = x;
						mLocatorPos = mRegion.min;
					}
					else
					{
						mRegion.max = x;
						mLocatorPos = mRegion.max;
					}
				}
				emit regionChanged(mRegion.min,mRegion.max,mLocatorPos > mRegion.min);
				_container->setHZoomPivot(mRegion.min+mRegion.Span()/2.0);
				_container->needUpdate();
				return;
			}
			if(mKeyboard.key_r)
			{
				mLocatorPos = x;
				mRegion.min = mRegion.max = mLocatorPos;
				emit regionChanged(mRegion.min,mRegion.max,false);
				_container->setHZoomPivot(mLocatorPos);
				_container->needUpdate();
				return;
			}
			double tolerance = double(TOLERANCE)*(mView.right-mView.left)/double(mViewport.w);
			if(mRegion.min == mRegion.max) 
			{
				if(PickBeginRegion(x,tolerance))
				{
					mEditionMode = DraggingLocator;
				}
				return;
			}
			if(PickBeginRegion(x,tolerance)) 
			{
				mEditionMode = DraggingBeginRegion;
				return;
			}
			if(PickEndRegion(x,tolerance)) 
			{
				mEditionMode = DraggingEndRegion;
				return;
			}
		}

		void Locator::MouseReleaseEvent(double x, double y)
		{
			if(!mCatchEvents) return;
			if(!mEnabled) return;
			if(mUpdatingLocator) return;
			mEditionMode = Idle;
			_container->rendererWorking(mKey,false);
			_container->setCursor(Qt::ArrowCursor);
			double zoom_ref = (mRegion.min == mRegion.max) ? mLocatorPos : mRegion.min+mRegion.Span()/2.0;
			_container->setHZoomPivot(zoom_ref);
			(zoom_ref == mLocatorPos) 
				? emit selectedRegion(mLocatorPos,mXRange.max) 
				: emit selectedRegion(mRegion.min,mRegion.max);
		}

		void Locator::MouseMoveEvent(double x, double y)
		{
			if(!mCatchEvents) return;
			if(!mEnabled) return;
			if(mUpdatingLocator) return;
			QString ttip("");
			if(mKeyboard.key_r) 
			{
				mEditionMode = Idle;
				_container->rendererWorking(mKey,true);
				_container->setToolTip(ttip);
				_container->setCursor(Qt::ArrowCursor);
				return;
			}
			switch (mEditionMode)
			{
				case DraggingBeginRegion:
				{
					if(x < mXRange.min) x = mXRange.min;
					if(x > mXRange.max) x = mXRange.max;
					bool flag = mRegion.min == mLocatorPos;
					mRegion.min = x;
					if(mRegion.min >= mRegion.max) mRegion.min = mRegion.max;
					if(mRegion.min < mXRange.min) mRegion.min = mXRange.min;
					if(flag)
						mLocatorPos = mRegion.min;
					else
						if(mLocatorPos <= mRegion.min) mLocatorPos = mRegion.min;
					ttip = "Cue:"+QString::number(mRegion.min,'f',2);
					_container->setToolTip(ttip);
					emit regionChanged(mRegion.min,mRegion.max,mLocatorPos > mRegion.min);
					_container->needUpdate();	
					return;
				}
				case DraggingEndRegion:
				{
					if(x < mXRange.min) x = mXRange.min;
					if(x > mXRange.max) x = mXRange.max;
					bool flag = mRegion.max == mLocatorPos;
					mRegion.max = x;
					if(mRegion.max <= mRegion.min) mRegion.max = mRegion.min;
					if(mRegion.max > mXRange.max) mRegion.max = mXRange.max;
					if(flag)
						mLocatorPos = mRegion.max;
					else
						if(mLocatorPos >= mRegion.max) mLocatorPos = mRegion.max;
					ttip = "Cue:"+QString::number(mRegion.max,'f',2);
					_container->setToolTip(ttip);
					emit regionChanged(mRegion.min,mRegion.max,mLocatorPos > mRegion.min);
					_container->needUpdate();	
					return;
				}
				case DraggingLocator:
				{
					if(x < mXRange.min) x = mXRange.min;
					if(x > mXRange.max) x = mXRange.max;
					mRegion.min = mRegion.max = mLocatorPos = x;
					ttip = "Cue:"+QString::number(mLocatorPos,'f',2);
					_container->setToolTip(ttip);
					emit regionChanged(mLocatorPos,mLocatorPos,false);
					return;
				}
				default:
					_container->rendererWorking(mKey,false);
					_container->setToolTip(ttip);
					_container->setCursor(Qt::ArrowCursor);
					break;
			}
			double tolerance = double(TOLERANCE)*(mView.right-mView.left)/double(mViewport.w);
			if(mRegion.min == mRegion.max) 
			{
				if(PickBeginRegion(x,tolerance))
				{
					_container->rendererWorking(mKey,true);
					ttip = "Cue:"+QString::number(mLocatorPos,'f',2);
					_container->setToolTip(ttip);
					_container->setCursor(Qt::SizeHorCursor);
				}
				else
				{
					mEditionMode = Idle;
					_container->rendererWorking(mKey,false);
					_container->setToolTip(ttip);
					_container->setCursor(Qt::ArrowCursor);
				}
				return;
			}
			if(PickBeginRegion(x,tolerance)) 
			{
				_container->rendererWorking(mKey,true);
				ttip = "Cue:"+QString::number(mRegion.min,'f',2);
				_container->setToolTip(ttip);
				_container->setCursor(Qt::SizeHorCursor);
				return;
			}
			if(PickEndRegion(x,tolerance)) 
			{
				_container->rendererWorking(mKey,true);
				ttip = "Cue:"+QString::number(mRegion.max,'f',2);
				_container->setToolTip(ttip);
				_container->setCursor(Qt::SizeHorCursor);
				return;
			}
		}

		void Locator::KeyPressEvent(int key)
		{
			if(!mCatchEvents) return;
			if(!mEnabled) return;
			if(mUpdatingLocator) return;
			switch(key)
			{
				case Qt::Key_Shift:
					mKeyboard.key_shift = true;
					break;
				case Qt::Key_R:
					mKeyboard.key_r = true;
					_container->rendererWorking(mKey,true);
					break;
				default:
					break;
			}
		}

		void Locator::KeyReleaseEvent(int key)
		{
			if(!mCatchEvents) return;
			if(!mEnabled) return;
			if(mUpdatingLocator) return;
			switch(key)
			{
				case Qt::Key_Shift:
					mKeyboard.key_shift = false;
					break;
				case Qt::Key_R:
					mKeyboard.key_r = false;
					break;
				default:
					break;
			}
		}

		void Locator::LeaveEvent()
		{
			_container->setToolTip("");
		}

		int Locator::DrawRegionMode()
		{
			// 0 -> neither the points nor the region are visible
			// 1 -> the region is visible but the points are not  
			// 2 -> the first point is visible but the second is not
			// 3 -> the second point is visible but the first is not
			// 4 -> both points are visible
			double p1 = mRegion.min;
			double p2 = mRegion.max;
			if(p1 < mView.left && p2 > mView.right) return 1;
			if(p1 >= mView.left && p2 > mView.right) return 2;
			if(p1 < mView.left && p2 <= mView.right) return 3;
			if(p1 >= mView.left && p2 <= mView.right) return 4;
			return 0;
		}

		void Locator::DrawRegion(int mode)
		{
			if(mRegion.min == mRegion.max) return;
			glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
			glEnable(GL_BLEND);
			glColor4ub(mRegionColor.red(),mRegionColor.green(),mRegionColor.blue(),175);
			glBegin(GL_QUADS);
			switch(mode)
			{
				case 1:
					// the region is visible but the points are not
					glVertex2d(mView.left,mView.top);
					glVertex2d(mView.right,mView.top);
					glVertex2d(mView.right,mView.bottom);
					glVertex2d(mView.left,mView.bottom);
					break;
				case 2:
					// the first point is visible but the second is not
					glVertex2d(mRegion.min,mView.top);
					glVertex2d(mView.right,mView.top);
					glVertex2d(mView.right,mView.bottom);
					glVertex2d(mRegion.min,mView.bottom);
					break;
				case 3:
					// the second point is visible but the first is not
					glVertex2d(mView.left,mView.top);
					glVertex2d(mRegion.max,mView.top);
					glVertex2d(mRegion.max,mView.bottom);
					glVertex2d(mView.left,mView.bottom);
					break;
				case 4:
					// both points are visible
					glVertex2d(mRegion.min,mView.top);
					glVertex2d(mRegion.max,mView.top);
					glVertex2d(mRegion.max,mView.bottom);
					glVertex2d(mRegion.min,mView.bottom);
					break;
				default:
					// neither the points nor the region are visible
					break;
			}
			glEnd();
			glDisable(GL_BLEND);
		}

		void Locator::DrawLocator()
		{
			if(mLocatorPos < mView.left || mLocatorPos > mView.right) return;
			glLineWidth(2);
			glColor3ub(mLocatorColor.red(),mLocatorColor.green(),mLocatorColor.blue());
			glBegin(GL_LINES);
			glVertex2d(mLocatorPos,mView.top);
			glVertex2d(mLocatorPos,mView.bottom);
			glEnd();
		}
		
		bool Locator::PickBeginRegion(double x, double tolerance)
		{
			return (mRegion.min >= (x-tolerance) && mRegion.min <= (x+tolerance));
		}

		bool Locator::PickEndRegion(double x, double tolerance)
		{
			return (mRegion.max >= (x-tolerance) && mRegion.max <= (x+tolerance));
		}
	}
}

// END

