// XTPCalendarController.cpp : implementation file
//
// 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/XTPNotifyConnection.h"
#include "Common/XTPColorManager.h"
#include "Common/XTPSmartPtrInternalT.h"

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

#include "XTPCalendarEvent.h"
#include "XTPCalendarEvents.h"

#include "XTPCalendarView.h"
#include "XTPCalendarWeekView.h"
#include "XTPCalendarMonthView.h"
#include "XTPCalendarDayView.h"
#include "XTPCalendarTimeLineView.h"

#include "XTPCalendarControl.h"

#include "XTPCalendarResource.h"

#include "XTPDatePickerControl.h"
#include "XTPDatePickerPaintManager.h"
#include "XTPDatePickerNotifications.h"

#include "XTPCalendarController.h"

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

// DBG
#define DBG_TRACE_DP_NF
#define DBG_TRACE_DP_TIMER

//#define DBG_TRACE_DP_NF  TRACE
//#define DBG_TRACE_DP_TIMER     TRACE
// DBG

/////////////////////////////////////////////////////////////////////////////
#define XTP_CC_DONT_SCROLL_DP 1234567890

#define XTP_CC_REFRESH_DAYS_STEP_TIMER_MS 120
#define XTP_CC_REFRESH_DAYS_STEP_DAYS     3

/////////////////////////////////////////////////////////////////////////////
// CXTPCalendarController

const int nXTPMaxDayViewDays = 6;

////////////////////////////////////////////////////////////////////////////
CXTPCalendarController::CXTPDayInfoCache::CXTPMapTimers CXTPCalendarController::CXTPDayInfoCache::m_mapTimers;

IMPLEMENT_DYNCREATE(CXTPCalendarController, CCmdTarget)
//////////////////////////////////////////////////////////////////////////
CXTPCalendarController::CXTPDayInfoCache::CXTPDayInfoCache()
{
	// 37, 53, 79 , 101, 127, 199, 503, 1021, 1511, 2003, 3001, 4001, 5003, 6007, 8009, 12007, 16001
	m_mapDaysInfo.InitHashTable(503, FALSE);
	m_mapDaysToRefresh.InitHashTable(503, FALSE);

	m_pOwner = NULL;

	m_uTimerID = 0;
	m_dwLastRedrawTime = 0;
	m_dwLastSelfClearTime = 0;
	m_dwWaitingDataTime = 0;

	m_uActivePriority = 1;
}

CXTPCalendarController::CXTPDayInfoCache::~CXTPDayInfoCache()
{
	KillTimer();
}

void CXTPCalendarController::CXTPDayInfoCache::KillTimer()
{
	if (m_uTimerID)
	{
		::KillTimer(NULL, m_uTimerID);
		m_mapTimers.RemoveKey(m_uTimerID);
		m_uTimerID = 0;
	}
}

BOOL CXTPCalendarController::CXTPDayInfoCache::HasEvents(DATE dtDay)
{
	XTPDayInfo tmpDI;
	if (m_mapDaysInfo.Lookup((long)dtDay, tmpDI))
	{
		UpdateDayInfo(dtDay, tmpDI.bHasEvents);
		return tmpDI.bHasEvents;
	}
	else
	{
		_RequestToRefreshDays(dtDay, dtDay, m_uActivePriority);
	}
	return FALSE;
}

void CXTPCalendarController::CXTPDayInfoCache::Init(CXTPCalendarController* pOwner)
{
	ASSERT(pOwner);
	m_pOwner = pOwner;
}

void CXTPCalendarController::CXTPDayInfoCache::Clear()
{
	m_mapDaysInfo.RemoveAll();
	m_mapDaysToRefresh.RemoveAll();
	m_uActivePriority = 1;

	DBG_TRACE_DP_TIMER(_T("XTPCalendarController::DayInfoCache - CLEAR \n"));
}

UINT CXTPCalendarController::CXTPDayInfoCache::UpActivePriority()
{
	return ++m_uActivePriority;
}

void CXTPCalendarController::CXTPDayInfoCache::UpdateDayInfo(DATE dtDay, BOOL bHasEvents)
{
	XTPDayInfo tmpDI = {bHasEvents, (DATE)CXTPCalendarUtils::GetCurrentTime()};
	m_mapDaysInfo[(long)dtDay] = tmpDI;
}

void CXTPCalendarController::CXTPDayInfoCache::ClearDays(COleDateTime dtDayFrom, COleDateTime dtDayTo)
{
	long nDay = min((long)dtDayFrom, (long)dtDayTo);
	long nDay2 = max((long)dtDayFrom, (long)dtDayTo);
	for (; nDay <= nDay2; nDay++)
	{
		m_mapDaysInfo.RemoveKey(nDay);
	}

	DBG_TRACE_DP_TIMER(_T("XTPCalendarController::DayInfoCache - Clear days (%s - %s) \n"), (LPCTSTR)dtDayFrom.Format(), (LPCTSTR)dtDayTo.Format());
}

void CXTPCalendarController::CXTPDayInfoCache::RequestToRefreshDays(COleDateTime dtDayFrom, COleDateTime dtDayTo)
{
	m_uActivePriority++;

	DBG_TRACE_DP_TIMER(_T("XTPCalendarController::DayInfoCache - RequestToRefreshDays (%s - %s). ActivePriority = %d \n"),
					(LPCTSTR)dtDayFrom.Format(), (LPCTSTR)dtDayTo.Format(), m_uActivePriority);

	_RequestToRefreshDays(dtDayFrom, dtDayTo, m_uActivePriority);
}

