// XTPCalendarViewEvent.cpp: implementation of the CXTPCalendarDayViewEvent class.
//
// This file is a part of the XTREME CALENDAR MFC class library.
// (c)1998-2012 Codejock Software, All Rights Reserved.
//
// THIS SOURCE FILE IS THE PROPERTY OF CODEJOCK SOFTWARE AND IS NOT TO BE
// RE-DISTRIBUTED BY ANY MEANS WHATSOEVER WITHOUT THE EXPRESSED WRITTEN
// CONSENT OF CODEJOCK SOFTWARE.
//
// THIS SOURCE CODE CAN ONLY BE USED UNDER THE TERMS AND CONDITIONS OUTLINED
// IN THE XTREME TOOLKIT PRO LICENSE AGREEMENT. CODEJOCK SOFTWARE GRANTS TO
// YOU (ONE SOFTWARE DEVELOPER) THE LIMITED RIGHT TO USE THIS SOFTWARE ON A
// SINGLE COMPUTER.
//
// CONTACT INFORMATION:
// support@codejock.com
// http://www.codejock.com
//
/////////////////////////////////////////////////////////////////////////////

#include "stdafx.h"

#include "Common/XTPColorManager.h"
#include "Common/XTPSmartPtrInternalT.h"

#include "XTPCalendarUtils.h"
#include "XTPCalendarDefines.h"
#include "XTPCalendarPtrCollectionT.h"

#include "XTPCalendarView.h"
#include "XTPCalendarViewDay.h"
#include "XTPCalendarViewEvent.h"
#include "XTPCalendarViewPart.h"

#include "XTPCalendarDayViewDay.h"
#include "XTPCalendarDayViewEvent.h"
#include "XTPCalendarDayView.h"

#include "XTPCalendarControl.h"
#include "XTPCalendarEvent.h"

#include "XTPCalendarPaintManager.h"
#include "XTPCalendarTheme.h"


#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#define new DEBUG_NEW
#endif

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
IMPLEMENT_DYNAMIC(CXTPCalendarDayViewEvent, CXTPCalendarViewEvent)

CXTPCalendarDayViewEvent::CXTPCalendarDayViewEvent(CXTPCalendarEvent* pEvent, CXTPCalendarDayViewGroup* pViewGroup)
	: TBase(pEvent, pViewGroup)
{
	ASSERT(pViewGroup);

	m_bHideEventTime = TRUE;
	m_bShowArrow = FALSE;
	m_rcTimeframe.SetRectEmpty();
}

CXTPCalendarDayViewEvent::~CXTPCalendarDayViewEvent()
{
}

BOOL CXTPCalendarDayViewEvent::IsMultidayEvent() const
{
	if (!m_ptrEvent)
	{
		ASSERT(FALSE);
		return FALSE;
	}

	if (m_ptrEvent->IsAllDayEvent())
		return TRUE;

	int nHours = m_ptrEvent->GetDurationMinutes()/60;
	return nHours >= 24;
}

void CXTPCalendarDayViewEvent::AdjustSubjectEditor()
{
	AdjustSubjectEditorEx(TRUE);
}

void CXTPCalendarDayViewEvent::AdjustSubjectEditorEx(BOOL bUpdateView)
{
	if (IsEditingSubject() && m_pSubjectEditor)
	{
		if (!GetView())
		{
			ASSERT(FALSE);
			return;
		}

		CRect rcEvents = GetViewGroup()->m_LayoutX.m_rcDayDetails;

		m_pSubjectEditor->MoveWindow(&m_rcSubjectEditor, FALSE);

		if (!IsMultidayEvent())
		{
			if (m_rcSubjectEditor.top >= rcEvents.top && m_rcSubjectEditor.top < rcEvents.bottom)
			{
				m_pSubjectEditor->ShowWindow(SW_SHOW);
				m_pSubjectEditor->SetFocus();
			}
			else
			{
				if (bUpdateView && GetViewGroup()->GetViewDay()->GetView()->IsEditingSubject())
				{
					GetViewGroup()->GetViewDay()->GetView()->OnUpdateEditingSubject(GetEditingSubjectFromEditor());
				}

				XTP_SAFE_CALL1(GetCalendarControl(), SetFocus());
				XTP_SAFE_CALL1(m_pSubjectEditor, ShowWindow(SW_HIDE));
			}
		}
	}
}

