#pragma once

#include <memory>

#pragma comment(lib, "oleacc.lib")

#include "CListControl-Cell.h"
#include "CListControlWithSelection.h"

//! Internal class interfacing with Windows accessibility APIs. \n
//! This class is not tied to any specific control and requires most of its methods to be overridden. \n
//! With CListControl you want to use CListControlAccImpl<> template instead of using CListAccessible directly.
class CListAccessible {
public:
	void AccInitialize(CWindow wnd);
	void AccCleanup();
	LRESULT AccGetObject(WPARAM wp,LPARAM lp);
	CWindow AccGetWnd() const {return m_wnd;}
	virtual size_t AccGetItemCount() const {return 0;}
	virtual LONG AccGetItemRole(size_t index) const { (void)index; return ROLE_SYSTEM_LISTITEM; }
	virtual void AccGetItemName(size_t index, pfc::string_base& out) const { (void)index; out = ""; }
	virtual void AccGetName(pfc::string_base & out) const;
	virtual size_t AccGetFocusItem() const {return SIZE_MAX;}
	virtual bool AccIsItemSelected(size_t index) const { (void)index; return false; }
	virtual bool AccIsItemChecked(size_t index) const { (void)index; return false; }
	virtual bool AccIsItemVisible(size_t index) const { (void)index; return false; }
	virtual bool AccGetItemDefaultAction(pfc::string_base& out) const { (void)out; return false; }
	virtual bool AccExecuteItemDefaultAction(size_t index) { (void)index; return false; }
	virtual void AccSetSelection(pfc::bit_array const& affected, pfc::bit_array const& state) { (void)affected; (void)state; }
	virtual void AccSetFocusItem(size_t index) { (void)index; }
	virtual bool AccGetItemRect(size_t index, CRect& out) const { (void)index; (void)out; return false; }
	virtual bool AccGetItemDescription(size_t index, pfc::string_base& out) const { (void)index; (void)out; return false; }
	virtual size_t AccItemHitTest(CPoint const& pt) const { (void)pt; return SIZE_MAX; }
	
	virtual DWORD AccGetOtherRole(size_t index) { (void)index; return 0; }
	virtual size_t AccGetOtherCount() const {return 0;}
	virtual void AccGetOtherName(size_t index, pfc::string_base& out) const { (void)index; out = ""; }
	virtual size_t AccGetFocusOther() const {return SIZE_MAX;}
	virtual void AccSetFocusOther(size_t index) { (void)index; }
	virtual bool AccIsOtherVisible(size_t index) const { (void)index; return false; }
	virtual bool AccGetOtherDescription(size_t index, pfc::string_base& out) const { (void)index; (void)out; return false; }
	virtual size_t AccOtherHitTest(CPoint const& pt) const { (void)pt; return SIZE_MAX; }
	virtual bool AccIsOtherFocusable(size_t index) const { (void)index; return false; }
	virtual bool AccGetOtherDefaultAction(size_t index, pfc::string_base& out) const { (void)index; (void)out; return false; }
	virtual bool AccExecuteOtherDefaultAction(size_t index) { (void)index; return false; }
	virtual bool AccGetOtherRect(size_t index, CRect& out) const { (void)index; (void)out; return false; }
	virtual CWindow AccGetOtherChildWnd(size_t index) const { (void)index; return NULL; }

	void AccItemNamesChanged( pfc::bit_array const & mask);
	void AccReloadItems(pfc::bit_array const & mask );
	void AccRefreshItems(pfc::bit_array const & mask, UINT what);
	void AccStateChange(pfc::bit_array const & mask);
	void AccStateChange(size_t index);
	void AccItemLayoutChanged();
	void AccFocusItemChanged(size_t index);
	void AccFocusOtherChanged(size_t index);
	void AccSelectionChanged(const pfc::bit_array & affected, const pfc::bit_array & status);
	void AccLocationChange();
protected:
	~CListAccessible() { * m_killSwitch = true; }
private:
	CWindow m_wnd;
	std::shared_ptr<bool> m_killSwitch = std::make_shared<bool>();
	pfc::com_ptr_t<IAccessible> m_interface;
};