void CXTPCalendarController::CXTPDayInfoCache::_RequestToRefreshDays(COleDateTime dtDayFrom, COleDateTime dtDayTo, UINT uPriority)
{
	ASSERT(m_pOwner);
	if (!m_pOwner)
	{
		return;
	}

	long nDay = min((long)dtDayFrom, (long)dtDayTo);
	long nDay2 = max((long)dtDayFrom, (long)dtDayTo);

	// WARNING! - Ooo... The days range is too large!
	ASSERT(nDay2 - nDay < 10*1000);

	for (; nDay <= nDay2; nDay++)
	{
		m_mapDaysToRefresh[nDay] = uPriority;
	}

	if (m_mapDaysToRefresh.GetCount() && m_uTimerID == 0)
	{
		UINT uTimeOut = (UINT)m_pOwner->m_nBoldDaysIdleStepTime_ms;

		m_uTimerID = ::SetTimer(NULL, 0, uTimeOut, OnTimerCallback);
		m_mapTimers[m_uTimerID] = this;

		DBG_TRACE_DP_TIMER(_T("XTPCalendarController::DayInfoCache - _RequestToRefreshDays, SetTimer (ID = %d) \n"), m_uTimerID);
	}
}

void CXTPCalendarController::CXTPDayInfoCache::OnRefreshDays(int nDaysCountToRefresh)
{
	ASSERT(m_pOwner);

	if (!XTP_SAFE_GET1(m_pOwner, m_pResourcesNf, NULL))
	{
		if (!m_dwWaitingDataTime)
		{
			m_dwWaitingDataTime = GetTickCount();
			DBG_TRACE_DP_TIMER(_T("XTPCalendarController::DayInfoCache - OnRefreshDays, Start Wait for data\n"));
		}

		if (abs((long)(GetTickCount() - m_dwWaitingDataTime)) >= 5000)
		{
			m_dwWaitingDataTime = 0;

			if (m_uTimerID)
			{
				DBG_TRACE_DP_TIMER(_T("XTPCalendarController::DayInfoCache - OnRefreshDays, KillTimer (ID = %d) \n"), m_uTimerID);
				DBG_TRACE_DP_TIMER(_T("XTPCalendarController::DayInfoCache - OnRefreshDays, end Wait for data\n"));

				KillTimer();

				OnSelfClearOld();
			}
		}
		return;
	}
	else
	{
		if (m_dwWaitingDataTime)
		{
			DBG_TRACE_DP_TIMER(_T("XTPCalendarController::DayInfoCache - OnRefreshDays, end Wait for data\n"));
		}
		m_dwWaitingDataTime = 0;
	}

	//***************************************************
	for (int i = 0; i < nDaysCountToRefresh; i++)
	{
		long nDay_min = LONG_MAX;
		UINT uPriority_max = 0;

		POSITION pos = m_mapDaysToRefresh.GetStartPosition();
		while (pos)
		{
			long nDay = 0;
			UINT uPriority = 0;
			m_mapDaysToRefresh.GetNextAssoc(pos, nDay, uPriority);

			if (uPriority == uPriority_max && nDay < nDay_min ||
				uPriority > uPriority_max)
			{
				uPriority_max = uPriority;
				nDay_min = nDay;
			}
		}

		if (nDay_min < LONG_MAX)
		{
			m_mapDaysToRefresh.RemoveKey(nDay_min);

			COleDateTime dtDay((DATE)nDay_min);
			BOOL bHasEvents = XTP_SAFE_GET1(m_pOwner, _HasEvents(dtDay), FALSE);

			UpdateDayInfo(dtDay, bHasEvents);
		}
	}
	//***************************************************

	if (m_mapDaysToRefresh.GetCount() == 0 && m_uTimerID)
	{
		DBG_TRACE_DP_TIMER(_T("XTPCalendarController::DayInfoCache - OnRefreshDays, KillTimer (ID = %d) \n"), m_uTimerID);
		DBG_TRACE_DP_TIMER(_T("XTPCalendarController::DayInfoCache - Days In Cache %d \n"), m_mapDaysInfo.GetCount());

		KillTimer();

		OnSelfClearOld();
	}

	if (abs((long)(GetTickCount() - m_dwLastRedrawTime)) >= 500 || m_uTimerID == 0)
	{
		XTP_SAFE_CALL2(m_pOwner, m_pDatePickerCtrl, RedrawControl());
		m_dwLastRedrawTime = GetTickCount();
	}
}
void CXTPCalendarController::CXTPDayInfoCache::OnSelfClearOld()
{

#ifdef _DEBUG
	int nUpdateTime_ms = 20 * 1000;
	COleDateTimeSpan spCachePeriod(0, 0, 1, 0); // 1 min
#else
	int nUpdateTime_ms = 5 * 60 * 1000; // 5 min
	COleDateTimeSpan spCachePeriod(0, 1, 0, 0); // 1 hour
#endif

	if (abs((long)(GetTickCount() - m_dwLastSelfClearTime)) < nUpdateTime_ms)
	{
		return;
	}
	m_dwLastSelfClearTime = ::GetTickCount();

	COleDateTime dtOldTime = CXTPCalendarUtils::GetCurrentTime();
	dtOldTime -= spCachePeriod;

	DBG_TRACE_DP_TIMER(_T("XTPCalendarController::DayInfoCache - OnSelfClear, Start. Days In Cache %d \n"), m_mapDaysInfo.GetCount());

	POSITION pos = m_mapDaysInfo.GetStartPosition();
	while (pos)
	{
		long nDay = 0;
		XTPDayInfo tmpDI;

		m_mapDaysInfo.GetNextAssoc(pos, nDay, tmpDI);
		if (tmpDI.dtLastAccessTime < dtOldTime)
		{
			m_mapDaysInfo.RemoveKey(nDay);
		}
	}

	DBG_TRACE_DP_TIMER(_T("XTPCalendarController::DayInfoCache - OnSelfClear, End. Days In Cache %d \n"), m_mapDaysInfo.GetCount());
}

