#include "stdafx.h"
#include "CListControl.h"
#include "PaintUtils.h"
#include "CListControlUserOptions.h"
#include "GDIUtils.h"

CListControlUserOptions * CListControlUserOptions::instance = nullptr;

CRect CListControlImpl::GetClientRectHook() const {
	CRect temp; if (!GetClientRect(temp)) temp.SetRectEmpty(); return temp;
}

bool CListControlImpl::UserEnabledSmoothScroll() const {
	auto i = CListControlUserOptions::instance;
	if ( i != nullptr ) return i->useSmoothScroll();
	return false;
}

LRESULT CListControlImpl::SetFocusPassThru(UINT,WPARAM,LPARAM,BOOL& bHandled) {
	SetFocus();
	bHandled = FALSE;
	return 0;
}

void CListControlImpl::EnsureVisibleRectAbs(const CRect & p_rect) {
	const CRect rcView = GetVisibleRectAbs();
	const CRect rcItem = p_rect;
	int deltaX = 0, deltaY = 0;

	const bool centerOnItem = m_ensureVisibleUser;

	if (rcItem.top < rcView.top || rcItem.bottom > rcView.bottom) {
		if (rcItem.Height() > rcView.Height()) {
			deltaY = rcItem.top - rcView.top;
		} else {
			if (centerOnItem) {
				deltaY = rcItem.CenterPoint().y - rcView.CenterPoint().y;
			} else {
				if (rcItem.bottom > rcView.bottom) deltaY = rcItem.bottom - rcView.bottom;
				else deltaY = rcItem.top - rcView.top;
				
			}
		}
	}
	if (rcItem.left < rcView.left || rcItem.right > rcView.right) {
		if (rcItem.Width() > rcView.Width()) {
			if (rcItem.left > rcView.left || rcItem.right < rcView.right) deltaX = rcItem.left - rcView.left;
		} else {
			if (centerOnItem) {
				deltaX = rcItem.CenterPoint().x - rcView.CenterPoint().x;
			} else {
				if (rcItem.right > rcView.right) deltaX = rcItem.right - rcView.right;
				else deltaX = rcItem.left - rcView.left;
			}
		}
	}

	if (deltaX != 0 || deltaY != 0) {
		MoveViewOriginDelta(CPoint(deltaX,deltaY));
	}
}
void CListControlImpl::EnsureItemVisible(t_size p_item, bool bUser) {
	m_ensureVisibleUser = bUser;
	EnsureVisibleRectAbs(GetItemRectAbs(p_item));
	m_ensureVisibleUser = false;
}
void CListControlImpl::EnsureHeaderVisible(int p_group) {
	CRect rect;
	if (GetGroupHeaderRectAbs(p_group,rect)) EnsureVisibleRectAbs(rect);
}

void CListControlImpl::RefreshSlider(bool p_vertical) {
	const CRect viewArea = GetViewAreaRectAbs();
	const CRect rcVisible = GetVisibleRectAbs();
	SCROLLINFO si = {};
	si.cbSize = sizeof(si);
	si.fMask = SIF_PAGE|SIF_RANGE|SIF_POS;


	if (AllowScrollbar(p_vertical)) {
		if (p_vertical) {
			si.nPage = rcVisible.Height();
			si.nMin = viewArea.top;
			si.nMax = viewArea.bottom - 1;
			si.nPos = rcVisible.top;
		} else {
			si.nPage = rcVisible.Width();
			si.nMin = viewArea.left;
			si.nMax = viewArea.right - 1;
			si.nPos = rcVisible.left;
		}	
	}

	SetScrollInfo(p_vertical ? SB_VERT : SB_HORZ, &si);
}

void CListControlImpl::RefreshSliders() {
	//PROBLEM: while lots of data can be reused across those, it has to be recalculated inbetween because view area etc may change when scroll info changes
	RefreshSlider(false); RefreshSlider(true);
}

int CListControlImpl::GetScrollThumbPos(int which) {
	SCROLLINFO si = {};
	si.cbSize = sizeof(si);
	si.fMask = SIF_TRACKPOS;
	GetScrollInfo(which,&si);
	return si.nTrackPos;
}

namespace {
	class ResolveGroupHelper {
	public:
		ResolveGroupHelper(const CListControlImpl & p_control) : m_control(p_control) {}
		int operator[](t_size p_index) const {return m_control.GetItemGroup(p_index);}
	private:
		const CListControlImpl & m_control;
	};
}

bool CListControlImpl::ResolveGroupRange(int p_id,t_size & p_base,t_size & p_count) const {

	return pfc::binarySearch<>::runGroup(ResolveGroupHelper(*this),0,GetItemCount(),p_id,p_base,p_count);


	//return pfc::bsearch_range_t(GetItemCount(),ResolveGroupHelper(*this),pfc::compare_t<int,int>,p_id,p_base,p_count);
}

