// Copyright 2017, NetEase (Hangzhou) Network Co., Ltd. All rights reserved.
//
// zqw
// 2015/7/22
//
// VirtualListBox class
#include "stdafx.h"
#include "VirtualListBox.h"

namespace ui 
{

CSize VirtualLayout::ArrangeChild(const std::vector<Control*>& items, UiRect rc) 
{
    CSize sz(rc.GetWidth(), 0);

    VirtualListBox *pList = dynamic_cast<VirtualListBox*>(m_pOwner);
    ASSERT(pList);

    if (pList->UseDefaultLayout()) {
        sz = VLayout::ArrangeChild(items, rc);
    } else {
        sz.cy = pList->CalcElementsHeight(-1);
        pList->ReArrangeChild(false);
    }

    return sz;
}

VirtualListBox::VirtualListBox(ui::Layout* pLayout) : 
	ui::ListBox(pLayout),
	m_pDataProvider(nullptr),
	m_nElementHeight(0),
	m_nMaxItemCount(0),
	m_nOldYScrollPos(0),
	m_bArrangedOnce(false),
	m_bForceArrange(false)
{

}

void VirtualListBox::SetDataProvider(VirtualListInterface *pProvider)
{
	ASSERT(pProvider);
	m_pDataProvider = pProvider;
}

void VirtualListBox::SetElementHeight(int nHeight) 
{
    m_nElementHeight = nHeight;
}

void VirtualListBox::InitElement(int nMaxItemCount)
{
	ASSERT(m_pDataProvider);
	ASSERT(m_nElementHeight);
    m_nMaxItemCount = nMaxItemCount;

    int nCount = GetElementCount();
	if (nCount > nMaxItemCount)
		nCount = nMaxItemCount;

	for (int i = 0; i < nCount; i++) {
        Control *pControl = CreateElement();
		this->Add(pControl);
		FillElement(pControl, i);
    }
}

void VirtualListBox::Refresh() 
{
    int nElementCount = GetElementCount();
    int nItemCount = GetCount();

    if (nItemCount > nElementCount) {
        int n = nItemCount - nElementCount;
        for (int i = 0; i < n; i++)
            this->RemoveAt(0);
    } 
	else if (nItemCount < nElementCount) {
        int n = 0;
        if (nElementCount <= m_nMaxItemCount) {
            n = nElementCount - nItemCount;
        } 
		else {
            n = m_nMaxItemCount - nItemCount;
        }

        for (int i = 0; i < n; i++) {
            Control *pControl = CreateElement();
			this->Add(pControl);
        }
    }

    if (UseDefaultLayout()) {
        // 刚从虚拟列表转换到普通模式时，存在布局错误的情况（虚拟列表滚动条接近底部，
        // 然后数据减少，变成普通模式）
        if (nItemCount == m_nMaxItemCount) {
            this->GetLayout()->ArrangeChild(m_items, m_rcItem);
        }

        for (int i = 0; i < (int)m_items.size(); i++)
            FillElement(m_items[i], i);
    } 
	else {
        ASSERT(nElementCount);
        if (nElementCount <= 0)
            return;

        ReArrangeChild(true);
        Arrange();
    }
}

void VirtualListBox::RemoveAll() 
{
    __super::RemoveAll();

    if (m_pVerticalScrollBar)
        m_pVerticalScrollBar->SetScrollPos(0);

    m_nOldYScrollPos = 0;
    m_bArrangedOnce = false;
    m_bForceArrange = false;
}

void VirtualListBox::SetForceArrange(bool bForce)
{
	m_bForceArrange = bForce;
}

void VirtualListBox::GetDisplayCollection(std::vector<int>& collection)
{
	collection.clear();

	if (GetCount() == 0)
		return;

	UiRect rcThis = this->GetPos(false);

	int min = GetScrollPos().cy / m_nElementHeight;
	int max = min + (rcThis.GetHeight() / m_nElementHeight);
	int nCount = GetElementCount();
	if (max >= nCount)
		max = nCount - 1;

	for (auto i = min; i <= max; i++)
		collection.push_back(i);
}

void VirtualListBox::EnsureVisible(int iIndex, bool bToTop) 
{
	if (iIndex < 0 || iIndex >= GetElementCount())
		return;

	int nPos = m_pVerticalScrollBar->GetScrollPos();
	int nTopIndex = nPos / m_nElementHeight;
	int nNewPos = 0;

	if (bToTop) {
		nNewPos = CalcElementsHeight(iIndex);
		if (nNewPos >= m_pVerticalScrollBar->GetScrollRange())
			return;
	}
	else {
		if (IsElementDisplay(iIndex))
			return;

		if (iIndex > nTopIndex) {
			// 向下
			int height = CalcElementsHeight(iIndex + 1);
			nNewPos = height - m_rcItem.GetHeight();
		}
		else {
			// 向上
			nNewPos = CalcElementsHeight(iIndex);
		}
	}
	CSize sz(0, nNewPos);
	SetScrollPos(sz);
}

void VirtualListBox::ReArrangeChild(bool bForce)
{
	ScrollDirection direction = kScrollUp;
	if (!bForce && !m_bForceArrange) {
		if (!NeedReArrange(direction))
			return;
	}
	//max( pos ) = range
	//int range = m_pVerticalScrollBar->GetScrollRange();
	//int pos = m_pVerticalScrollBar->GetScrollPos();

	int nElementCount = GetElementCount();

	int nTopIndexBottom = 0;
	int nTopIndex = GetTopElementIndex(nTopIndexBottom);

	if (direction == kScrollDown) {
		// 向下滚动
		ui::UiRect rcItem = m_rcItem;
		rcItem.bottom = rcItem.top + nTopIndexBottom;

		for (int i = 0; i < (int)m_items.size(); i++) {
			rcItem.top = rcItem.bottom;
			rcItem.bottom = rcItem.top + m_nElementHeight;

			m_items[i]->SetPos(rcItem);

			int nElementIndex = nTopIndex + i;
			if (nElementIndex < nElementCount)
				FillElement(m_items[i], nElementIndex);
		}
	}
	else {
		// 向上滚动
		int nDisplayCount = m_rcItem.GetHeight() / m_nElementHeight + 1;
		int nHideCount = (int)m_items.size() - nDisplayCount;

		// 上半部分
		UiRect rcItem = m_rcItem;
		rcItem.top = m_rcItem.top + nTopIndexBottom;
		for (int i = nHideCount - 1; i >= 0; i--) {
			rcItem.bottom = rcItem.top;
			rcItem.top = rcItem.bottom - m_nElementHeight;

			m_items[i]->SetPos(rcItem);

			int nElementIndex = nTopIndex - (nHideCount - i);
			if (nElementIndex >= 0)
				FillElement(m_items[i], nElementIndex);
		}

		// 下半部分
		rcItem = m_rcItem;
		rcItem.bottom = m_rcItem.top + nTopIndexBottom;
		for (int i = nHideCount; i < (int)m_items.size(); i++) {
			rcItem.top = rcItem.bottom;
			rcItem.bottom = rcItem.top + m_nElementHeight;

			m_items[i]->SetPos(rcItem);

			int nElementIndex = nTopIndex + (i - nHideCount);
			if (nElementIndex < nElementCount)
				FillElement(m_items[i], nElementIndex);
		}
	}
}

void VirtualListBox::AddElement(int iIndex) 
{
    int nCount = GetElementCount();
    if (nCount <= m_nMaxItemCount) {
        Control *pControl = CreateElement();
		this->AddAt(pControl, iIndex);
		FillElement(pControl, iIndex);
    } else {
        ReArrangeChild(true);
        Arrange();
    }
}

void VirtualListBox::RemoveElement(int iIndex)
{
    int nCount = GetElementCount();
    if (nCount < m_nMaxItemCount) {
        this->RemoveAt(iIndex);
    } else {
        ReArrangeChild(true);
        Arrange();
    }
}

void VirtualListBox::SetScrollPos(ui::CSize szPos) 
{
    m_nOldYScrollPos = m_pVerticalScrollBar->GetScrollPos();
    ListBox::SetScrollPos(szPos);

    if (UseDefaultLayout())
        return;

    ReArrangeChild(false);
}

void VirtualListBox::HandleMessage(ui::EventArgs& event) {
    if (!IsMouseEnabled() && event.Type > ui::kEventMouseBegin && event.Type < ui::kEventMouseEnd) {
        if (m_pParent != NULL)
            m_pParent->HandleMessageTemplate(event);
        else
            ui::ScrollableBox::HandleMessage(event);
        return;
    }

    switch (event.Type) {
    case kEventKeyDown: {
        switch (event.chKey) {
        case VK_UP: {
            OnKeyDown(VK_UP);
            return;
        }
        case VK_DOWN: {
            OnKeyDown(VK_DOWN);
            return;
        }
        case VK_HOME:
            SetScrollPosY(0);
            return;
        case VK_END: {
            int range = m_pVerticalScrollBar->GetScrollRange();
            SetScrollPosY(range);
            return;
        }
        }
    }
    case kEventKeyUp: {
        switch (event.chKey) {
        case VK_UP: {
            OnKeyUp(VK_UP);
            return;
        }
        case VK_DOWN: {
            OnKeyUp(VK_DOWN);
            return;
        }
        }
    }
    }

    __super::HandleMessage(event);
}

void VirtualListBox::SetPos(UiRect rc) 
{
    bool bChange = false;
    if (!m_rcItem.Equal(rc))
        bChange = true;

    ListBox::SetPos(rc);

    if (bChange) {
        if (UseDefaultLayout())
            return;

        ReArrangeChild(true);
    }
}

ui::Control* VirtualListBox::CreateElement()
{
	if (m_pDataProvider)
		return m_pDataProvider->CreateElement();

	return nullptr;
}

void VirtualListBox::FillElement(Control *pControl, int iIndex)
{
	if (m_pDataProvider)
		m_pDataProvider->FillElement(pControl, iIndex);
}

int VirtualListBox::GetElementCount()
{
	if (m_pDataProvider)
		return m_pDataProvider->GetElementtCount();

	return 0;
}

bool VirtualListBox::UseDefaultLayout()
{
	return GetElementCount() <= GetCount();
}

int VirtualListBox::CalcElementsHeight(int nCount) 
{
    if (nCount < 0)
        nCount = GetElementCount();

    return nCount * m_nElementHeight;
}

int VirtualListBox::GetTopElementIndex(int &bottom)
{
    int nPos = m_pVerticalScrollBar->GetScrollPos();

    int iIndex = nPos / m_nElementHeight;
    bottom = iIndex * m_nElementHeight;

    return iIndex;
}

bool VirtualListBox::IsElementDisplay(int iIndex) 
{
    if (iIndex < 0)
        return false;

    int nPos = m_pVerticalScrollBar->GetScrollPos();
    int nElementPos = CalcElementsHeight(iIndex);
    if (nElementPos >= nPos) {
        int nHeight = this->GetHeight();
        if (nElementPos + m_nElementHeight <= nPos + nHeight)
            return true;
    }

    return false;
}

bool VirtualListBox::NeedReArrange(ScrollDirection &direction) 
{
    direction = kScrollUp;
    if (!m_bArrangedOnce) {
        m_bArrangedOnce = true;
        return true;
    }

    int nCount = GetCount();
    if (nCount <= 0)
        return false;

    if (GetElementCount() <= nCount)
        return false;

    UiRect rcThis = this->GetPos();
    if (rcThis.GetWidth() <= 0)
        return false;

	int nPos = m_pVerticalScrollBar->GetScrollPos();
    UiRect rcItem;

    // 补救措施
    // 情况一：通讯录列表，一开始不可见，切换后可见，如果提前布局，
    // 则Element宽度为0，因此，必须重新布局；
    // 情况二：写信窗口联系人列表，列表宽度会不断变化，因此，需要在宽度变化后
    // 重新布局，否则，导致最终Element布局时的宽度不正确
    rcItem = m_items[0]->GetPos();
    // modified by zqw, 2016/10/12
    // 针对情况二，解决方法是，列表宽度变化 或者 拖动写信窗口右侧，列表position改变，
    // 此时，在SetPos中强制重新布局
    //if (u.GetWidth() != rect.GetWidth()) {
    //    return true;
    //}

    if (nPos >= m_nOldYScrollPos) {
        // 下
        rcItem = m_items[nCount - 1]->GetPos();
        int nSub = (rcItem.bottom - rcThis.top) - (nPos + rcThis.GetHeight());
		if (nSub < 0) {
            direction = kScrollDown;
            return true;
        }
    } else {
        // 上
        rcItem = m_items[0]->GetPos();
        if (nPos < (rcItem.top - rcThis.top)) {
            direction = kScrollUp;
            return true;
        }
    }
 
    return false;
}

}