VOID CALLBACK CXTPCalendarController::CXTPDayInfoCache::OnTimerCallback(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
{
	UNREFERENCED_PARAMETER(hwnd);
	UNREFERENCED_PARAMETER(uMsg);
	UNREFERENCED_PARAMETER(dwTime);

	CXTPDayInfoCache* pThis = m_mapTimers[idEvent];
	ASSERT(pThis && pThis->m_pOwner);

	if (pThis && pThis->m_pOwner)
	{
		//***********************
		SAFE_MANAGE_STATE(pThis->m_pOwner->m_pModuleState);
		//***********************

		pThis->OnRefreshDays(pThis->m_pOwner->m_nBoldDaysPerIdleStep);
	}
}


//////////////////////////////////////////////////////////////////////////

CXTPCalendarController::CXTPCalendarController()
{
	m_pSink = new CXTPNotifySink();

	m_pCalendarCtrl = NULL;
	m_pDatePickerCtrl = NULL;
	m_bAdjustingView = FALSE;

	m_bBoldDaysWithEvents = TRUE;

	m_bBoldDaysOnIdle = TRUE;
	m_nBoldDaysPerIdleStep = XTP_CC_REFRESH_DAYS_STEP_DAYS;
	m_nBoldDaysIdleStepTime_ms = XTP_CC_REFRESH_DAYS_STEP_TIMER_MS;

	m_bUseActiveViewResoucesForBold = FALSE;

	m_pResourcesNf = NULL;
	m_eLastActiveView = -1;

	m_cnidEventAdded = 0;
	m_cnidEventChanged = 0;
	m_cnidEventDeleted = 0;

	m_DayInfoCache.Init(this);
}

CXTPCalendarController::~CXTPCalendarController()
{
	CMDTARGET_RELEASE(m_pCalendarCtrl);
	CMDTARGET_RELEASE(m_pDatePickerCtrl);
	CMDTARGET_RELEASE(m_pResourcesNf);

	m_pSink->Delete();
}


/////////////////////////////////////////////////////////////////////////////
// CXTPCalendarController message handlers

void CXTPCalendarController::AdviseToNotifications()
{
	m_pSink->UnadviseAll();

	m_cnidEventAdded = 0;
	m_cnidEventChanged = 0;
	m_cnidEventDeleted = 0;

	CMDTARGET_RELEASE(m_pResourcesNf);

	// Advise to Calendar notifications
	if (m_pCalendarCtrl)
	{
		CXTPNotifyConnection* pConnection = m_pCalendarCtrl ? m_pCalendarCtrl->GetConnection() : NULL;
		ASSERT(pConnection);

		if (!pConnection)
		{
			return;
		}

		m_pSink->Advise(pConnection, XTP_NC_CALENDARVIEWWASCHANGED, CreateNotfySinkClassDelegate(this, &CXTPCalendarController::OnEvent_CalendarView));
		m_pSink->Advise(pConnection, XTP_NC_CALENDAROPTIONSWASCHANGED, CreateNotfySinkClassDelegate(this, &CXTPCalendarController::OnEvent_CalendarOptionsChanged));
		m_pSink->Advise(pConnection, XTP_NC_CALENDAR_RESOURCES_WHERE_CHANGED, CreateNotfySinkClassDelegate(this, &CXTPCalendarController::OnEvent_CalendarResourcesChanged));
		m_pSink->Advise(pConnection, XTP_NC_CALENDAR_THEME_CHANGED, CreateNotfySinkClassDelegate(this, &CXTPCalendarController::OnEvent_CalendarThemeChanged));
	}

	// Advise to Date Picker notifications
	if (m_pDatePickerCtrl)
	{
		CXTPNotifyConnection* pConnection = m_pDatePickerCtrl ? m_pDatePickerCtrl->GetConnection() : NULL;
		ASSERT(pConnection);

		if (!pConnection)
		{
			return;
		}

		m_pSink->Advise(pConnection, XTP_NC_DATEPICKERBUTTONCLICKED, CreateNotfySinkClassDelegate(this, &CXTPCalendarController::OnEvent_DatePicker));
		m_pSink->Advise(pConnection, XTP_NC_DATEPICKERSELECTIONCHANGED, CreateNotfySinkClassDelegate(this, &CXTPCalendarController::OnEvent_DatePicker));
		m_pSink->Advise(pConnection, XTP_NC_DATEPICKERBEFOREGOMODAL, CreateNotfySinkClassDelegate(this, &CXTPCalendarController::OnEvent_DatePicker));
		m_pSink->Advise(pConnection, XTP_NC_DATEPICKERMONTHCHANGED, CreateNotfySinkClassDelegate(this, &CXTPCalendarController::OnEvent_DatePicker));
		m_pSink->Advise(pConnection, XTP_NC_DATEPICKERGETDAYMETRICS, CreateNotfySinkClassDelegate(this, &CXTPCalendarController::OnEvent_DtPickGetItemMetrics));
	}
}

void CXTPCalendarController::_AdviseToDataChanged()
{
	ASSERT(m_pResourcesNf);
	if (!m_pResourcesNf)
	{
		return;
	}

	CXTPNotifyConnection* pConnection = m_pResourcesNf->GetConnection();
	ASSERT(pConnection);

	if (!pConnection)
	{
		return;
	}

	m_cnidEventAdded = m_pSink->Advise(pConnection, XTP_NC_CALENDAREVENTWASADDED, CreateNotfySinkClassDelegate(this, &CXTPCalendarController::OnEvent_CalendarDataChanged));
	m_cnidEventChanged = m_pSink->Advise(pConnection, XTP_NC_CALENDAREVENTWASCHANGED, CreateNotfySinkClassDelegate(this, &CXTPCalendarController::OnEvent_CalendarDataChanged));
	m_cnidEventDeleted = m_pSink->Advise(pConnection, XTP_NC_CALENDAREVENTWASDELETED, CreateNotfySinkClassDelegate(this, &CXTPCalendarController::OnEvent_CalendarDataChanged));
}

void CXTPCalendarController::_UnadviseFromDataChanged()
{
	if (m_cnidEventAdded)
	{
		m_pSink->Unadvise(m_cnidEventAdded);
	}

	if (m_cnidEventChanged)
	{
		m_pSink->Unadvise(m_cnidEventChanged);
	}

	if (m_cnidEventDeleted)
	{
		m_pSink->Unadvise(m_cnidEventDeleted);
	}

	m_cnidEventAdded = 0;
	m_cnidEventChanged = 0;
	m_cnidEventDeleted = 0;
}

//BOOL CXTPCalendarController::IsFirstDayOfWeekShifted_FromSun2Mon()
//{
//  if (!m_pCalendarCtrl || !m_pCalendarCtrl->GetActiveView())
//      return FALSE;
//
//  int nViewType = m_pCalendarCtrl->GetActiveView()->GetViewType();
//  if (nViewType == xtpCalendarMonthView)
//  {
//      if (m_pCalendarCtrl->MonthView_IsCompressWeekendDays())
//      {
//          if (m_pCalendarCtrl->GetFirstDayOfWeek() == 1)
//              return TRUE;
//      }
//  }
//  else if (nViewType == xtpCalendarWeekView)
//  {
//      if (m_pCalendarCtrl->GetFirstDayOfWeek() == 1)
//          return TRUE;
//  }
//  return FALSE;
//}

void CXTPCalendarController::OnEvent_CalendarView(XTP_NOTIFY_CODE Event, WPARAM /*wParam*/, LPARAM lParam)
{
	if (!XTP_NC_CALENDARVIEWWASCHANGED == Event)
	{
		ASSERT(FALSE);
		return;
	}
	if (m_bAdjustingView)
	{
		return;
	}

	if (m_pDatePickerCtrl && m_pCalendarCtrl)
	{
		CXTPCalendarView* pView = m_pCalendarCtrl->GetActiveView();
		if (!pView)
			return;

		int nViewTypeX = pView->GetViewType();

		if (nViewTypeX == xtpCalendarWorkWeekView)
			nViewTypeX = xtpCalendarDayView;

		if (m_eLastActiveView != nViewTypeX)
		{
			m_eLastActiveView = nViewTypeX;
			CMDTARGET_RELEASE(m_pResourcesNf);
			_UnadviseFromDataChanged();

			if (m_bBoldDaysOnIdle)
			{
				COleDateTime dtFirstVisibleDay, dtLastVisibleDay;
				if (m_pDatePickerCtrl->GetVisibleRange(dtFirstVisibleDay, dtLastVisibleDay))
				{
					m_DayInfoCache.RequestToRefreshDays(dtFirstVisibleDay, dtLastVisibleDay);
				}
			}
		}

		COleDateTimeSpan spCompressCorrector(0, 0, 0, 0);
		//if (IsFirstDayOfWeekShifted_FromSun2Mon())
		//  spCompressCorrector.SetDateTimeSpan(-1, 0, 0, 0);

		int nCount = pView->GetViewDayCount();
		if (nCount > 0)
		{
			COleDateTime dtDay = m_pCalendarCtrl->GetActiveView()->GetViewDayDate(0);
			dtDay += spCompressCorrector;
			m_pDatePickerCtrl->SetSelRange(dtDay, dtDay);
		}

		for (int i = 1; i < nCount; i++)
		{
			COleDateTime dtDay = m_pCalendarCtrl->GetActiveView()->GetViewDayDate(i);
			dtDay += spCompressCorrector;
			m_pDatePickerCtrl->Select(dtDay);
		}

		if (lParam != XTP_CC_DONT_SCROLL_DP)
			m_pDatePickerCtrl->EnsureVisibleSelection();

		if (::IsWindow(m_pDatePickerCtrl->GetSafeHwnd()))
		{
			m_pDatePickerCtrl->RedrawControl();
			m_pDatePickerCtrl->UpdateWindow();
		}
	}
}

void CXTPCalendarController::OnEvent_DatePicker(XTP_NOTIFY_CODE Event, WPARAM wParam, LPARAM /*lParam*/)
{
	wParam;

	switch (Event)
	{
	case XTP_NC_DATEPICKERBEFOREGOMODAL:
		if (m_pDatePickerCtrl && m_pCalendarCtrl)
		{
			OnEvent_CalendarView(XTP_NC_CALENDARVIEWWASCHANGED, 0, 0);
		}
		break;
	case XTP_NC_DATEPICKERBUTTONCLICKED:
		DBG_TRACE_DP_NF(_T("OnEvent: DatePickerButtonClicked - ID = %u.     {CalendarController}. \n"), wParam);
		break;
	case XTP_NC_DATEPICKERMONTHCHANGED:
		m_DayInfoCache.UpActivePriority();
		break;
	case XTP_NC_DATEPICKERSELECTIONCHANGED:
		DBG_TRACE_DP_NF(_T("OnEvent: XTP_NC_DATEPICKERSELECTIONCHANGED.     {CalendarController}. \n"));
		if (m_pDatePickerCtrl && m_pCalendarCtrl && m_pCalendarCtrl->GetActiveView())
		{
			BOOL bFastMode = m_pCalendarCtrl->m_bSwitchToDayViewIfPickedSingleDay;
			BOOL bChanged = FALSE;

			COleDateTime dtFrom;
			COleDateTime dtTo;
			if (m_pDatePickerCtrl->GetSelRange(dtFrom, dtTo))
			{
				int nFirstDayOfWeekIndex = m_pDatePickerCtrl->GetFirstDayOfWeek();
				int nFirstDayOfWeek = dtFrom.GetDayOfWeek();
				BOOL bWeekStart = (nFirstDayOfWeekIndex == nFirstDayOfWeek);

				CXTPCalendarControl::CUpdateContext updateContext(m_pCalendarCtrl);

				int nSelRangeDays = CXTPCalendarUtils::GetTotalDays(dtTo - dtFrom) + 1;
				// count selected days
				int nSelDays = 0;
				COleDateTimeSpan spDay(1, 0, 0, 0);
				for (COleDateTime dtDay = dtFrom; dtDay <= dtTo; dtDay += spDay)
				{
					if (m_pDatePickerCtrl->IsSelected(dtDay))
						nSelDays++;
				}
				BOOL bDaysCont = (nSelRangeDays == nSelDays);
//LOGIC TO SELECT VIEW TYPE <<
				// Day view
				int eViewMode = XTP_SAFE_GET2(m_pCalendarCtrl, GetActiveView(), GetViewType(), xtpCalendarDayView);

				if (eViewMode == xtpCalendarTimeLineView)
				{
					CXTPCalendarTimeLineView* pView = (CXTPCalendarTimeLineView* ) m_pCalendarCtrl->GetActiveView();
					if (pView)
					{
						pView->SetStartViewDate(dtFrom);
						if (nSelRangeDays == 1)
							pView->SetTimeScaleID(xtpTSPID_Day);
						else if (nSelRangeDays <= 5)
							pView->SetTimeScaleID(xtpTSPID_WorkWeek);
						else if (nSelRangeDays <= 7)
							pView->SetTimeScaleID(xtpTSPID_Week);
						else
							pView->SetTimeScaleID(xtpTSPID_Month);
					}

					return;
				}

				if (nSelRangeDays <= nXTPMaxDayViewDays ||
					!bDaysCont || !bWeekStart && nSelRangeDays <= 8)
				{
					m_bAdjustingView = TRUE;

					// to save WorkWeek view mode

					//if (eViewMode != xtpCalendarWorkWeekView || nSelDays != 1)
					if (bFastMode &&
						((eViewMode != xtpCalendarWorkWeekView)
						&& (eViewMode != xtpCalendarFullWeekView))
						|| nSelDays != 1)
						eViewMode = xtpCalendarDayView;

					if (eViewMode == xtpCalendarMonthView)
					{
						CXTPCalendarMonthView* pMonthView = DYNAMIC_DOWNCAST(CXTPCalendarMonthView, m_pCalendarCtrl->GetActiveView());
						if (pMonthView)
						{
							pMonthView->SelectDay(dtFrom);
							CXTPCalendarControl::CViewChangedContext viewChanged(m_pCalendarCtrl, xtpCalendarViewChangedLock);
							COleDateTime dtBegin = dtFrom;
							// Calculate weeks count
							COleDateTime dtWeekFromBegin = pMonthView->GetGrid()->ShiftDateToCell_00(dtFrom);
							COleDateTime dtWeekToEnd = pMonthView->GetGrid()->ShiftDateToCell_00(dtTo);
							dtWeekToEnd += COleDateTimeSpan(7, 0, 0, 0);
							int nSelectedWeeksRange = CXTPCalendarUtils::GetTotalDays(dtWeekToEnd - dtWeekFromBegin);

							int nWeeks = nSelectedWeeksRange / 7;
							nWeeks = max(XTP_CALENDAR_MONTHVIEW_SHOW_WEEKS_MIN, nWeeks);
							nWeeks = min(XTP_CALENDAR_MONTHVIEW_SHOW_WEEKS_MAX, nWeeks);

							pMonthView->GetGrid()->SetBeginDate(dtBegin);
							pMonthView->GetGrid()->SetWeeksCount(nWeeks);

							m_pCalendarCtrl->Populate();
						}
					}
					else if ((eViewMode == xtpCalendarWorkWeekView)
						|| (eViewMode == xtpCalendarFullWeekView)
						|| (eViewMode == xtpCalendarWeekView))
					{
						CXTPCalendarDayView* pDayView = DYNAMIC_DOWNCAST(CXTPCalendarDayView, m_pCalendarCtrl->GetActiveView());
						if (pDayView)
						{
							pDayView->UnselectAllEvents();

							COleDateTime dtWWStart;
							m_pCalendarCtrl->GetWorkDayStartTime(dtWWStart);
							dtWWStart = CXTPCalendarUtils::UpdateDate(dtWWStart, dtFrom);

							pDayView->SetSelection(dtWWStart, dtWWStart + pDayView->GetCellDuration());
						}
						else
						{
							CXTPCalendarWeekView* pWeekView = DYNAMIC_DOWNCAST(CXTPCalendarWeekView, m_pCalendarCtrl->GetActiveView());
							if (pWeekView)
							{
								pWeekView->SelectDay(dtFrom);
								pWeekView->SetBeginDate(dtFrom);
								m_pCalendarCtrl->Populate();
							}
						}
					}

					//*** switch calendar view
					m_pCalendarCtrl->SwitchActiveView((XTPCalendarViewType)eViewMode);
					//***

					CXTPCalendarDayView* pDayView = DYNAMIC_DOWNCAST(CXTPCalendarDayView, m_pCalendarCtrl->GetActiveView());

					// DayView - set visible days
					if (pDayView && eViewMode == xtpCalendarDayView)
					{
						COleDateTime dtSelStart, dtSelEnd;
						BOOL bAllDay = FALSE;
						BOOL bSel = pDayView->GetSelection(&dtSelStart, &dtSelEnd, &bAllDay);
						if (bSel)
						{
							COleDateTimeSpan spSel = dtSelEnd - dtSelStart;
							COleDateTimeSpan spSelMin = pDayView->GetCellDuration();
							if (spSel < spSelMin)
								bSel = FALSE;
						}
						if (!bSel)
						{
							m_pCalendarCtrl->GetWorkDayStartTime(dtSelStart);
							dtSelEnd = dtSelStart + pDayView->GetCellDuration();
							bAllDay = FALSE;
						}
						dtSelStart = CXTPCalendarUtils::UpdateDate(dtSelStart, dtFrom);
						dtSelEnd = CXTPCalendarUtils::UpdateDate(dtSelEnd, dtFrom);

						pDayView->ShowDay(dtFrom);

						pDayView->SetSelection(dtSelStart, dtSelEnd, bAllDay);

						// add other selected days to calendar view
						for (COleDateTime dtDay(dtFrom + spDay); dtDay <= dtTo; dtDay += spDay)
						{
							if (m_pDatePickerCtrl->IsSelected(dtDay))
								pDayView->AddDay(dtDay); // add it to Calendar View
						}
						m_pCalendarCtrl->Populate();
					}
					m_bAdjustingView = FALSE;

					bChanged = TRUE;
				}
				// Week view
				else if (nSelRangeDays == 7 && bDaysCont)
				{
					COleDateTime dtBegin = dtFrom;

					m_bAdjustingView = TRUE;

					if (m_pCalendarCtrl->m_bMultiColumnWeekMode)
						m_pCalendarCtrl->SwitchActiveView(xtpCalendarFullWeekView);
					else
						m_pCalendarCtrl->SwitchActiveView(xtpCalendarWeekView);

					CXTPCalendarWeekView* pWeekView = DYNAMIC_DOWNCAST(CXTPCalendarWeekView, m_pCalendarCtrl->GetActiveView());
					if (pWeekView)
					{
						pWeekView->SetBeginDate(dtBegin);

						m_pCalendarCtrl->Populate();
					}
					else if (m_pCalendarCtrl->m_bMultiColumnWeekMode)
					{
						CXTPCalendarDayView* pWeekDayView = DYNAMIC_DOWNCAST(CXTPCalendarDayView, m_pCalendarCtrl->GetActiveView());
						if (pWeekDayView)
						{
							pWeekDayView->ShowDays(dtBegin, dtTo);

							m_pCalendarCtrl->Populate();
						}
					}
					m_bAdjustingView = FALSE;

					bChanged = TRUE;
				}
				// Month view
				else
				{
					ASSERT(nSelRangeDays == nSelDays && nSelRangeDays > 7);

					m_bAdjustingView = TRUE;

					m_pCalendarCtrl->SwitchActiveView(xtpCalendarMonthView);
					CXTPCalendarMonthView* pMonthView = DYNAMIC_DOWNCAST(CXTPCalendarMonthView, m_pCalendarCtrl->GetActiveView());
					if (pMonthView)
					{
						CXTPCalendarControl::CViewChangedContext viewChanged(m_pCalendarCtrl, xtpCalendarViewChangedLock);
						COleDateTime dtBegin = dtFrom;
						// Calculate weeks count
						COleDateTime dtWeekFromBegin = pMonthView->GetGrid()->ShiftDateToCell_00(dtFrom);
						COleDateTime dtWeekToEnd = pMonthView->GetGrid()->ShiftDateToCell_00(dtTo);
						dtWeekToEnd += COleDateTimeSpan(7, 0, 0, 0);
						int nSelectedWeeksRange = CXTPCalendarUtils::GetTotalDays(dtWeekToEnd - dtWeekFromBegin);

						int nWeeks = nSelectedWeeksRange / 7;
						nWeeks = max(XTP_CALENDAR_MONTHVIEW_SHOW_WEEKS_MIN, nWeeks);
						nWeeks = min(XTP_CALENDAR_MONTHVIEW_SHOW_WEEKS_MAX, nWeeks);

						pMonthView->GetGrid()->SetBeginDate(dtBegin);
						pMonthView->GetGrid()->SetWeeksCount(nWeeks);

						m_pCalendarCtrl->Populate();
					}
					m_bAdjustingView = FALSE;

					bChanged = TRUE;
				}
//LOGIC TO SELECT VIEW TYPE <<
				if (bChanged)
				{
					OnEvent_CalendarView(XTP_NC_CALENDARVIEWWASCHANGED, 0, XTP_CC_DONT_SCROLL_DP);

					if (m_pDatePickerCtrl->IsModal())
					{
						m_pCalendarCtrl->Invalidate(FALSE);
						m_pCalendarCtrl->RedrawControl();
					}
				}
			}
		}
		break;
	default:
		ASSERT(FALSE);
	}
}

void CXTPCalendarController::OnEvent_CalendarOptionsChanged(XTP_NOTIFY_CODE Event, WPARAM wParam, LPARAM /*lParam*/)
{
	if (Event != XTP_NC_CALENDAROPTIONSWASCHANGED)
	{
		ASSERT(FALSE);
		return;
	}

	if ((int) wParam < 0)
		SetFirstDayOfWeekToDatePicker();
}

void CXTPCalendarController::OnEvent_CalendarResourcesChanged(XTP_NOTIFY_CODE Event, WPARAM /*wParam*/, LPARAM /*lParam*/)
{
	if (Event != XTP_NC_CALENDARDATAPROVIDERWASCHANGED)
	{
		ASSERT(FALSE);
		return;
	}

	CMDTARGET_RELEASE(m_pResourcesNf);
	_UnadviseFromDataChanged();

	m_DayInfoCache.Clear();
}

void CXTPCalendarController::OnEvent_CalendarThemeChanged(XTP_NOTIFY_CODE Event, WPARAM /*wParam*/, LPARAM /*lParam*/)
{
	if (Event != XTP_NC_CALENDAR_THEME_CHANGED)
	{
		ASSERT(FALSE);
		return;
	}

	if (m_pCalendarCtrl && m_pDatePickerCtrl)
	{
		if (m_pDatePickerCtrl->GetPaintTheme() != m_pCalendarCtrl->GetPaintTheme())
		{
			m_pDatePickerCtrl->SetPaintTheme(m_pCalendarCtrl->GetPaintTheme());
		}
	}

}

void CXTPCalendarController::OnEvent_CalendarDataChanged(XTP_NOTIFY_CODE Event, WPARAM /*wParam*/, LPARAM lParam)
{
	if (!m_pDatePickerCtrl)
	{
		return;
	}

	if (Event != XTP_NC_CALENDAREVENTWASADDED &&
		Event != XTP_NC_CALENDAREVENTWASCHANGED&&
		Event != XTP_NC_CALENDAREVENTWASDELETED)
	{
		ASSERT(FALSE);
		return;
	}

	COleDateTime dtFirstVisibleDay, dtLastVisibleDay;

	BOOL bRes = m_pDatePickerCtrl->GetVisibleRange(dtFirstVisibleDay, dtLastVisibleDay);
	if (!bRes)
	{
		return;
	}

	CXTPCalendarEvent* pEvent = (CXTPCalendarEvent*)lParam;
	if (!pEvent)
	{
		return;
	}

	if (Event == XTP_NC_CALENDAREVENTWASADDED ||
		Event == XTP_NC_CALENDAREVENTWASDELETED)
	{
		if (CXTPCalendarUtils::ResetTime(pEvent->GetStartTime()) > dtLastVisibleDay ||
			CXTPCalendarUtils::ResetTime(pEvent->GetEndTime()) < dtFirstVisibleDay )
		{
			return; // xtpCalendar_Skip;
		}

		if (m_bBoldDaysOnIdle)
		{
			m_DayInfoCache.ClearDays(pEvent->GetStartTime(), pEvent->GetEndTime());
		}
	}
	else
	{
		if (m_bBoldDaysOnIdle)
		{
			m_DayInfoCache.RequestToRefreshDays(dtFirstVisibleDay, dtLastVisibleDay);

			COleDateTime dtFom = max(dtFirstVisibleDay, pEvent->GetStartTime());
			COleDateTime dtTo = min(dtLastVisibleDay, pEvent->GetEndTime());
			m_DayInfoCache.RequestToRefreshDays(dtFom, dtTo);
		}
	}

	m_pDatePickerCtrl->_RedrawControl(FALSE);
}

void CXTPCalendarController::SetFirstDayOfWeekToDatePicker()
{
	if (m_pDatePickerCtrl && m_pCalendarCtrl)
	{
		int nFirstDayOfWeekIndex = m_pCalendarCtrl->GetFirstDayOfWeek();
//<<<
		int nOldFirstDayOfWeekIndex = m_pDatePickerCtrl->GetFirstDayOfWeek();

		COleDateTime dt1, dt2;
		m_pDatePickerCtrl->GetSelRange(dt1, dt2);
		dt1 += COleDateTimeSpan(nFirstDayOfWeekIndex - nOldFirstDayOfWeekIndex, 0, 0, 0);
		dt2 += COleDateTimeSpan(nFirstDayOfWeekIndex - nOldFirstDayOfWeekIndex, 0, 0, 0);

		int nViewType = m_pCalendarCtrl->GetActiveView()->GetViewType();
		if (nViewType == xtpCalendarWeekView)
			m_pDatePickerCtrl->SetSelRange(dt1, dt2);
//>>>
		m_pDatePickerCtrl->SetFirstDayOfWeek(nFirstDayOfWeekIndex);
	}
}

void CXTPCalendarController::OnEvent_DtPickGetItemMetrics(XTP_NOTIFY_CODE Event, WPARAM wParam, LPARAM lParam)
{
	if (Event != XTP_NC_DATEPICKERGETDAYMETRICS)
	{
		ASSERT(FALSE);
		return;
	}
	if (!m_bBoldDaysWithEvents)
	{
		return;
	}

	const COleDateTime dtDay = (DATE)(XTP_DATE_VALUE)wParam;
	XTP_DAYITEM_METRICS* pDayItemMetrics = (XTP_DAYITEM_METRICS*)lParam;
	ASSERT(pDayItemMetrics);

	if (!pDayItemMetrics || !m_pCalendarCtrl || !m_pDatePickerCtrl)
	{
		return;
	}

	if (!m_pResourcesNf)
	{
		m_pResourcesNf = new CXTPCalendarResourcesNf();
		if (!m_pResourcesNf)
		{
			return;
		}
		CXTPCalendarResources* pRC0 = m_pCalendarCtrl->GetResources();
		if (m_bUseActiveViewResoucesForBold)
		{
			pRC0 = m_pCalendarCtrl->GetActiveView()->GetResources();
		}

		m_pResourcesNf->Append(pRC0);

		_AdviseToDataChanged();

		m_pResourcesNf->ReBuildInternalData();
	}

	BOOL bHasEvents = FALSE;

	if (m_bBoldDaysOnIdle)
	{
		bHasEvents = m_DayInfoCache.HasEvents(dtDay);
	}
	else
	{
		// check whether specified day has events
		bHasEvents = _HasEvents(dtDay);
	}

	// make the day with events as bold
	if (bHasEvents)
	{
		// set bold to day metrics
		pDayItemMetrics->SetFont(m_pDatePickerCtrl->GetPaintManager()->GetDayTextFontBold());
	}
}
BOOL CXTPCalendarController::_HasEvents(COleDateTime dtDay)
{
	ASSERT(m_pResourcesNf);
	if (!m_pResourcesNf)
	{
		return FALSE;
	}

	const CXTPCalendarResources* pRCx = m_pResourcesNf->GetResourcesGroupedByDP();
	int nRCCount = pRCx ? pRCx->GetCount() : 0;

	for (int i = 0; i < nRCCount; i++)
	{
		CXTPCalendarResource* pRC = pRCx->GetAt(i);
		ASSERT(pRC);
		CXTPCalendarEventsPtr ptrEvents = pRC ? pRC->RetrieveDayEvents(dtDay) : NULL;
		if (ptrEvents && ptrEvents->GetCount() > 0)
		{
			return TRUE;
		}
	}

	return FALSE;
}


void CXTPCalendarController::SetCalendar(CXTPCalendarControl* pCalendarCtrl)
{
	m_pSink->UnadviseAll();

	if (m_pCalendarCtrl)
	{
		m_pCalendarCtrl->InternalRelease();
	}
	m_pCalendarCtrl = pCalendarCtrl;
	if (m_pCalendarCtrl)
	{
		m_pCalendarCtrl->InternalAddRef();

		AdviseToNotifications();

		SetFirstDayOfWeekToDatePicker();

		OnEvent_CalendarThemeChanged(XTP_NC_CALENDAR_THEME_CHANGED, 0, 0);
	}

	if (m_pCalendarCtrl && m_pDatePickerCtrl)
	{
		m_eLastActiveView = -1; // to ensure update
		OnEvent_CalendarView(XTP_NC_CALENDARVIEWWASCHANGED, 0, 0);
	}
}

void CXTPCalendarController::SetDatePicker(CXTPDatePickerControl* pDatePickerCtrl)
{
	m_pSink->UnadviseAll();

	if (m_pDatePickerCtrl)
	{
		m_pDatePickerCtrl->InternalRelease();
	}
	m_pDatePickerCtrl = pDatePickerCtrl;
	if (m_pDatePickerCtrl)
	{
		m_pDatePickerCtrl->InternalAddRef();

		m_pDatePickerCtrl->SetMaxSelCount(XTP_CALENDAR_MONTHVIEW_SHOW_WEEKS_MAX * 7);

		AdviseToNotifications();

		SetFirstDayOfWeekToDatePicker();

		OnEvent_CalendarThemeChanged(XTP_NC_CALENDAR_THEME_CHANGED, 0, 0);
	}

	if (m_pCalendarCtrl && m_pDatePickerCtrl)
	{
		m_eLastActiveView = -1; // to ensure update
		OnEvent_CalendarView(XTP_NC_CALENDARVIEWWASCHANGED, 0, 0);
	}
}

CXTPCalendarControl* CXTPCalendarController::GetCalendar() const
{
	return m_pCalendarCtrl;
}

CXTPDatePickerControl* CXTPCalendarController::GetDatePicker() const
{
	return m_pDatePickerCtrl;
}

void CXTPCalendarController::SetBoldDaysWithEvents(BOOL bBold)
{
	m_bBoldDaysWithEvents = bBold;

	if (m_pResourcesNf && !m_bBoldDaysWithEvents)
	{
		CMDTARGET_RELEASE(m_pResourcesNf);
		m_eLastActiveView = -1;
		_UnadviseFromDataChanged();

		m_DayInfoCache.Clear();
	}
}