// NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
// Copyright (C) 2010  Winch Gate Property Limited
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero 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 Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

#include "std_afx.h"
#include "sound_anim_view.h"
#include "sound_anim_dlg.h"
#include "object_viewer.h"

#include "nel/sound/sound_animation.h"
#include "nel/sound/sound_anim_manager.h"
#include "nel/sound/sound_anim_marker.h"

using namespace std;
using namespace NLMISC;
using namespace NL3D;
using namespace NLSOUND;


IMPLEMENT_DYNCREATE(CSoundAnimView, CWnd)



BEGIN_MESSAGE_MAP(CSoundAnimView, CWnd)
	//{{AFX_MSG_MAP(CSoundAnimView)
	ON_WM_LBUTTONDOWN()
	ON_WM_LBUTTONUP()
	ON_WM_MOUSEMOVE()
	ON_WM_PAINT()
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

bool			CSoundAnimView::_Registered = false;
CString			CSoundAnimView::_WndClass;
uint			CSoundAnimView::_WndId = 0;
const uint		CSoundAnimView::_ZoomCount = 7;
float			CSoundAnimView::_ZoomValue[] = { 0.1f, 0.25f, 0.5f, 1.0f, 2.0f, 4.0f, 10.0f };
const float		CSoundAnimView::_Scale = 200.0f;    // 1 second equals 200 pixels
CFont			CSoundAnimView::_Font;
CBrush			CSoundAnimView::_FillBrush;
CBrush			CSoundAnimView::_MarkerBrush;
CBrush			CSoundAnimView::_SelectBrush;
CPen			CSoundAnimView::_RedPen;


// ***************************************************************************

bool CSoundAnimView::registerClass()
{
	if (_Registered)
	{
		return true;
	}

	_WndClass = AfxRegisterWndClass(CS_VREDRAW | CS_HREDRAW, ::LoadCursor(NULL, IDC_ARROW), (HBRUSH) ::GetStockObject(WHITE_BRUSH));

	// Do some additional initialization of static veriables
	_Font.CreateFont(14, 0, 0, 0, FW_NORMAL, FALSE, FALSE, 0, ANSI_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_SWISS, "Arial");
	_FillBrush.CreateSolidBrush(RGB(230, 245, 245));
	_MarkerBrush.CreateSolidBrush(RGB(0, 0, 0));
	_SelectBrush.CreateSolidBrush(RGB(0, 210, 210));
	_RedPen.CreatePen(PS_SOLID, 1, RGB(255, 0, 0));

	_Registered = true;

	return true;
}


// ***************************************************************************

void CSoundAnimView::Create(CObjectViewer* objView, CAnimationDlg* animDlg, CSoundAnimDlg* sndDlg, const RECT& rect)
{
	registerClass();

	_ObjView = objView;
	_AnimationDlg = animDlg;
	_SoundAnimDlg = sndDlg;
	_Zoom = 1.0f;
	_Cursor = 0;
	_TimeStart = 0.0f;
	_TimeEnd = 0.0f;
	_TimeOffset = 0.0f;
	_PixelsTotal = 0;
	_PixelsOffset = 0;
	_PixelsViewH = rect.right - rect.left;
	_PixelsViewV = rect.bottom - rect.top;
	_Dragging = false;
	_SelectedAnim = 0;
	_SelectedMarker = 0;

	// Find the zoom index dynamically so we can change the _ZoomValue array
	// withou risk of using a bad _ZoomIndex
	for (uint i = 0; i < _ZoomCount; i++)
	{
		if (_ZoomValue[i] == 1.0f)
		{
			_ZoomIndex = i;
			break;
		}
	}


	CWnd::Create((LPCTSTR) _WndClass, "Sound Animation", WS_CHILD | WS_VISIBLE, rect, (CWnd*) sndDlg, ++_WndId);
}

// ********************************************************

void CSoundAnimView::changeTimeScale()
{
	_PixelsTotal = timeToPixel(_TimeEnd - _TimeStart);
	_PixelsOffset = timeToPixel(_TimeOffset - _TimeStart);


	if (_PixelsTotal < _PixelsViewH)
	{
		_PixelsOffset = 0;
		_TimeOffset = _TimeStart;
		_SoundAnimDlg->updateScroll(0, 0, 0); 
	}
	else if (_PixelsOffset + _PixelsViewH > _PixelsTotal)
	{
		_PixelsOffset = _PixelsTotal - _PixelsViewH;
		_TimeOffset = _TimeStart + pixelToTime(_PixelsOffset);
		_SoundAnimDlg->updateScroll(_PixelsOffset, 0, _PixelsTotal - _PixelsViewH); 
	}
	else
	{
		_SoundAnimDlg->updateScroll(_PixelsOffset, 0, _PixelsTotal - _PixelsViewH); 		
	}

	Invalidate();
}