static int HandleScroll(WORD p_code,int p_offset,int p_page, int p_line, int p_bottom, int p_thumbpos) {
	switch(p_code) {
	case SB_LINEUP:
		return p_offset - p_line;
	case SB_LINEDOWN:
		return p_offset + p_line;
	case SB_BOTTOM:
		return p_bottom - p_page;
	case SB_TOP:
		return 0;
	case SB_PAGEUP:
		return p_offset - p_page;
	case SB_PAGEDOWN:
		return p_offset + p_page;
	case SB_THUMBPOSITION:
		return p_thumbpos;
	case SB_THUMBTRACK:
		return p_thumbpos;
	default:
		return p_offset;
	}
}

static CPoint ClipPointToRect(CPoint const & p_pt,CRect const & p_rect) {
	return CPoint(pfc::clip_t(p_pt.x,p_rect.left,p_rect.right),pfc::clip_t(p_pt.y,p_rect.top,p_rect.bottom));
}

void CListControlImpl::MoveViewOriginNoClip(CPoint p_target) {
	UpdateWindow();
	const CPoint old = m_viewOrigin;
	m_viewOrigin = p_target;

	if (m_viewOrigin != old) {
		if (m_viewOrigin.x != old.x) SetScrollPos(SB_HORZ,m_viewOrigin.x);
		if (m_viewOrigin.y != old.y) SetScrollPos(SB_VERT,m_viewOrigin.y);

		const CPoint delta = old - m_viewOrigin;
		if (FixedOverlayPresent()) Invalidate();
		else {
			DWORD flags = SW_INVALIDATE | SW_ERASE;
			const DWORD smoothScrollMS = 50;
			if (this->UserEnabledSmoothScroll() && this->CanSmoothScroll()) {
				flags |= SW_SMOOTHSCROLL | (smoothScrollMS << 16);
			}

			ScrollWindowEx(delta.x,delta.y,GetClientRectHook(),NULL,0,0,flags );
		}

		OnViewOriginChange(m_viewOrigin - old);
	}
}

CPoint CListControlImpl::ClipViewOrigin(CPoint p_origin) const {
	return ClipPointToRect(p_origin,GetValidViewOriginArea());
}
void CListControlImpl::MoveViewOrigin(CPoint p_target) {
	MoveViewOriginNoClip(ClipViewOrigin(p_target));
}

#ifndef SPI_GETWHEELSCROLLCHARS
#define SPI_GETWHEELSCROLLCHARS   0x006C
#endif
int CListControlImpl::HandleWheel(int & p_accum,int p_delta, bool bHoriz) {
	if ( m_suppressMouseWheel ) return 0;
	UINT scrollLines = 1;
	SystemParametersInfo(bHoriz ? SPI_GETWHEELSCROLLCHARS : SPI_GETWHEELSCROLLLINES,0,&scrollLines,0);
	if (scrollLines == ~0) {
		p_accum = 0;
		int rv = -pfc::sgn_t(p_delta);
		CRect client = GetClientRectHook();
		if (bHoriz) rv *= client.Width();
		else rv *= client.Height();
		return rv;
	}

	const int itemHeight = GetItemHeight();
	const int extraScale = 10000;

	p_accum += p_delta * extraScale;
	if ((int)scrollLines < 1) scrollLines = 1;
	int multiplier = (WHEEL_DELTA * extraScale) / (scrollLines * itemHeight);
	if (multiplier<1) multiplier = 1;

	int delta = pfc::rint32( (double) p_accum / (double) multiplier );
	p_accum -= delta * multiplier;
	return -delta;

	/*
	if (p_accum<=-multiplier || p_accum>=multiplier) {
		int direction;
		int ov = p_accum;
		if (ov<0) {
			direction = -1;
			ov = -ov;
			p_accum = - ((-p_accum)%multiplier);
		} else {
			p_accum %= multiplier;
			direction = 1;
		}

		return  - (direction * (ov + multiplier - 1) ) / multiplier;
	} else {
		return 0;
	}
	*/
}

LRESULT CListControlImpl::OnVWheel(UINT,WPARAM p_wp,LPARAM p_lp,BOOL&) {
	const CRect client = GetClientRectHook(), view = this->GetViewAreaRectAbs();
	int deltaPixels = HandleWheel(m_wheelAccumY,(short)HIWORD(p_wp), false);

	const bool canVScroll = client.Height() < view.Height();
	const bool canHScroll = client.Width() < view.Width();
	
	CPoint ptDelta;
	if ( canVScroll && canHScroll && GetHotkeyModifierFlags() == MOD_SHIFT) {
		ptDelta = CPoint(deltaPixels, 0); // default to horizontal scroll if shift is pressed
	} else if (canVScroll) {
		ptDelta = CPoint(0,deltaPixels);
	} else if (canHScroll) {
		ptDelta = CPoint(deltaPixels,0);
	}

	if ( ptDelta != CPoint(0,0) ) {
		MoveViewOriginDelta(ptDelta);
	}
	return 0;
}
LRESULT CListControlImpl::OnHWheel(UINT,WPARAM p_wp,LPARAM p_lp,BOOL&) {
	const CRect client = GetClientRectHook();
	int deltaPixels = HandleWheel(m_wheelAccumX,(short)HIWORD(p_wp), true);
	MoveViewOriginDelta(CPoint(-deltaPixels,0));
	return 0;
}