void CXTPCalendarDayViewEvent::AdjustLayout(CDC* pDC, const CRect& rcEventMax, int nEventPlaceNumber)
{
	TBase::AdjustLayout(pDC, rcEventMax, nEventPlaceNumber);

	m_nMultiDayEventFlags = 0;
	m_bHideEventTime = TRUE;

	CXTPCalendarEvent* pEvent = GetEvent();
	if (!pEvent  || !pDC || !GetView())
	{
		ASSERT(FALSE);
		return;
	}

	// all-day event
	if (IsMultidayEvent())
	{
		//calculate icons rect

		CString strLoc = GetItemTextLocation();
		CString strEventText = GetItemTextSubject();
		if (strLoc.GetLength() > 0)
		{
			strEventText += _T(" (") + strLoc + _T(")");
		}

		CSize szText = XTP_SAFE_GET3(GetView(), GetPaintManager(), GetDayViewEventPart(), GetTextExtent(pDC, strEventText), CSize(0));
		m_szText.cx = szText.cx;
		m_szText.cy = szText.cy;

		AdjustMultidayEvent(rcEventMax, nEventPlaceNumber);

		if (!pEvent->IsAllDayEvent() &&
				m_nMultiDayEventFlags & xtpCalendarMultiDayLast)
		{
			COleDateTimeSpan spDay(1, 0, 0, 0);
			COleDateTime dtDay = XTP_SAFE_GET2(GetViewGroup_(), GetViewDay_(), GetDayDate(), (DATE)0);
			if (dtDay != pEvent->GetEndTime() - spDay)
			{
				CSize szClock = XTP_SAFE_GET2(GetView(), GetPaintManager(), GetClockSize(), CSize(0));
				m_rcLastClock.CopyRect(m_rcEvent);
				m_rcLastClock.top -= 1;
				m_rcLastClock.bottom -= 1;
				m_rcLastClock.left = m_rcLastClock.right - szClock.cx;
				m_rcText.right = m_rcLastClock.left;
			}
		}

		return;
	}

	m_nMultiDayEventFlags |= xtpCalendarMultiDayNoMultiDay;

	// non all-day event
	int nPace = GetEventPlacePos();
	int nPaceCount = max(1, GetEventPlaceCount());

	ASSERT(GetEventPlaceCount() >= 1);
	ASSERT(nPace >= 0 && nPace < nPaceCount);

	m_rcEvent.CopyRect(GetViewGroup()->m_LayoutX.m_rcDayDetails);
	int nEventWidth = m_rcEvent.Width() / nPaceCount;

	// skip event views if they are not fit even by 5 pixel bar width
	// and correct some first event views heights
	if (nEventWidth <= 5)
	{
		while (nPaceCount > 1 && nEventWidth <= 5)
		{
			nPaceCount--;
			nEventWidth = m_rcEvent.Width() / nPaceCount;
		}
		if (nPace > nPaceCount)
		{
			m_rcEvent.SetRectEmpty();
			return;
		}
	}

	// calc other rect coordinates
	m_rcEvent.left += nEventWidth * nPace;
	m_rcEvent.right = m_rcEvent.left + nEventWidth;

	COleDateTime dtStartTime(pEvent->GetStartTime());
	COleDateTime dtEndTime(pEvent->GetEndTime());
	COleDateTimeSpan spEnd(0.0);
	if (CXTPCalendarUtils::IsZeroTime(dtEndTime) &&
		!CXTPCalendarUtils::IsEqual(dtStartTime, dtEndTime))
	{
		spEnd.SetDateTimeSpan(0, 0, 0, 1);
	}
	int nTopCell = XTP_SAFE_GET1(GetViewGroup(), GetDateTimeCell(dtStartTime, FALSE), 0);
	int nBottomCell = XTP_SAFE_GET1(GetViewGroup(), GetDateTimeCell(dtEndTime - spEnd, TRUE), 0);

	// is event's Begin/End on cells borders
	COleDateTime dtStartCellTime = GetViewGroup()->GetViewDay()->GetView()->GetCellTime(nTopCell);
	COleDateTime dtEndCellTime = GetViewGroup()->GetViewDay()->GetView()->GetCellTime(nBottomCell);
	dtEndCellTime += GetViewGroup()->GetViewDay()->GetView()->GetCellDuration();

	COleDateTime dtStart_TimeOnly = CXTPCalendarUtils::ResetDate(dtStartTime);
	COleDateTime dtEnd_TimeOnly = CXTPCalendarUtils::ResetDate(dtEndTime - spEnd);
	dtEnd_TimeOnly += spEnd;

	m_bHideEventTime = CXTPCalendarUtils::IsEqual(dtStartCellTime, dtStart_TimeOnly) &&
						(CXTPCalendarUtils::IsEqual(dtEndCellTime, dtEnd_TimeOnly, TRUE) ||
						CXTPCalendarUtils::IsEqual(dtStartCellTime, dtEnd_TimeOnly) );

	CXTPCalendarEventPtr ptrDragEventOrig(GetView()->GetDraggingEventOrig(), TRUE);

	if (ptrDragEventOrig && ptrDragEventOrig->IsEqualIDs(GetEvent()) &&
		!ptrDragEventOrig->IsEqualStartEnd(GetEvent()))
	{
		m_bHideEventTime = TRUE;
	}
	//---------------------------------------------------------------------------

	if (nBottomCell < nTopCell)
	{
		if (pEvent->GetEventPeriodDays() <= 1)
		{
			nBottomCell = nTopCell;
		}
		else
			if (pEvent->GetEndTime().GetDay() != GetViewGroup()->GetCellDateTime(0).GetDay())
			{
				nBottomCell = GetViewGroup()->GetRowCount() - 1;
			}
			else
			{
				nTopCell = 0;
			}
	}
	ASSERT(nBottomCell >= nTopCell);

	CRect rcTopCell = GetViewGroup()->GetCellRect(nTopCell);
	CRect rcBottomCell = GetViewGroup()->GetCellRect(nBottomCell);

	m_rcEvent.top = rcTopCell.top-1;
	m_rcEvent.bottom = rcBottomCell.bottom-2;
	m_rcEvent.right -= 5;

	m_rcEventMax.top = max(m_rcEvent.top-5, GetViewGroup()->m_LayoutX.m_rcDayDetails.top);
	m_rcEventMax.bottom = min(m_rcEvent.bottom + 5, GetViewGroup()->m_LayoutX.m_rcDayDetails.bottom);

	// adjust subject area
	m_rcText.CopyRect(m_rcEvent);
	m_rcText.left += 10; // correct to busy area
	m_rcText.left += CalcIconsRect(m_rcText);
	m_rcText.left += 4;

	m_rcSubjectEditor.CopyRect(&m_rcEvent);
	m_rcSubjectEditor.top += 1;
	m_rcSubjectEditor.left += 8;
	m_rcSubjectEditor.right -= 1;

	// Calculate Real time frame rect
	double dSecondsPerPixel = GetViewGroup()->GetViewDay()->GetView()->GetCellDuration().GetTotalSeconds()
			/ max(1, GetViewGroup()->GetRowHeight());
	COleDateTime dtTopTime = GetViewGroup()->GetCellDateTime(nTopCell);
	COleDateTime dtBottomTime = GetViewGroup()->GetCellDateTime(nBottomCell)
			+ GetViewGroup()->GetViewDay()->GetView()->GetCellDuration();

	if (CXTPCalendarUtils::IsEqual(dtStartTime, dtEndTime))
	{
		m_rcTimeframe.top = m_rcTimeframe.bottom = LONG_MAX;
	}
	else
	{
		double dShift = max(0, (dtStartTime - dtTopTime).GetTotalSeconds());
		dShift /= dSecondsPerPixel;
		m_rcTimeframe.top = m_rcEvent.top + (LONG)dShift;

		dShift = max(0, (dtBottomTime - dtEndTime).GetTotalSeconds());
		dShift = min(m_rcEvent.Height(), dShift/dSecondsPerPixel);
		m_rcTimeframe.bottom = m_rcEvent.bottom - (LONG)dShift;
	}

	m_rcTimeframe.left = m_rcEvent.left + 1;
	m_rcTimeframe.right = m_rcTimeframe.left + 4;

	// set expanded signs for day view
	CRect rcEvents = GetViewGroup()->m_LayoutX.m_rcDayDetails;

	if (m_rcEvent.top >= rcEvents.bottom-10)
		GetViewGroup()->SetExpandDown();
	if (m_rcEvent.bottom <= rcEvents.top + 10)
		GetViewGroup()->SetExpandUp();
}