// ********************************************************

void CSoundAnimView::changeScroll(uint curpos)
{
	_PixelsOffset = curpos;
	_TimeOffset = _TimeStart + pixelToTime(_PixelsOffset);
	Invalidate();	
}

// ********************************************************

void CSoundAnimView::zoomIn()
{
	if (_ZoomIndex < _ZoomCount - 1)
	{
		_ZoomIndex++;
		_Zoom = _ZoomValue[_ZoomIndex];
	}

	changeTimeScale();
}

// ********************************************************

void CSoundAnimView::zoomOut()
{
	if (_ZoomIndex > 0)
	{
		_ZoomIndex--;
		_Zoom = _ZoomValue[_ZoomIndex];
	}

	changeTimeScale();
}

// ********************************************************

void CSoundAnimView::setAnimTime(float animStart, float animEnd)
{
	_TimeStart = animStart / _AnimationDlg->getSpeed();
	_TimeEnd = animEnd / _AnimationDlg->getSpeed();

	changeTimeScale();
}

// ********************************************************

void CSoundAnimView::mark()
{
	insertMarkerAt(_AnimationDlg->getTime());
}

// ********************************************************

void CSoundAnimView::save()
{
	CAnimationVector::iterator iter;
	for (iter = _Animations.begin(); iter != _Animations.end(); iter++)
	{
		CSoundAnimationHolder &h = *iter;
		CSoundAnimation* anim = h._Anim;

		if (anim->isDirty())
		{
			string filename = anim->getFilename();

			// Ask user for filename
			if (filename.empty())
			{
				filename.append(anim->getName()).append(".sound_anim");

				// Create a dialog
				char BASED_CODE szFilter[] = "NeL Sound Animations (*.sound_anim)|*.sound_anim|All Files (*.*)|*.*||";
				CFileDialog fileDlg( FALSE, ".sound_anim", filename.c_str(), OFN_HIDEREADONLY|OFN_OVERWRITEPROMPT, szFilter);

				if (fileDlg.DoModal() == IDOK)
				{
					filename = (const char*) fileDlg.GetPathName();
				}
				else
				{
					continue;
				}
			}

			// Open the file
			try
			{
				CSoundAnimManager::instance()->saveAnimation(anim, filename);
			}
			catch (Exception& e)
			{
				MessageBox (e.what(), "NeL object viewer", MB_OK|MB_ICONEXCLAMATION);
			}
		}
	}


	Invalidate();
}

// ********************************************************

void CSoundAnimView::deleteMarker()
{
	if (_SelectedMarker != 0)
	{
		_SelectedAnim._Anim->removeMarker(_SelectedMarker);
		_SelectedAnim._Anim->setDirty(true);
		Invalidate();
	}
}

// ********************************************************

bool CSoundAnimView::getAnimationAt(CSoundAnimationHolder& holder, float time)
{
	CAnimationVector::iterator iter;
	for (iter = _Animations.begin(); iter != _Animations.end(); iter++)
	{
		CSoundAnimationHolder &h = *iter;
		if ((h._AnimStart <= time) && (time < h._AnimEnd))
		{
			holder = h;
			return true;
		}
	}

	return false;
}

// ********************************************************