LRESULT CListControlImpl::OnVScroll(UINT,WPARAM p_wp,LPARAM,BOOL&) {
	int target = HandleScroll(LOWORD(p_wp),m_viewOrigin.y,GetVisibleRectAbs().Height(),GetItemHeight(),GetViewAreaRectAbs().bottom,GetScrollThumbPos(SB_VERT));
	MoveViewOrigin(CPoint(m_viewOrigin.x,target));
	return 0;
}
LRESULT CListControlImpl::OnHScroll(UINT,WPARAM p_wp,LPARAM,BOOL&) {
	int target = HandleScroll(LOWORD(p_wp),m_viewOrigin.x,GetVisibleRectAbs().Width(),GetItemHeight() /*fixme*/,GetViewAreaRectAbs().right,GetScrollThumbPos(SB_HORZ));
	MoveViewOrigin(CPoint(target,m_viewOrigin.y));
	return 0;
}

LRESULT CListControlImpl::OnGesture(UINT,WPARAM,LPARAM lParam,BOOL& bHandled) {
	if (!this->m_gestureAPI.IsAvailable()) {
		bHandled = FALSE;
		return 0;
	}
	HGESTUREINFO hGesture = (HGESTUREINFO) lParam;
	GESTUREINFO gestureInfo = {sizeof(gestureInfo)};
	if (m_gestureAPI.GetGestureInfo(hGesture, &gestureInfo)) {
		//console::formatter() << "WM_GESTURE " << pfc::format_hex( gestureInfo.dwFlags ) << " " << (int)gestureInfo.dwID << " X:" << gestureInfo.ptsLocation.x << " Y:" << gestureInfo.ptsLocation.y << " arg:" << (__int64) gestureInfo.ullArguments;
		CPoint pt( gestureInfo.ptsLocation.x, gestureInfo.ptsLocation.y );
		switch(gestureInfo.dwID) {
		case GID_BEGIN:
			m_gesturePoint = pt;
			break;
		case GID_END:
			break;
		case GID_PAN:
			MoveViewOriginDelta( this->m_gesturePoint - pt);
			m_gesturePoint = pt;
			break;
		}
	}

	m_gestureAPI.CloseGestureInfoHandle(hGesture);
	bHandled = TRUE;
	return 0;
}

LRESULT CListControlImpl::OnSize(UINT,WPARAM,LPARAM p_lp,BOOL&) {
	OnSizeAsync_Trigger();
	RefreshSliders();
	return 0;
}


void CListControlImpl::RenderBackground( CDCHandle dc, CRect const & rc ) {
	PaintUtils::FillRectSimple(dc,rc,GetSysColorHook(colorBackground));
}

void CListControlImpl::PaintContent(CRect rcPaint, HDC dc) {
	CDCHandle renderDC(dc);

	CMemoryDC bufferDC(renderDC,rcPaint);
	renderDC = bufferDC;
	this->RenderBackground(renderDC, rcPaint);
		
	{
		const CPoint pt = GetViewOffset();
		OffsetWindowOrgScope offsetScope(renderDC, pt);
		CRect renderRect = rcPaint; renderRect.OffsetRect(pt);
		RenderRect(renderRect,renderDC);
	}
}

void CListControlImpl::OnPrintClient(HDC dc, UINT uFlags) {
	CRect rcClient; this->GetClientRect( rcClient );
	PaintContent( rcClient, dc );
}

LRESULT CListControlImpl::OnPaint(UINT,WPARAM,LPARAM,BOOL&) {
	CPaintDC paintDC(*this);

	PaintContent( paintDC.m_ps.rcPaint, paintDC.m_hDC );

	return 0;
}

namespace {
	class comparator_rect {
	public:
		static int compare(const CRect & p_rect1,const CRect & p_rect2) {
			if (p_rect1.bottom <= p_rect2.top) return -1;
			else if (p_rect1.top >= p_rect2.bottom) return 1;
			else return 0;
		}
	};

	static int RectPointCompare(const CRect & p_item1,const int p_y) {
		if (p_item1.bottom <= p_y) return -1;
		else if (p_item1.top > p_y) return 1;
		else return 0;
	}

	class RectSearchHelper_Items {
	public:
		RectSearchHelper_Items(const CListControlImpl & p_control) : m_control(p_control) {}
		CRect operator[](t_size p_index) const {
			return m_control.GetItemRectAbs(p_index);
		}
	private:
		const CListControlImpl & m_control;
	};
	class RectSearchHelper_Groups {
	public:
		RectSearchHelper_Groups(const CListControlImpl & p_control) : m_control(p_control) {}
		CRect operator[](t_size p_index) const {
			CRect rect;
			if (!m_control.GetGroupHeaderRectAbs((int)(p_index + 1),rect)) rect.SetRectEmpty();
			return rect;
		}
	private:
		const CListControlImpl & m_control;
	};
}

bool CListControlImpl::GetItemRange(const CRect & p_rect,t_size & p_base,t_size & p_count) const {
	CRect temp(p_rect); temp.OffsetRect( GetViewOffset() );
	return GetItemRangeAbs(temp, p_base, p_count);
}