void CXTPCalendarDayViewEvent::AdjustLayout2(CDC* pDC, const CRect& rcEventMax, int nEventPlaceNumber)
{
	TBase::AdjustLayout(pDC, rcEventMax, nEventPlaceNumber);

	m_nMultiDayEventFlags = 0;
	m_bHideEventTime = TRUE;

	CXTPCalendarView* pView = GetView();
	CXTPCalendarEvent* pEvent = GetEvent();

	if (!pEvent  || !pDC || !pView || !XTP_SAFE_GET1(pView, GetTheme(), NULL) )
	{
		ASSERT(FALSE);
		return;
	}

	CXTPCalendarTheme* pTheme = pView->GetTheme();

	// all-day event
	if (IsMultidayEvent())
	{
		// to calculate multiday-flags
		AdjustMultidayEvent(rcEventMax, nEventPlaceNumber);

		pTheme->GetDayViewPart()->GetDayPart()->GetGroupPart()->GetMultiDayEventPart()->AdjustLayout(this, pDC, rcEventMax, nEventPlaceNumber);
		return;

	}

	pTheme->GetDayViewPart()->GetDayPart()->GetGroupPart()->GetSingleDayEventPart()->AdjustLayout(this, pDC, rcEventMax, nEventPlaceNumber);

}