void CSoundAnimView::refresh(BOOL update)
{
	_Animations.clear();

	uint selected = _ObjView->getEditedObject();
	if (selected == 0xffffffff)
	{
		return;
	}

	CSoundAnimManager* animManager = CSoundAnimManager::instance();

	// Make sure the sound anim manager is already instanciated
	if (animManager == 0)
	{
		return;
	}

	CInstanceInfo *instanceInfo = _ObjView->getInstance(selected);

	// Some animation in the list ?
	if (instanceInfo->Saved.PlayList.size() > 0)
	{
		// Accumul time
		float startTime = 0;
		float endTime = 0;

		// Get start time of the animation that starts before the current time
		for (uint index = 0; index < instanceInfo->Saved.PlayList.size(); index++)
		{
			// Pointer on the animation
			string& name = instanceInfo->Saved.PlayList[index];
			CAnimation *anim = instanceInfo->AnimationSet.getAnimation (instanceInfo->AnimationSet.getAnimationIdByName(name));

			// Add start time
			startTime = endTime;
			endTime = startTime + anim->getEndTime() - anim->getBeginTime();

			CSoundAnimation* soundAnim = animManager->findAnimation(name);

			if (soundAnim == 0)
			{
				bool needCreate = false;
				try
				{
					TSoundAnimId res = animManager->loadAnimation(name);
					if(res == CSoundAnimationNoId)
						needCreate = true;
					else
						soundAnim = animManager->findAnimation(name);
				}
				catch (exception& e)
				{
					nlwarning("Couldn't find sound animation <%s>: %s", name.c_str(), e.what());
					needCreate = true;
				}
				if(needCreate)
				{
					animManager->createAnimation(name);
					soundAnim = animManager->findAnimation(name);
				}
			}

			CSoundAnimationHolder holder(soundAnim, startTime, endTime);

			_Animations.push_back(holder);
		}
	}

	Invalidate();
}

// ********************************************************

void CSoundAnimView::updateCursor()
{
	sint cursor = timeToPixel(_AnimationDlg->getTime());

	if (cursor != _Cursor)
	{
		RECT r;

		r.left = (cursor < _Cursor)? cursor : _Cursor;
		r.right = (cursor < _Cursor)? _Cursor + 1 : cursor + 1;
		r.top = 0;
		r.bottom = _PixelsViewV;

		// correct for offset
		r.left -= _PixelsOffset;
		r.right -= _PixelsOffset;

		InvalidateRect(&r);

		_Cursor = cursor;
	}
}

// ********************************************************

CSoundAnimMarker* CSoundAnimView::getMarkerAt(CPoint point)
{
	CSoundAnimationHolder holder;


	//nldebug("TIME=%f", pixelToTime(_PixelsOffset + point.x));

	if (getAnimationAt(holder, pixelToTime(_PixelsOffset + point.x)))
	{
		CSoundAnimation* anim = holder._Anim;
		float offset = holder._AnimStart;
		uint32 nmarkers = anim->countMarkers();

		for (uint32 i = 0; i < nmarkers; i++)
		{
			CSoundAnimMarker* marker = anim->getMarker(i);
			uint pixel = timeToPixel(offset + marker->getTime()) - _PixelsOffset;

			::CRect r;
			r.left = pixel - 4;
			r.right = pixel + 5;
			r.top = 0;
			r.bottom = _PixelsViewV;		

			if (r.PtInRect(point))
			{
				return marker;
			}
		}
	}
	return 0;
}

// ********************************************************

void CSoundAnimView::insertMarkerAt(float time)
{
	CSoundAnimationHolder holder;

	if (getAnimationAt(holder, time))
	{
		CSoundAnimation* anim = holder._Anim;

		CSoundAnimMarker* marker = new CSoundAnimMarker((float)(time - holder._AnimStart));
		anim->addMarker(marker);
		anim->setDirty(true);

		_SelectedMarker = marker;
		_SelectedAnim = holder;
		_SoundAnimDlg->selectMarker(marker);
	}

	Invalidate();
}

// ***************************************************************************

void CSoundAnimView::OnLButtonDown(UINT nFlags, CPoint point) 
{
	CSoundAnimMarker* marker = getMarkerAt(point);

	if (marker != 0)
	{	
		float time = pixelToTime(_PixelsOffset + point.x);
		_Dragging = true;
		_DragStartPoint = point;
		_DragStartTime = marker->getTime();
		_SelectedMarker = marker;
		getAnimationAt(_SelectedAnim, time);
	}
	else
	{
		_SelectedMarker = 0;
		_SelectedAnim = 0;
	}
}

// ***************************************************************************

void CSoundAnimView::OnLButtonUp(UINT nFlags, CPoint point) 
{
	float time = pixelToTime(point.x);

	if (nFlags == MK_CONTROL)
	{
		insertMarkerAt(time);	
	}
	else if (nFlags == 0)
	{
		_SoundAnimDlg->selectMarker(_SelectedMarker);
		if (_Dragging) 
		{
			_Dragging = false;
		}
	}

	Invalidate();
}