bool CListControlImpl::GetItemRangeAbsInclHeaders(const CRect & p_rect,t_size & p_base,t_size & p_count) const {
	CRect temp(p_rect);
	temp.bottom += this->GetGroupHeaderHeight();
	return GetItemRangeAbs(temp, p_base, p_count);
}

bool CListControlImpl::GetItemRangeAbs(const CRect & p_rect,t_size & p_base,t_size & p_count) const {
	if (p_rect.right < 0 || p_rect.left >= GetItemWidth()) return false;

	return pfc::binarySearch<comparator_rect>::runGroup(RectSearchHelper_Items(*this),0,GetItemCount(),p_rect,p_base,p_count);
}

void CListControlImpl::RenderRect(const CRect & p_rect,CDCHandle p_dc) {
	const CRect rectAbs = p_rect;

	t_size base, count;
	if (GetItemRangeAbs(rectAbs,base,count)) {
		for(t_size walk = 0; walk < count; ++walk) {
			CRect rcUpdate, rcItem = GetItemRectAbs(base+walk);
			if (rcUpdate.IntersectRect(rcItem,p_rect)) {
				DCStateScope dcState(p_dc);
				if (p_dc.IntersectClipRect(rcUpdate) != NULLREGION) {
					try {
						RenderItem(base+walk,rcItem,rcUpdate,p_dc);
					} catch(std::exception const & e) {
						(void) e;
						// console::complain("List Control: Item rendering failure", e);
					}
				}
			}
		}
	}

	if (pfc::binarySearch<comparator_rect>::runGroup(RectSearchHelper_Groups(*this),0,GetGroupCount(),rectAbs,base,count)) {
		for(t_size walk = 0; walk < count; ++walk) {
			CRect rcHeader, rcUpdate;
			const int id = (int)(base+walk+1);
			if (GetGroupHeaderRectAbs(id,rcHeader) && rcUpdate.IntersectRect(rcHeader,p_rect)) {
				DCStateScope dcState(p_dc);
				if (p_dc.IntersectClipRect(rcUpdate) != NULLREGION) {
					try {
						RenderGroupHeader(id,rcHeader,rcUpdate,p_dc);
					} catch(std::exception const & e) {
						(void) e;
						// console::complain("List Control: Group header rendering failure", e);
					}
				}
			}
		}
	}

	RenderOverlay(p_rect,p_dc);
}

CRect CListControlImpl::GetItemRect(t_size p_item) const {
	CRect rcItem = GetItemRectAbs(p_item);
	rcItem.OffsetRect( - GetViewOffset() );
	return rcItem;
}
bool CListControlImpl::GetGroupHeaderRect(int p_group,CRect & p_rect) const {
	if (!GetGroupHeaderRectAbs(p_group,p_rect)) return false;
	p_rect.OffsetRect( - GetViewOffset() );
	return true;
}

int CListControlImpl::GetViewAreaHeight() const {
	const t_size itemCount = GetItemCount();
	int subAreaBase = 0;
	if (itemCount > 0) {
		subAreaBase = GetItemRectAbs(itemCount - 1).bottom;
	}
	return subAreaBase;
}

CRect CListControlImpl::GetItemRectAbs(t_size p_item) const {
	CRect rcItem;
	const int itemHeight = GetItemHeight(), itemWidth = GetItemWidth(), groupHeight = GetGroupHeaderHeight(), itemGroup = GetItemGroup(p_item);
	rcItem.top = (int)p_item * itemHeight + groupHeight * itemGroup;
	rcItem.bottom = rcItem.top + itemHeight;
	rcItem.left = 0;
	rcItem.right = rcItem.left + itemWidth;
	return rcItem;
}
bool CListControlImpl::GetGroupHeaderRectAbs(int p_group,CRect & p_rect) const {
	if (p_group == 0) return false;
	t_size itemBase, itemCount;
	if (!ResolveGroupRange(p_group,itemBase,itemCount)) return false;
	const int itemHeight = GetItemHeight(), itemWidth = GetItemWidth(), groupHeight = GetGroupHeaderHeight();
	p_rect.bottom = (int) itemBase * itemHeight + groupHeight * p_group;
	p_rect.top = p_rect.bottom - groupHeight;
	p_rect.left = 0;
	p_rect.right = p_rect.left + itemWidth;
	return true;
}

CRect CListControlImpl::GetViewAreaRectAbs() const {
	return CRect(0,0,GetViewAreaWidth(),GetViewAreaHeight());
}

CRect CListControlImpl::GetViewAreaRect() const {
	CRect rc = GetViewAreaRectAbs();
	rc.OffsetRect( - GetViewOffset() );
	CRect ret; ret.IntersectRect(rc,GetClientRectHook());
	return ret;
}

t_size CListControlImpl::GetGroupCount() const {
	const t_size itemCount = GetItemCount();
	if (itemCount > 0) {
		return (t_size) GetItemGroup(itemCount-1);
	} else {
		return 0;
	}
}

void CListControlImpl::UpdateGroupHeader(int p_id) {
	CRect rect;
	if (GetGroupHeaderRect(p_id,rect)) {
		InvalidateRect(rect);
	}
}
static void AddUpdateRect(HRGN p_rgn,CRect const & p_rect) {
	CRgn temp; temp.CreateRectRgnIndirect(p_rect);
	CRgnHandle(p_rgn).CombineRgn(temp,RGN_OR);
}