void CXTPCalendarDayViewEvent::OnPostAdjustLayout()
{
	CXTPCalendarViewEvent::OnPostAdjustLayout();

	CRect rcEvents = GetEventsRect();

	if (m_rcToolTip.top < rcEvents.top)
	{
		m_rcToolTip.top = rcEvents.top;

		if (m_rcToolTip.top >= m_rcToolTip.bottom)
		{
			m_rcToolTip.bottom = m_rcToolTip.top + 10;
		}
	}
}

CRect CXTPCalendarDayViewEvent::GetEventsRect() const
{
	if (!GetViewGroup() || !GetViewGroup()->GetViewDay())
	{
		ASSERT(FALSE);
		return CRect(0, 0, 0, 0);
	}

	if (IsMultidayEvent())
	{
		return GetViewGroup()->m_LayoutX.m_rcAllDayEvents;
	}
	return GetViewGroup()->m_LayoutX.m_rcDayDetails;
}

BOOL CXTPCalendarDayViewEvent::IsVisible() const
{
//<<EventVisible>>
//  if (GetEvent() && !GetEvent()->IsEventVisible())
//      return FALSE;
//<<EventVisible>>
	return IsVisibleEx(TRUE, TRUE, FALSE);
}

BOOL CXTPCalendarDayViewEvent::IsVisibleEx(BOOL bCheckTop, BOOL bCheckBottom, BOOL bTopAndBottom) const
{
	// at least one of bCheckTop or bCheckBottom must be TRUE (or both)
	ASSERT((!bCheckTop && !bCheckBottom) == FALSE);

	if (!GetViewGroup() || !GetViewGroup()->GetViewDay())
	{
		ASSERT(FALSE);
		return FALSE;
	}

	BOOL bVisible = FALSE;

	if (IsMultidayEvent())
	{
		CRect rcEvents = GetViewGroup()->m_LayoutX.m_rcAllDayEvents;
		bVisible = m_rcEvent.bottom <= rcEvents.bottom && m_rcEvent.top >= rcEvents.top;
	}
	else
	{
		CRect rcEvents = GetViewGroup()->m_LayoutX.m_rcDayDetails;
		BOOL bETopVisible = m_rcEvent.top <= rcEvents.bottom &&
			m_rcEvent.top >= rcEvents.top-2;

		BOOL bEBottomVisible = m_rcEvent.bottom <= rcEvents.bottom &&
			m_rcEvent.bottom >= rcEvents.top-2;

		BOOL bEBodyVisible = !(m_rcEvent.top < rcEvents.top-2 &&
			m_rcEvent.bottom < rcEvents.top-2
			||
			m_rcEvent.top > rcEvents.bottom &&
			m_rcEvent.bottom > rcEvents.bottom);

		if (bTopAndBottom)
		{
			bETopVisible |= !bCheckTop;
			bEBottomVisible |= !bCheckBottom;

			bVisible = bETopVisible && bEBottomVisible;
		}
		else
		{
			bVisible = bETopVisible || bEBottomVisible || bEBodyVisible;
		}

	}
	return bVisible;
}