// ********************************************************

void CSoundAnimView::OnLButtonDblClk(UINT nFlags, CPoint point) 
{
	float time = pixelToTime(point.x);
	CSoundAnimationHolder holder;

	if (getAnimationAt(holder, time))
	{
		nlwarning("CSoundAnimView::OnLButtonDblClk: x=%d, t=%.3f", point.x, time);
	}
}

// ***************************************************************************

void CSoundAnimView::OnMouseMove(UINT nFlags, CPoint point) 
{
	if (_Dragging && (_SelectedMarker != 0) && (point.x != _DragStartPoint.x)) 
	{
		sint32 deltaPx = point.x - _DragStartPoint.x;
		float newTime = _DragStartTime + pixelToTime(point.x - _DragStartPoint.x);
		clamp(newTime, 0.0f, _SelectedAnim._AnimEnd - _SelectedAnim._AnimStart);
		_SelectedMarker->setTime(newTime);
		_SelectedAnim._Anim->setDirty(true);
		Invalidate();
	}
}

// ***************************************************************************

void CSoundAnimView::OnPaint()
{
	PAINTSTRUCT ps;
	CDC* dc = BeginPaint(&ps);

	RECT r;
	GetClientRect(&r);
	dc->Rectangle(&r);

	// Shift the origin according to the scroll offset
	CPoint p = dc->GetViewportOrg();
	p.Offset(- (sint) _PixelsOffset, 0);
	dc->SetViewportOrg(p);


	CAnimationVector::iterator iter;
	sint lastPixel = 0;

	dc->MoveTo(0, _PixelsViewV - 15);
	dc->LineTo(_PixelsTotal, _PixelsViewV - 15);

	for (iter = _Animations.begin(); iter != _Animations.end(); iter++)
	{
		CSoundAnimationHolder &holder = *iter;

		if (holder._Anim == NULL) continue;

		dc->MoveTo(timeToPixel(holder._AnimStart), 0);
		dc->LineTo(timeToPixel(holder._AnimStart), _PixelsViewV);

		r.left = timeToPixel(holder._AnimStart) + 3;
		r.right = timeToPixel(holder._AnimEnd) - 3;
		r.top = 0;
		r.bottom = 20;

		CGdiObject* oldFont = (CGdiObject*) dc->SelectObject(&_Font);
		_StringBuffer.erase();
		_StringBuffer.append(holder._Anim->getName());
		if (holder._Anim->isDirty())
		{
			_StringBuffer.append("*");
		}
		dc->DrawText(_StringBuffer.c_str(), &r, DT_VCENTER | DT_LEFT | DT_SINGLELINE);
		dc->SelectObject(oldFont);

		lastPixel = timeToPixel(holder._AnimEnd);

		// Draw the markers of the animation
		CSoundAnimation* anim = holder._Anim;
		float offset = holder._AnimStart;
		uint32 nmarkers = anim->countMarkers();
		for (uint32 i = 0; i < nmarkers; i++)
		{
			CSoundAnimMarker* marker = anim->getMarker(i);
			sint pixel = timeToPixel(offset + marker->getTime());

			r.left = pixel - 3;
			r.right = pixel + 3;
			r.top = _PixelsViewV - 20 ;
			r.bottom = _PixelsViewV - 10;		

			dc->FillRect(&r, (_SelectedMarker == marker)? &_SelectBrush : &_MarkerBrush);
		}
	}

	dc->MoveTo(lastPixel, 0);
	dc->LineTo(lastPixel, _PixelsViewV);


	if (lastPixel < (sint) _PixelsViewH)
	{
		r.left = lastPixel + 1;
		r.right = _PixelsViewH - 1;
		r.top = 1;
		r.bottom = _PixelsViewV - 1;		
		dc->FillRect(&r, &_FillBrush);
	}

	CPen* oldPen = (CPen*) dc->SelectObject(&_RedPen);
	dc->MoveTo(_Cursor, 0);
	dc->LineTo(_Cursor, _PixelsViewV);
	dc->SelectObject(oldPen);
	
	EndPaint(&ps);
}


#ifdef _DEBUG

// ***************************************************************************

void CSoundAnimView::AssertValid() const
{
	CWnd::AssertValid();
}

// ***************************************************************************

void CSoundAnimView::Dump(CDumpContext& dc) const
{
	CWnd::Dump(dc);
}

#endif //_DEBUG