void CListControlImpl::OnItemsReordered( const size_t * order, size_t count ) {
	PFC_ASSERT( count == GetItemCount() );
	ReloadItems( pfc::bit_array_order_changed(order) );
}
void CListControlImpl::UpdateItems(const pfc::bit_array & p_mask) {
	t_size base,count;
	if (GetItemRangeAbs(GetVisibleRectAbs(),base,count)) {
		const t_size max = base+count;
		CRgn updateRgn; updateRgn.CreateRectRgn(0,0,0,0);
		bool found = false;
		for(t_size walk = p_mask.find_first(true,base,max); walk < max; walk = p_mask.find_next(true,walk,max)) {
			found = true;
			AddUpdateRect(updateRgn,GetItemRect(walk));
		}
		if (found) {
			InvalidateRgn(updateRgn);
		}
	}
}

void CListControlImpl::UpdateItemsAndHeaders(const pfc::bit_array & p_mask) {
	t_size base,count;
	int groupWalk = 0;
	if (GetItemRangeAbsInclHeaders(GetVisibleRectAbs(),base,count)) {
		const t_size max = base+count;
		CRgn updateRgn; updateRgn.CreateRectRgn(0,0,0,0);
		bool found = false;
		for(t_size walk = p_mask.find_first(true,base,max); walk < max; walk = p_mask.find_next(true,walk,max)) {
			found = true;
			const int groupId = GetItemGroup(walk);
			if (groupId != groupWalk) {
				if (groupId > 0) {
					CRect rect;
					if (GetGroupHeaderRect(groupId,rect)) {
						AddUpdateRect(updateRgn,rect);
					}
				}
				groupWalk = groupId;
			}
			AddUpdateRect(updateRgn,GetItemRect(walk));
		}
		if (found) {
			InvalidateRgn(updateRgn);
		}
	}
}


CRect CListControlImpl::GetValidViewOriginArea() const {
	const CRect rcView = GetViewAreaRectAbs();
	const CRect rcClient = GetClientRectHook();
	CRect rcArea = rcView;
	rcArea.right -= pfc::min_t(rcView.Width(),rcClient.Width());
	rcArea.bottom -= pfc::min_t(rcView.Height(),rcClient.Height());
	return rcArea;
}

void CListControlImpl::OnViewAreaChanged(CPoint p_originOverride) {
	const CPoint oldViewOrigin = m_viewOrigin;
	m_viewOrigin = ClipPointToRect(p_originOverride,GetValidViewOriginArea());

	RefreshSliders();

	Invalidate();
	
	if (oldViewOrigin != m_viewOrigin) {
		OnViewOriginChange(m_viewOrigin - oldViewOrigin);
	}
}

bool CListControlImpl::ItemFromPointAbs(CPoint const & p_pt,t_size & p_item) const {
	if (p_pt.x < 0 || p_pt.x >= GetItemWidth()) return false;
	t_size dummy;
	return GetItemRangeAbs(CRect(p_pt,p_pt + CPoint(1,1)),p_item,dummy);
}

bool CListControlImpl::GroupHeaderFromPointAbs(CPoint const & p_pt,int & p_group) const {
	if (p_pt.x < 0 || p_pt.x >= GetItemWidth()) return false;
	t_size result;

	if (!pfc::binarySearch<comparator_rect>::run(RectSearchHelper_Groups(*this),0,GetGroupCount(),CRect(p_pt,p_pt + CSize(1,1)),result)) return false;


	//if (!pfc::bsearch_t(GetGroupCount(),RectSearchHelper_Groups(*this),RectCompare,CRect(p_pt,p_pt),result)) return false;
	p_group = (int) (result + 1);
	return true;
}

void CListControlImpl::OnThemeChanged() {
	m_themeCache.remove_all();
}

CTheme & CListControlImpl::themeFor(const char * what) {
	bool bNew;
	auto & ret = this->m_themeCache.find_or_add_ex( what, bNew );
	if (bNew) ret.OpenThemeData(*this, pfc::stringcvt::string_wide_from_utf8(what));
	return ret;
}

LRESULT CListControlImpl::OnCreatePassThru(UINT,WPARAM,LPARAM,BOOL& bHandled) {
	::SetWindowTheme(*this, _T("explorer"), NULL);
	OnViewAreaChanged();

	if (m_gestureAPI.IsAvailable()) {
		GESTURECONFIG config = {GID_PAN, GC_PAN_WITH_SINGLE_FINGER_VERTICALLY|GC_PAN_WITH_INERTIA, GC_PAN_WITH_SINGLE_FINGER_HORIZONTALLY | GC_PAN_WITH_GUTTER};
		m_gestureAPI.SetGestureConfig( *this, 0, 1, &config, sizeof(GESTURECONFIG));
	}

	bHandled = FALSE;
	return 0;
}
bool CListControlImpl::IsSameItemOrHeaderAbs(const CPoint & p_point1, const CPoint & p_point2) const {
	t_size item1, item2; int group1, group2;
	if (ItemFromPointAbs(p_point1, item1)) {
		if (ItemFromPointAbs(p_point2,item2)) {
			return item1 == item2;
		} else {
			return false;
		}
	}
	if (GroupHeaderFromPointAbs(p_point1, group1)) {
		if (GroupHeaderFromPointAbs(p_point2, group2)) {
			return group1 == group2;
		} else {
			return false;
		}
	}
	return false;
}