void CXTPCalendarDayViewEvent::ChangeEventPlace(int nNewPlace)
{
	CXTPCalendarTheme* pTheme = XTP_SAFE_GET3(GetViewGroup_(), GetViewDay_(), GetView_(), GetTheme(), NULL);

	if (pTheme)
	{
		CRect rcEventNew = m_rcEvent;
		pTheme->GetDayViewPart()->GetDayPart()->GetGroupPart()->GetMultiDayEventPart()->CalcEventYs(rcEventNew, nNewPlace);

		int nY0 = m_rcEvent.top;
		int nY1 = rcEventNew.top;

		_ChangeEventPlace(nNewPlace, nY0, nY1);
	}
	else
	{
		TBase::ChangeEventPlace(nNewPlace);
	}
}

void CXTPCalendarDayViewEvent::Draw(CDC* pDC)
{
	BOOL bNoVisibleMDEvent = IsMultidayEvent() && !IsVisible();

	if (m_nMultiDayEventFlags & xtpCalendarMultiDaySlave || bNoVisibleMDEvent)
		return;// was drawn in the Master (or out of events rect)

	//ASSERT((m_nMultiDayEventFlags & xtpCalendarMultiDayMaster) || m_nMultiDayEventFlags == xtpCalendarMultiDayNoMultiDay);

	XTP_SAFE_CALL3(GetView(), GetPaintManager(), GetDayViewEventPart(), OnDraw(pDC, this) );
}