//! Basic wrapper implementing CListAccessible methods on top of CListControl. Leaves many methods to be overridden by calle
template<typename TBaseClass> class CListControlAccImpl : public TBaseClass, protected CListAccessible {
public:
	template<typename ... arg_t> CListControlAccImpl( arg_t && ... arg ) : TBaseClass(std::forward<arg_t>(arg) ... ) {}

	BEGIN_MSG_MAP_EX(CListControlAccImpl)
		MESSAGE_HANDLER(WM_GETOBJECT,OnGetObject)
		MESSAGE_HANDLER(WM_DESTROY,OnDestroyPassThru)
		MESSAGE_HANDLER(WM_CREATE,OnCreatePassThru)
		CHAIN_MSG_MAP(TBaseClass)
	END_MSG_MAP()
public:
	void ReloadData() {
		TBaseClass::ReloadData();
		AccItemLayoutChanged();
	}
	void ReloadItems(pfc::bit_array const & mask) {
		TBaseClass::ReloadItems(mask);
		AccReloadItems(mask);
	}
protected:
	size_t AccGetItemCount() const {return this->GetItemCount();}
	size_t AccGetFocusItem() const {return this->GetFocusItem();}
	bool AccIsItemSelected(size_t index) const {return this->IsItemSelected(index);}
	bool AccIsItemVisible(size_t index) const {
		if (index >= AccGetItemCount()) return false;
		return this->IsRectVisible(this->GetItemRect(index));
	}
	bool AccExecuteItemDefaultAction(size_t index) {if (index < AccGetItemCount()) {this->ExecuteDefaultAction(index);return true;} else return false;}
	bool AccGetItemRect(size_t index, CRect & out) const {
		if (index >= AccGetItemCount()) return false;
		out = this->GetItemRect(index);
		return true;
	}
	size_t AccItemHitTest(CPoint const & pt) const {
		size_t item;
		if (!this->ItemFromPoint(pt,item)) return ~0;
		return item;
	}

	void OnViewOriginChange(CPoint p_delta) override {
		TBaseClass::OnViewOriginChange(p_delta);
		AccLocationChange();
	}

	/* overrideme, optional
	void AccGetName(pfc::string_base & out) const;
	bool AccGetItemDefaultAction(pfc::string_base & out) const;
	*/

	LONG AccRoleAt(size_t idx, size_t sub) const {
		auto cell = this->GetCellType(idx, sub);
		if (cell == nullptr) return 0;
		return cell->AccRole();
	}
	bool isCellText(size_t idx, size_t sub) const {
		switch (AccRoleAt(idx, sub)) {
		case ROLE_SYSTEM_TEXT:
		case ROLE_SYSTEM_STATICTEXT:
		case ROLE_SYSTEM_LISTITEM:
			return true;
		default:
			return false;
		}
	}
	bool useCellForDescription(size_t idx, size_t sub) const {
		return sub > 0 && isCellText(idx, sub);
	}
	bool AccGetItemDescription(size_t index, pfc::string_base& out) const {
		pfc::string_formatter ret, temp, temp2;
		const size_t total = this->GetColumnCount();
		for (size_t walk = 0; walk < total; ) {
			if (useCellForDescription(index, walk) && this->GetSubItemText(index, walk, temp) && temp.length() > 0) {
				if (ret.length() > 0) ret << "; ";
				this->GetColumnText(walk, temp2);
				if (temp2.length() > 0) ret << temp2 << ": ";
				ret << temp;
			}
			size_t d = this->GetSubItemSpan(index, walk);
			if (d < 1) d = 1;
			walk += d;
		}
		bool rv = (ret.length() > 0);
		if (rv) out = ret;
		return ret;
	}

	void AccGetItemNameAlt(size_t index, pfc::string_base & out) const {
		pfc::string_formatter ret, temp;
		const size_t total = this->GetColumnCount();
		for (size_t walk = 0; walk < total; ) {
			if (this->isCellText(index, walk) && this->GetSubItemText(index, walk, temp) && temp.length() > 0) {
				if (ret.length() > 0) ret << "; ";
				ret << temp;
			}
			size_t d = this->GetSubItemSpan(index, walk);
			if (d < 1) d = 1;
			walk += d;
		}
		out = ret;
	}

	// Item name by default taken from column 0, override this if you supply another
	void AccGetItemName(size_t index, pfc::string_base & out) const {
		this->GetSubItemText(index, 0, out);
	}

	void AccSetSelection(pfc::bit_array const & affected, pfc::bit_array const & state) override {
		this->SetSelection(affected, state); 
}
	void AccSetFocusItem(size_t index) override {
		this->SetFocusItem(index); 
	}
	
	void OnFocusChangedGroup2(size_t baseItem) override {
		TBaseClass::OnFocusChangedGroup2(baseItem);
		AccFocusOtherChanged((size_t)baseItem + 1);
	}
	void OnFocusChanged(size_t o, size_t f) override {
		TBaseClass::OnFocusChanged(o, f);
		AccFocusItemChanged(f);
	}

	void OnSelectionChanged(pfc::bit_array const & affected, pfc::bit_array const & status) override {
		TBaseClass::OnSelectionChanged(affected, status);
		AccSelectionChanged(affected, status);
	}

	CWindow AccGetOtherChildWnd(size_t index) const {return index == 0 ? CWindow(this->GetHeaderCtrl()) : CWindow(NULL) ;}
	virtual DWORD AccGetOtherRole(size_t index) {return index > 0 ? ROLE_SYSTEM_GROUPING : ROLE_SYSTEM_WINDOW;}//FIXME?
	virtual bool AccGetOtherDescription(size_t index, pfc::string_base & out) const {return false;}//FIXME??

	size_t AccGetOtherCount() const {return 1 + this->GetItemCount();}
	void AccGetOtherName(size_t index, pfc::string_base & out) const override {
		if (index == 0) out = "Columns Header";
		else if (!this->GetGroupHeaderText2(index-1, out)) out = "";
	}
	size_t AccGetFocusOther() const override {
		size_t focus = this->GetGroupFocus2();
		if (focus != SIZE_MAX) return focus + 1;
		else return SIZE_MAX;
	}
	void AccSetFocusOther(size_t index) override {
		if (index > 0) {
			this->SetGroupFocusByItem(index-1);
		}
	}
	bool AccIsOtherVisible(size_t index) const override {
		if (index == 0) return true;
		CRect rc;
		if (!this->GetGroupHeaderRect2(index-1,rc)) return false;
		return IsRectVisible(rc);
	}
	
	size_t AccOtherHitTest(CPoint const & pt) const {
		{
			CPoint s(pt);
			if (this->ClientToScreen(&s)) {
				CRect rc;
				auto hdr = this->GetHeaderCtrl();
				if (hdr != NULL && hdr.GetWindowRect(rc)) {
					if (rc.PtInRect(s)) {
						return 0;
					}
				}
			}
		}
		size_t groupBase;
		if (this->GroupHeaderFromPoint2(pt, groupBase)) return groupBase+1;
		return SIZE_MAX;
	}
	bool AccIsOtherFocusable(size_t index) const {return index > 0;}
	bool AccGetOtherDefaultAction(size_t index, pfc::string_base & out) const {return false;}
	bool AccExecuteOtherDefaultAction(size_t index) {return false;}
	bool AccGetOtherRect(size_t index, CRect & out) const {
		if (index == 0) {
			CRect rc, client;

			auto hdr = this->GetHeaderCtrl();
			if ( hdr == NULL ) return false;
			if (!hdr.GetWindowRect(rc)) return false;
			if (!this->ScreenToClient(rc)) return false;
			if (!this->GetClientRect(client)) return false;
			return !! out.IntersectRect(rc, client);
		} else {
			return this->GetGroupHeaderRect2(index-1, out);
		}
	}
	LONG AccGetItemRole( size_t index ) const override {
		auto type = this->GetCellType( index, 0 );
		if ( type != nullptr ) {
			return type->AccRole();
		}
		return ROLE_SYSTEM_LISTITEM;
	}
	void SetCellCheckState(size_t item, size_t subItem, bool value) override {
		__super::SetCellCheckState(item, subItem, value);
		this->AccStateChange(item);
	}
	bool AccIsItemChecked( size_t index ) const override {
		auto type = this->GetCellType( index, 0 );
		if ( type != nullptr && type->IsToggle() ) {
			return this->GetCellCheckState( index, 0 );
		}
		return false;
	}
private:
	bool IsRectVisible(CRect const & rc) const {
		return !!CRect().IntersectRect(this->GetClientRectHook(),rc);
	}
	LRESULT OnCreatePassThru(UINT,WPARAM,LPARAM,BOOL& bHandled) {
		bHandled = FALSE;
		try { AccInitialize(*this); } catch(...) {AccCleanup();}
		return 0;
	}
	LRESULT OnDestroyPassThru(UINT,WPARAM,LPARAM,BOOL& bHandled) {
		bHandled = FALSE;
		AccCleanup();
		return 0;
	}
	LRESULT OnGetObject(UINT,WPARAM wp, LPARAM lp, BOOL & ) {
		return this->AccGetObject(wp,lp);
	}
	
};

class CListControlWithSelectionImpl;
typedef CListControlAccImpl< CListControlWithSelectionImpl > CListControlWithSelectionAccImpl;