void CListControlImpl::OnSizeAsync_Trigger() {
	if (!m_sizeAsyncPending) {
		if (PostMessage(MSG_SIZE_ASYNC,0,0)) {
			m_sizeAsyncPending = true;
		} else {
			PFC_ASSERT(!"Shouldn't get here!");
			//should not happen
			ListHandleResize();
		}
	}
}

void CListControlImpl::ListHandleResize() {
	MoveViewOriginDelta(CPoint(0,0));
	m_sizeAsyncPending = false;
}

void CListControlImpl::AddGroupHeaderToUpdateRgn(HRGN p_rgn, int id) const {
	if (id > 0) {
		CRect rcHeader;
		if (GetGroupHeaderRect(id,rcHeader)) AddUpdateRect(p_rgn,rcHeader);
	}
}
void CListControlImpl::AddItemToUpdateRgn(HRGN p_rgn, t_size p_index) const {
	if (p_index < this->GetItemCount()) {
		AddUpdateRect(p_rgn,GetItemRect(p_index));
	}
}

COLORREF CListControlImpl::GetSysColorHook(int colorIndex) const {
	return GetSysColor(colorIndex);
}

LRESULT CListControlImpl::OnEraseBkgnd(UINT,WPARAM wp,LPARAM,BOOL&) {
#ifndef CListControl_ScrollWindowFix
	const CRect rcClient = GetClientRectHook();
	PaintUtils::FillRectSimple((HDC)wp,rcClient,GetColor(ui_color_background));
#endif
	return 1;
}

t_size CListControlImpl::InsertIndexFromPointEx(const CPoint & pt, bool & bInside) const {
	bInside = false;
	t_size insertMark = ~0;
	t_size itemIdx; int groupId;
	CPoint test(pt); test += GetViewOffset();
	test.x = GetItemWidth() / 2;
	if (test.y >= GetViewAreaHeight()) {
		return GetItemCount();
	} else if (ItemFromPointAbs(test,itemIdx)) {
		const CRect rc = GetItemRectAbs(itemIdx);
		if (test.y > rc.top + MulDiv(rc.Height(),2,3)) itemIdx++;
		else if (test.y >= rc.top + MulDiv(rc.Height(),1,3)) bInside = true;
		return itemIdx;
	} else if (GroupHeaderFromPointAbs(test,groupId)) {
		t_size base,count;
		if (ResolveGroupRange(groupId,base,count)) {
			return base;
		}
	}
	return ~0;
}
t_size CListControlImpl::InsertIndexFromPoint(const CPoint & pt) const {
	bool dummy; return InsertIndexFromPointEx(pt,dummy);
}

COLORREF CListControlImpl::BlendGridColor( COLORREF bk ) {
	return BlendGridColor( bk, PaintUtils::DetermineTextColor( bk ) );
}

COLORREF CListControlImpl::BlendGridColor( COLORREF bk, COLORREF tx ) {
	return PaintUtils::BlendColor(bk, tx, 10);
}

COLORREF CListControlImpl::GridColor() {
	return BlendGridColor( GetSysColorHook(colorBackground), GetSysColorHook(colorText) );
}

void CListControlImpl::RenderItemBackground(CDCHandle p_dc,const CRect & p_itemRect,size_t p_item, uint32_t bkColor) {
	switch( this->m_rowStyle ) {
	case rowStylePlaylistDelimited:
		PaintUtils::RenderItemBackground(p_dc,p_itemRect,p_item+GetItemGroup(p_item),bkColor);
		{
			auto blend = BlendGridColor(bkColor);
			CDCPen pen(p_dc, blend);
			SelectObjectScope scope(p_dc, pen);

			p_dc.MoveTo( p_itemRect.right-1, p_itemRect.top );
			p_dc.LineTo( p_itemRect.right-1, p_itemRect.bottom );
		}
		break;
	case rowStylePlaylist:
		PaintUtils::RenderItemBackground(p_dc,p_itemRect,p_item+GetItemGroup(p_item),bkColor);
		break;
	case rowStyleGrid:
		PaintUtils::FillRectSimple(p_dc, p_itemRect, bkColor );
		{
			auto blend = BlendGridColor(bkColor);
			CDCBrush brush(p_dc, blend);
			p_dc.FrameRect(&p_itemRect, brush);

		}
		break;
	case rowStyleFlat:
		PaintUtils::FillRectSimple(p_dc, p_itemRect, bkColor );
		break;
	}
}

