/*
 * 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 <CLAM/Segmentation.hxx>
#include "vmSegmentEditor.hxx"
#include "vmPlotCanvas.hxx"

//#define __SEGMENT_EDITOR__DEBUG__

#ifdef __SEGMENT_EDITOR__DEBUG__
#include <iostream>
#endif
#include <QtGui/QColor>

namespace CLAM
{
	namespace VM
	{
		SegmentEditor::SegmentEditor()
			: mSegmentation(0)
			, mAllowChangeCurrent(false)
			, mCurrentSegment(0)
			, mEditionMode(Idle)
			, mDraggedSegment(0)
			, mColors(5)
			, mHighlighted(-1)
			, mVMargin(0.0)
			, mBoundType(LEFT_BOUND)
		{
			Colorize();
		}

		SegmentEditor::~SegmentEditor()
		{
		}

		void SegmentEditor::SetSegmentation(Segmentation* s)
		{
			mSegmentation = s;
			if(!mSegmentation) return;
			mCurrentSegment = mSegmentation->current();
			_container->updateRenderers();
		}

		void SegmentEditor::checkCurrent(double xpos)
		{
			if(!mSegmentation) return;
			if(!mEnabled || !mAllowChangeCurrent) return;
			unsigned index = mSegmentation->pickSegmentBody(xpos);
			if(index == mCurrentSegment || index == mSegmentation->offsets().size()) return;
			mSegmentation->current(index);
			mCurrentSegment = mSegmentation->current();
			emit currentSegmentChanged();
			_container->needUpdate();	
		}

		void SegmentEditor::allowChangeCurrent(bool allow)
		{
			mAllowChangeCurrent = allow;
		}

		void SegmentEditor::SetHBounds(double left, double right)
		{
			Renderer2D::SetHBounds(left,right);
		}

		void SegmentEditor::SetVBounds(double bottom, double top)
		{
			Renderer2D::SetVBounds(bottom,top);
		}

		void SegmentEditor::Render()
		{
			if(!mSegmentation) return;
			if(!mEnabled) return;
			unsigned nElems = mSegmentation->onsets().size();
			if(!nElems) return;
			mVMargin = double(PIXMARGIN)*(mView.top-mView.bottom)/double(mViewport.h);
			unsigned current = mSegmentation->current();
			const Segmentation::TimePositions & beginnings = mSegmentation->onsets();
			const Segmentation::TimePositions & endings = mSegmentation->offsets();
			glLineWidth(2);
			for(unsigned i=0;  i < nElems; i++)
			{
				if(beginnings[i] > mView.right) break;
				if(IsVisible(beginnings[i],endings[i],mView.left,mView.right)) 
				{
					DrawSegment(beginnings[i],
								endings[i],
								mYRange.min,
								mYRange.max,
								i==current,
								mSegmentation->selections()[i]);
				}				
			}
			DrawVHighlighted(mYRange.min,mYRange.max);
			if (current>=nElems) return; // no current
			if (IsVisible(beginnings[current],endings[current],mView.left,mView.right)) 
				StippledRect(beginnings[current],endings[current],mYRange.min,mYRange.max);
			glLineWidth(1);
		}

		void SegmentEditor::MouseMoveEvent(double x, double y)
		{
			if(!mEnabled) return;
			if(!mCatchEvents) return;
			if(!mSegmentation) return;
			QString ttip("");
			QString units = mSegmentation->xUnits().c_str();
			switch (mEditionMode)
			{
				case DraggingOnset:
					mSegmentation->dragOnset(mDraggedSegment,x);
					ttip = QString("Begin (%1): %2%3")
						.arg(mDraggedSegment)
						.arg(mSegmentation->onsets()[mDraggedSegment],0,'g')
						.arg(units)
						;
					_container->setToolTip(ttip);
					_container->needUpdate();	
					return;
				case DraggingOffset:
					mSegmentation->dragOffset(mDraggedSegment,x);
					ttip = QString("End (%1): %2%3")
						.arg(mDraggedSegment)
						.arg(mSegmentation->offsets()[mDraggedSegment],0,'g')
						.arg(units)
						;
					_container->setToolTip(ttip);
					_container->needUpdate();	
					return;
				case DraggingBody:
					_container->rendererWorking(mKey,true);
					mHighlighted = -1;
					_container->setToolTip(ttip);
					return;
				default:
					// Just continue below
					mHighlighted = -1;
					_container->rendererWorking(mKey,false);
					_container->setToolTip(ttip);
					_container->setCursor(Qt::ArrowCursor);
					break;
			}

			unsigned size = mSegmentation->onsets().size();
			double tolerance = double(TOLERANCE)*(mView.right-mView.left)/double(mViewport.w);

			unsigned index;
			index = mSegmentation->pickOnset(x,tolerance);
			if(index != size) 
			{
				_container->rendererWorking(mKey,true);
				mHighlighted = index;
				mBoundType = LEFT_BOUND;
				ttip = QString("Begin (%1): %2%3")
						.arg(index)
						.arg(mSegmentation->onsets()[index],0,'g')
						.arg(units)
						;
				_container->setToolTip(ttip);
				_container->setCursor(Qt::SizeHorCursor);
				return;
			}
			index = mSegmentation->pickOffset(x,tolerance);
			if(index != size) 
			{
				_container->rendererWorking(mKey,true);
				mHighlighted = index;
				mBoundType = RIGHT_BOUND;
				ttip = QString("End (%1): %2%3")
						.arg(index)
						.arg(mSegmentation->offsets()[index],0,'g')
						.arg(units)
						;
				_container->setToolTip(ttip);
				_container->setCursor(Qt::SizeHorCursor);
				return;
			}
			if(mKeyboard.key_insert || mKeyboard.key_ctrl)
			{
				_container->rendererWorking(mKey,true);	
			}
		}

		void SegmentEditor::MousePressEvent(double x, double y)
		{
			if(!mEnabled) return;
			if(!mCatchEvents) return;
			if(!mSegmentation) return;
			if(mKeyboard.key_insert)
			{
				unsigned index = mSegmentation->insert(x);
				_container->needUpdate();
				emit segmentInserted(index);
#ifdef __SEGMENT_EDITOR__DEBUG__
				std::cout << "Inserted segment at index  " << index << std::endl;
#endif
				return;
			}
			unsigned index;
			unsigned nSegments = mSegmentation->onsets().size();
			double tolerance = double(TOLERANCE)*(mView.right-mView.left)/double(mViewport.w);
			index = mSegmentation->pickOnset(x,tolerance);
			if(index!=nSegments)
			{
				mEditionMode=DraggingOnset;
				mDraggedSegment=index;
#ifdef __SEGMENT_EDITOR__DEBUG__
				std::cout << "Dragging onset " << index << std::endl;
#endif
				return;
			}
			index = mSegmentation->pickOffset(x,tolerance);
			if(index!=nSegments)
			{
				mEditionMode=DraggingOffset;
				mDraggedSegment=index;
#ifdef __SEGMENT_EDITOR__DEBUG__
				std::cout << "Dragging offset " << index << std::endl;
#endif
				return;
			}
			index = mSegmentation->pickSegmentBody(x);
			if(index!=nSegments)
			{
				mEditionMode=DraggingBody;
				mDraggedSegment=index;
				if(mKeyboard.key_ctrl)
				{
					(mSegmentation->selections()[index]) 
						? mSegmentation->deselect(index) 
						: mSegmentation->select(index);
				}
				else
				{
					mSegmentation->current(index);
					mSegmentation->clearSelection();
					mCurrentSegment = mSegmentation->current();
					emit currentSegmentChanged();
#ifdef __SEGMENT_EDITOR__DEBUG__
					std::cout << "Current segment is " << index << std::endl;
#endif
				}
				_container->needUpdate();	
				return;
			}
		}

		void SegmentEditor::MouseReleaseEvent(double x, double y)
		{
			if(!mEnabled) return;
			if(!mCatchEvents) return;
			if(!mSegmentation) return;
			int mode = mEditionMode;
			mEditionMode=Idle;
			if(!mKeyboard.key_ctrl) _container->rendererWorking(mKey,false);
			mHighlighted = -1;
			_container->setToolTip("");
			_container->setCursor(Qt::ArrowCursor);
			if(mode == DraggingOnset) 
			{
				emit segmentOnsetChanged(mDraggedSegment,mSegmentation->onsets()[mDraggedSegment]);
#ifdef __SEGMENT_EDITOR__DEBUG__
				std::cout << "Onset changed at index " << mDraggedSegment << ", new value " 
						  << mSegmentation->onsets()[mDraggedSegment] << std::endl;
#endif
				return;
			}
			if(mode == DraggingOffset) 
			{
				emit segmentOffsetChanged(mDraggedSegment,mSegmentation->offsets()[mDraggedSegment]);
#ifdef __SEGMENT_EDITOR__DEBUG__
				std::cout << "Offset changed at index " << mDraggedSegment << ", new value " 
						  << mSegmentation->offsets()[mDraggedSegment] << std::endl;
#endif
				return;
			}
		}

		void SegmentEditor::KeyPressEvent(int key)
		{
			if(!mEnabled) return;
			if(!mCatchEvents) return;
			if(!mSegmentation) return;
			switch(key)
			{
				case Qt::Key_Insert:
				{
					_container->rendererWorking(mKey,true);
					mKeyboard.key_insert = true; 
				}
				break;
						
				case Qt::Key_Delete:
				{
					mKeyboard.key_delete = true; 
					unsigned nSegments = mSegmentation->onsets().size();
					unsigned index = mSegmentation->current();
					mSegmentation->remove(index);
					if(mSegmentation->onsets().size() == nSegments) return; // Remove was ignored
					_container->needUpdate();
					emit segmentDeleted(index);
					emit currentSegmentChanged();
					mCurrentSegment = mSegmentation->current();
#ifdef __SEGMENT_EDITOR__DEBUG__
					std::cout << "Segment deleted  " << index << std::endl;
					std::cout << "Current segment is " << mSegmentation->current() << std::endl;
#endif
				}
				break;

				case Qt::Key_Control:
				{
					_container->rendererWorking(mKey,true);
					mKeyboard.key_ctrl = true;
				}
				break;
			}
		}

		void SegmentEditor::KeyReleaseEvent(int key)
		{
			if(!mEnabled) return;
			if(!mCatchEvents) return;
			if(!mSegmentation) return;
			switch(key)
			{
				case Qt::Key_Insert:
					_container->rendererWorking(mKey,false);
					mKeyboard.key_insert = false; 
					break;
						
				case Qt::Key_Delete:
					mKeyboard.key_delete = false; 
					break;

				case Qt::Key_Control:
					_container->rendererWorking(mKey,false);
					mKeyboard.key_ctrl = false;
					break;

				default:
					break;
			}
		}

		void SegmentEditor::LeaveEvent()
		{
			mHighlighted = -1;
			_container->setToolTip("");
			_container->rendererWorking(mKey,false);
		}

		void SegmentEditor::Colorize()
		{
			mColors[NORMAL] = QColor(100,200,20);
			mColors[CURRENT] = QColor(100,200,20);
			mColors[SELECTED] = QColor(140,240,60); 
			mColors[HIGHLIGHTED] = QColor(255,255,0);
			mColors[STIPPLED] = QColor(255,90,60);
		}

		void SegmentEditor::PlainRect(double left, double right, double bottom, double top)
		{
			glVertex2d(left,bottom+mVMargin);
			glVertex2d(left,top-mVMargin);
			glVertex2d(right,top-mVMargin);
			glVertex2d(right,bottom+mVMargin);
			glVertex2d(left,bottom+mVMargin);
		}

		void SegmentEditor::StippledRect(double left, double right, double bottom, double top)
		{
			float vPadding = mVMargin-(2.0*(mView.top-mView.bottom)/double(mViewport.h));
			float hPadding = 3.0*(mView.right-mView.left)/double(mViewport.w);
			// draw stippled rect
			glColor3ub(mColors[STIPPLED].red(),mColors[STIPPLED].green(),mColors[STIPPLED].blue());
			glEnable(GL_LINE_STIPPLE);
			glLineStipple(FACTOR,PATTERN);
			glBegin(GL_LINE_STRIP);
			PlainRect(left-hPadding,right+hPadding,bottom-vPadding,top+vPadding);
			glEnd();
			glDisable(GL_LINE_STIPPLE);
		}

		bool SegmentEditor::IsVisible(double left, double right, double lBound, double rBound)
		{
			if(left >= lBound  && left <= rBound) return true;
			if(right >= lBound && right <= rBound) return true;
			if(left <= lBound && right >= rBound) return true;
			return false;
		}

		void SegmentEditor::DrawSegment(double left, double right, 
										double bottom, double top, 
										bool isCurrent, 
										bool isSelected)
		{
			int cindex = isSelected ? SELECTED : NORMAL;
			// fill rec
			glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
			glColor4ub(mColors[cindex].red(),mColors[cindex].green(),mColors[cindex].blue(),125);
			glBegin(GL_QUADS);
			PlainRect(left,right,bottom,top);
			glEnd();
			// draw plain
			glColor4ub(mColors[cindex].red(),mColors[cindex].green(),mColors[cindex].blue(),200);
			glBegin(GL_LINE_STRIP);
			PlainRect(left,right,bottom,top);
			glEnd();
		}

		void SegmentEditor::DrawVHighlighted(double bottom,  double top)
		{
			if(mHighlighted < 0 || mHighlighted >= (int)mSegmentation->onsets().size()) return;
			float x = 
				(mBoundType==LEFT_BOUND) 
				? mSegmentation->onsets()[mHighlighted] 
				: mSegmentation->offsets()[mHighlighted];
			bool isCurrent = (mHighlighted == (int)mSegmentation->current());
		   
			// draw vertical highlighted 
			glColor3ub(mColors[HIGHLIGHTED].red(),mColors[HIGHLIGHTED].green(),mColors[HIGHLIGHTED].blue());
			glBegin(GL_LINES);
			glVertex2d(x,top-mVMargin); 
			glVertex2d(x,bottom+mVMargin);
			glEnd();
		}
	}
}

// END