BOOL CXTPCalendarDayViewEvent::HitTestEx(CPoint point, XTP_CALENDAR_HITTESTINFO* pInfo) const
{
	if (!pInfo || !GetViewGroup() || !GetViewGroup()->GetViewDay())
	{
		ASSERT(FALSE);
		return FALSE;
	}

	CRect rcEvents = GetEventsRect();

	CRect rcEventMax2(m_rcEventMax);

	if (IsSelected() && !IsMultidayEvent())
	{
		rcEventMax2.top = rcEventMax2.top - 5;
		rcEventMax2.bottom = rcEventMax2.bottom + 5;
	}

	BOOL bVisible = IsVisibleEx(TRUE, TRUE, FALSE);
	if (!rcEventMax2.PtInRect(point) || !rcEvents.PtInRect(point) || !bVisible)
	{
		return FALSE;
	}
	//-----------------------------------------
	XTP_CALENDAR_HITTESTINFO tmpInfo, tmpInfo2;
	tmpInfo.pt = tmpInfo2.pt = point;

	tmpInfo.pViewDay = GetViewGroup()->GetViewDay();
	tmpInfo.pViewGroup = GetViewGroup();
	tmpInfo.pViewEvent = (CXTPCalendarViewEvent*)this;
	tmpInfo.uHitCode = xtpCalendarHitTestUnknown;

	if (GetViewGroup()->HitTestDateTime(&tmpInfo2))
	{
		tmpInfo.dt = tmpInfo2.dt;
		tmpInfo.bTimePartValid = tmpInfo2.bTimePartValid;
		tmpInfo.uHitCode |= (tmpInfo2.uHitCode & xtpCalendarHitTestDayView_Mask);
	}
	else
	{
		tmpInfo.dt = GetViewGroup()->GetViewDay()->GetDayDate();
		tmpInfo.bTimePartValid = FALSE;
	}
	//-----------------------------------------

	if (IsMultidayEvent())
	{
		BOOL bIsFirst = (m_nMultiDayEventFlags & xtpCalendarMultiDayFirst);
		BOOL bIsLast = (m_nMultiDayEventFlags & xtpCalendarMultiDayLast);
		//BOOL bIsMiddle = (m_nMultiDayEventFlags & xtpCalendarMultiDayMiddle);

		CRect rcBegin = m_rcEventMax;
		rcBegin.right = min(m_rcEvent.left + 5, m_rcEvent.right);

		CRect rcEnd = m_rcEventMax;
		rcEnd.left = max(m_rcEvent.right - 5, m_rcEvent.left);

		if (bIsFirst && rcBegin.PtInRect(point))
		{
			tmpInfo.uHitCode = xtpCalendarHitTestEventResizeHArea | xtpCalendarHitTestEventResizeBegin;
			*pInfo = tmpInfo;
			return TRUE;
		}

		if (bIsLast && rcEnd.PtInRect(point))
		{
			tmpInfo.uHitCode = xtpCalendarHitTestEventResizeHArea | xtpCalendarHitTestEventResizeEnd;
			*pInfo = tmpInfo;
			return TRUE;
		}

		if (m_rcEvent.PtInRect(point))
		{
			tmpInfo.uHitCode = xtpCalendarHitTestEventTextArea;
			*pInfo = tmpInfo;
			return TRUE;
		}
		return FALSE;
	}

	CRect rcTime = m_rcEvent;
	rcTime.right = m_rcTimeframe.right + 1;

	CRect rcText = m_rcEvent;
	rcText.left = rcTime.right;

	CRect rcBegin = m_rcEvent;
	rcBegin.bottom = min(m_rcEvent.top + 5, m_rcEvent.bottom);

	CRect rcEnd = m_rcEvent;
	rcEnd.top = max(m_rcEvent.bottom - 5, m_rcEvent.top);

	if (IsSelected())
	{
		rcBegin.top = m_rcEvent.top - 5;
		rcEnd.bottom = m_rcEvent.bottom + 5;
	}

	if (rcTime.PtInRect(point))
	{
		tmpInfo.uHitCode = xtpCalendarHitTestEventDragArea;
		*pInfo = tmpInfo;
		return TRUE;
	}

	if (rcBegin.PtInRect(point))
	{
		tmpInfo.uHitCode = xtpCalendarHitTestEventResizeVArea | xtpCalendarHitTestEventResizeBegin;
		*pInfo = tmpInfo;
		return TRUE;
	}

	if (rcEnd.PtInRect(point))
	{
		tmpInfo.uHitCode = xtpCalendarHitTestEventResizeVArea | xtpCalendarHitTestEventResizeEnd;
		*pInfo = tmpInfo;
		return TRUE;
	}

	if (rcText.PtInRect(point))
	{
		tmpInfo.uHitCode = xtpCalendarHitTestEventTextArea;
		*pInfo = tmpInfo;
		return TRUE;
	}

	//-----------------------------------------
	return FALSE;
}

CFont* CXTPCalendarDayViewEvent::GetSubjectEditorFont() const
{
	CFont* pFont = &XTP_SAFE_GET3(GetView(), GetPaintManager(), GetDayViewEventPart(), GetTextFont(), *((CFont*)(NULL)));
	ASSERT(pFont);
	return pFont;
}

CXTPCalendarViewEvent* CXTPCalendarDayViewEvent::StartEditSubject()
{
	XTP_SAFE_CALL3(GetViewGroup(), GetViewDay(), GetView(), EnsureVisibleH(this));

	CXTPCalendarViewEvent* pEV = TBase::StartEditSubject();

	AdjustSubjectEditorEx(FALSE);

	return pEV;
}