void CListControlImpl::RenderGroupHeaderBackground(CDCHandle p_dc,const CRect & p_headerRect,int p_group) {
	const t_uint32 bkColor = GetSysColorHook(colorBackground);
	t_size index = 0;
	t_size base, count;
	if (p_group > 0 && ResolveGroupRange(p_group,base,count)) {
		index = base + (t_size) p_group - 1;
	}
	switch( this->m_rowStyle ) {
	default:
		PaintUtils::FillRectSimple( p_dc, p_headerRect, bkColor );
		break;
	case rowStylePlaylistDelimited:
	case rowStylePlaylist:
		PaintUtils::RenderItemBackground(p_dc,p_headerRect,index,bkColor);
		break;
	}
}

void CListControlImpl::RenderItem(t_size p_item,const CRect & p_itemRect,const CRect & p_updateRect,CDCHandle p_dc) {
	this->RenderItemBackground(p_dc, p_itemRect, p_item, GetSysColorHook(colorBackground) );

	DCStateScope backup(p_dc);
	p_dc.SetBkMode(TRANSPARENT);
	p_dc.SetBkColor(GetSysColorHook(colorBackground));
	p_dc.SetTextColor(GetSysColorHook(colorText));

	RenderItemText(p_item,p_itemRect,p_updateRect,p_dc, true);
}

void CListControlImpl::RenderGroupHeader(int p_group,const CRect & p_headerRect,const CRect & p_updateRect,CDCHandle p_dc) {
	this->RenderGroupHeaderBackground(p_dc, p_headerRect, p_group );

	DCStateScope backup(p_dc);
	p_dc.SetBkMode(TRANSPARENT);
	p_dc.SetBkColor(GetSysColorHook(colorBackground));
	p_dc.SetTextColor(GetSysColorHook(colorHighlight));

	RenderGroupHeaderText(p_group,p_headerRect,p_updateRect,p_dc);
}


CListControlFontOps::CListControlFontOps() : m_font((HFONT)::GetStockObject(DEFAULT_GUI_FONT)), m_itemHeight(), m_groupHeaderHeight() {
	UpdateGroupHeaderFont();
	CalculateHeights();
}

void CListControlFontOps::UpdateGroupHeaderFont() {
	try {
		m_groupHeaderFont = NULL;
		LOGFONT lf = {};
		WIN32_OP_D( m_font.GetLogFont(lf) );
		lf.lfHeight = pfc::rint32( (double) lf.lfHeight * GroupHeaderFontScale() );
		lf.lfWeight = GroupHeaderFontWeight(lf.lfWeight);
		WIN32_OP_D( m_groupHeaderFont.CreateFontIndirect(&lf) != NULL );
	} catch(std::exception const & e) {
		(void) e;
		// console::print(e.what());
		m_groupHeaderFont = (HFONT)::GetStockObject(DEFAULT_GUI_FONT);
	}
}

void CListControlFontOps::CalculateHeights() {
	const t_uint32 spacing = MulDiv(4, m_dpi.cy, 96);
	m_itemHeight = GetFontHeight( m_font ) + spacing;
	m_groupHeaderHeight = GetFontHeight( m_groupHeaderFont ) + spacing;
}

void CListControlFontOps::SetFont(HFONT font,bool bUpdateView) {
	m_font = font;
	UpdateGroupHeaderFont(); CalculateHeights();
	OnSetFont(bUpdateView);
	if (bUpdateView && m_hWnd != NULL) OnViewAreaChanged();
	
}

LRESULT CListControlFontOps::OnSetFont(UINT,WPARAM wp,LPARAM,BOOL&) {
	SetFont((HFONT)wp);
	return 0;
}

LRESULT CListControlFontOps::OnGetFont(UINT,WPARAM,LPARAM,BOOL&) {
	return (LRESULT)(HFONT)m_font;
}

void CListControlImpl::SetCaptureEx(CaptureProc_t proc) {
	this->m_captureProc = proc; SetCapture();
}

LRESULT CListControlImpl::MousePassThru(UINT msg, WPARAM wp, LPARAM lp) {
	auto p = m_captureProc; // create local ref in case something in mid-captureproc clears it
	if ( p ) {
		CPoint pt(lp);
		if (!p(msg, (DWORD) wp, pt ) ) {
			ReleaseCapture();
			m_captureProc = nullptr;
		}
		return 0;
	}

	SetMsgHandled(FALSE);
	return 0;
}

LRESULT CListControlImpl::OnGetDlgCode(UINT, WPARAM wp, LPARAM) {
	switch(wp) {
	case VK_RETURN:
		return m_dlgWantEnter ? DLGC_WANTMESSAGE : 0;
	default:
		SetMsgHandled(FALSE);
		return 0;
	}
}

void CListControlImpl::CreateInDialog(CWindow wndDialog, UINT replaceControlID ) {
	CWindow lstReplace = wndDialog.GetDlgItem(replaceControlID);
	PFC_ASSERT( lstReplace != NULL );
	auto status = lstReplace.SendMessage(WM_GETDLGCODE, VK_RETURN );
	m_dlgWantEnter = (status & DLGC_WANTMESSAGE);
	CRect rc;
	CWindow wndPrev = wndDialog.GetNextDlgTabItem(lstReplace, TRUE);
	WIN32_OP_D( lstReplace.GetWindowRect(&rc) );
	WIN32_OP_D( wndDialog.ScreenToClient(rc) );
	WIN32_OP_D( lstReplace.DestroyWindow() );
	WIN32_OP_D( this->Create(wndDialog, &rc, 0, 0, WS_EX_STATICEDGE, replaceControlID) );
	if (wndPrev != NULL ) this->SetWindowPos(wndPrev, 0,0,0,0, SWP_NOSIZE | SWP_NOMOVE );
	this->SetFont(wndDialog.GetFont());
}


void CListControlImpl::defer(std::function<void() > f) {
	m_deferred.push_back( f );
	if (!m_defferredMsgPending) {
		if ( PostMessage(MSG_EXEC_DEFERRED) ) m_defferredMsgPending = true;
	}
}

LRESULT CListControlImpl::OnExecDeferred(UINT, WPARAM, LPARAM) {
	
	for ( ;; ) { 
		auto i = m_deferred.begin();
		if ( i == m_deferred.end() ) break;
		(*i)();
		m_deferred.erase(i);
	}

	m_defferredMsgPending = false;
	return 0;
}

// ========================================================================================
// Mouse wheel vs drag&drop hacks
// Install MouseHookProc for the duration of DoDragDrop and handle the input from there
// ========================================================================================
static HHOOK g_hook = NULL;
static CListControlImpl * g_dragDropInstance = nullptr;
LRESULT CALLBACK CListControlImpl::MouseHookProc(int nCode, WPARAM wParam, LPARAM lParam) {
	if (nCode == HC_ACTION && g_dragDropInstance != nullptr) {
		switch (wParam) {
		case WM_MOUSEWHEEL:
		case WM_MOUSEHWHEEL:
			g_dragDropInstance->MouseWheelFromHook((UINT)wParam, lParam);
			break;
		}
	}
	return CallNextHookEx(g_hook, nCode, wParam, lParam);
}

bool CListControlImpl::MouseWheelFromHook(UINT msg, LPARAM data) {
	MOUSEHOOKSTRUCTEX const * mhs = reinterpret_cast<MOUSEHOOKSTRUCTEX const *> ( data ); 
	if ( ::WindowFromPoint(mhs->pt) != m_hWnd ) return false;
	LRESULT dummyResult = 0;
	WPARAM wp = mhs->mouseData;
	LPARAM lp = MAKELPARAM( mhs->pt.x, mhs->pt.y );
	// If we get here, m_suppressMouseWheel should be true per our DoDragDrop()
	pfc::vartoggle_t<bool> scope(m_suppressMouseWheel, false);
	this->ProcessWindowMessage( m_hWnd, msg, wp, lp, dummyResult );
	return true;
}

HRESULT CListControlImpl::DoDragDrop(LPDATAOBJECT pDataObj, LPDROPSOURCE pDropSource, DWORD dwOKEffects, LPDWORD pdwEffect) {
	HRESULT ret = E_FAIL;
	// Should not get here with non null g_dragDropInstance - means we have a recursive call
	PFC_ASSERT(g_dragDropInstance == nullptr);
	if ( g_dragDropInstance == nullptr ) {
		// futureproofing: kill mouse wheel message processing if we get them delivered the regular way while this is in progress
		pfc::vartoggle_t<bool> scope(m_suppressMouseWheel, true);
		g_dragDropInstance = this;
		g_hook = SetWindowsHookEx(WH_MOUSE, MouseHookProc, NULL, GetCurrentThreadId());
		try {
			ret = ::DoDragDrop(pDataObj, pDropSource, dwOKEffects, pdwEffect);
		} catch (...) {
		}
		g_dragDropInstance = nullptr;
		UnhookWindowsHookEx(pfc::replace_null_t(g_hook));
	}
	return ret;
}

void CListControlImpl::OnKillFocus(CWindow) {
	if (m_captureProc) {
		ReleaseCapture();
		m_captureProc = nullptr;
	}
	SetMsgHandled(FALSE);
}

void CListControlImpl::OnWindowPosChanged(LPWINDOWPOS arg) {
	if ( arg->flags & SWP_HIDEWINDOW ) {
		if (m_captureProc) {
			ReleaseCapture();
			m_captureProc = nullptr;
		}
	}
	SetMsgHandled(FALSE);
}

void CListControlHeaderImpl::RenderItemBackground(CDCHandle p_dc,const CRect & p_itemRect,size_t item, uint32_t bkColor) {
	if ( ! this->DelimitColumns() ) {
		__super::RenderItemBackground(p_dc, p_itemRect, item, bkColor);
	} else {
		auto cnt = this->GetColumnCount();
		uint32_t x = 0;
		for( size_t walk = 0; walk < cnt; ) {
			auto span = this->GetSubItemSpan( item, walk );
			PFC_ASSERT( span > 0 );
			uint32_t width = 0;
			for( size_t walk2 = 0; walk2 < span; ++ walk2 ) {
				width += this->GetSubItemWidth( walk + walk2 );
			}
			CRect rc = p_itemRect;
			rc.left = x;
			x += width;
			rc.right = x;
			__super::RenderItemBackground(p_dc, rc, item, bkColor);
			walk += span;
		}
	}
}
