/*
 *    AutoComplete interfaces implementation.
 *
 *    Copyright 2004    Maxime Bellengé <maxime.bellenge@laposte.net>
 *    Copyright 2009  Andrew Hill
 *    Copyright 2020-2021 Katayama Hirofumi MZ <katayama.hirofumi.mz@gmail.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 */

#include "precomp.h"
#include <imm.h> // For IMN_OPENCANDIDATE
#include <process.h> // _beginthreadex

/*
  TODO:
  - implement ACO_SEARCH style
  - implement ACO_FILTERPREFIXES style
  - implement ACO_RTLREADING style
 */

#define CX_LIST 30160 // width of m_hwndList (very wide but alright)
#define CY_LIST 288 // maximum height of drop-down window
#define CY_ITEM 18 // default height of listview item
#define MAX_ITEM_COUNT 1000 // the maximum number of items
#define WATCH_TIMER_ID 0xFEEDBEEF // timer ID to watch m_rcEdit
#define WATCH_INTERVAL 300 // in milliseconds

static HHOOK s_hMouseHook = NULL; // hook handle
static HWND s_hWatchWnd = NULL; // the window handle to watch

struct PREFIX_INFO
{
    LPCWSTR psz;
    INT cch;
};
static const PREFIX_INFO s_prefixes[] =
{
    { L"https://", 8 },
    { L"http://www.", 11 },
    { L"http://", 7 },
    { L"www.", 4 },
};

static BOOL DropPrefix(const CStringW& str, CStringW& strBody)
{
    for (size_t iPrefix = 0; iPrefix < _countof(s_prefixes); ++iPrefix)
    {
        LPCWSTR psz = s_prefixes[iPrefix].psz;
        INT cch = s_prefixes[iPrefix].cch;
        if (::StrCmpNIW(str, psz, cch) == 0)
        {
            strBody = str.Mid(cch);
            return TRUE;
        }
    }
    strBody = str;
    return FALSE;
}

static BOOL DoesMatch(const CStringW& strTarget, const CStringW& strText)
{
    CStringW strBody;
    if (DropPrefix(strTarget, strBody))
    {
        if (::StrCmpNIW(strBody, strText, strText.GetLength()) == 0)
            return TRUE;
    }
    else if (::StrCmpNIW(strTarget, strText, strText.GetLength()) == 0)
    {
        return TRUE;
    }
    return FALSE;
}

// mouse hook procedure to watch the mouse click
// https://docs.microsoft.com/en-us/previous-versions/windows/desktop/legacy/ms644988(v=vs.85)
static LRESULT CALLBACK MouseProc(INT nCode, WPARAM wParam, LPARAM lParam)
{
    if (s_hMouseHook == NULL)
        return 0; // do default
    // if the user clicked the outside of s_hWatchWnd, then hide the drop-down window
    if (nCode == HC_ACTION && // an action?
        s_hWatchWnd && ::IsWindow(s_hWatchWnd) && // s_hWatchWnd is valid?
        ::GetCapture() == NULL) // no capture? (dragging something?)
    {
        RECT rc;
        MOUSEHOOKSTRUCT *pMouseHook = reinterpret_cast<MOUSEHOOKSTRUCT *>(lParam);
        switch (wParam)
        {
            case WM_LBUTTONDOWN: case WM_LBUTTONUP:
            case WM_RBUTTONDOWN: case WM_RBUTTONUP:
            case WM_MBUTTONDOWN: case WM_MBUTTONUP:
            case WM_NCLBUTTONDOWN: case WM_NCLBUTTONUP:
            case WM_NCRBUTTONDOWN: case WM_NCRBUTTONUP:
            case WM_NCMBUTTONDOWN: case WM_NCMBUTTONUP:
            {
                ::GetWindowRect(s_hWatchWnd, &rc);
                if (!::PtInRect(&rc, pMouseHook->pt)) // outside of s_hWatchWnd?
                {
                    ::ShowWindowAsync(s_hWatchWnd, SW_HIDE); // hide it
                }
                break;
            }
        }
    }
    return ::CallNextHookEx(s_hMouseHook, nCode, wParam, lParam); // go next hook
}

//////////////////////////////////////////////////////////////////////////////
// sorting algorithm
// http://www.ics.kagoshima-u.ac.jp/~fuchida/edu/algorithm/sort-algorithm/

typedef CSimpleArray<CStringW> list_t;

static inline INT compare1(const CStringW& str1, const CStringW& str2)
{
    CStringW s1, s2;
    DropPrefix(str1, s1);
    DropPrefix(str2, s2);
    return s1.CompareNoCase(s2);
}

static inline INT pivot(list_t& list, INT i, INT j)
{
    INT k = i + 1;
    while (k <= j && compare1(list[i], list[k]) == 0)
        k++;
    if (k > j)
        return -1;
    if (compare1(list[i], list[k]) >= 0)
        return i;
    return k;
}

static inline INT partition(list_t& list, INT i, INT j, const CStringW& x)
{
    INT left = i, right = j;
    while (left <= right)
    {
        while (left <= j && compare1(list[left], x) < 0)
            left++;
        while (right >= i && compare1(list[right], x) >= 0)
            right--;
        if (left > right)
            break;

        CStringW tmp = list[left];
        list[left] = list[right];
        list[right] = tmp;

        left++;
        right--;
    }
    return left;
}

static void quicksort(list_t& list, INT i, INT j)
{
    if (i == j)
        return;
    INT p = pivot(list, i, j);
    if (p == -1)
        return;
    INT k = partition(list, i, j, list[p]);
    quicksort(list, i, k - 1);
    quicksort(list, k, j);
}

static inline void DoSort(list_t& list)
{
    if (list.GetSize() <= 1) // sanity check
        return;
    quicksort(list, 0, list.GetSize() - 1); // quick sort
}

// std::unique
static INT DoUnique(list_t& list)
{
    INT first = 0, last = list.GetSize();
    if (first == last)
        return last;
    INT result = first;
    while (++first != last)
    {
        if (compare1(list[result], list[first]) != 0)
            list[++result] = list[first];
    }
    return ++result;
}

static inline void DoUniqueAndTrim(list_t& list)
{
    INT last = DoUnique(list);
    while (list.GetSize() > last)
    {
        list.RemoveAt(last);
    }
}

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

// range of WCHAR (inclusive)
struct RANGE
{
    WCHAR from, to;
};

// a callback function for bsearch: comparison of two ranges
static inline int RangeCompare(const void *x, const void *y)
{
    const RANGE *a = reinterpret_cast<const RANGE *>(x);
    const RANGE *b = reinterpret_cast<const RANGE *>(y);
    if (a->to < b->from)
        return -1;
    if (b->to < a->from)
        return 1;
    return 0;
}

// is the WCHAR a word break?
static inline BOOL IsWordBreak(WCHAR ch)
{
    // the ranges of word break characters
    static const RANGE s_ranges[] =
    {
        { 0x0009, 0x0009 }, { 0x0020, 0x002f }, { 0x003a, 0x0040 }, { 0x005b, 0x0060 },
        { 0x007b, 0x007e }, { 0x00ab, 0x00ab }, { 0x00ad, 0x00ad }, { 0x00bb, 0x00bb },
        { 0x02c7, 0x02c7 }, { 0x02c9, 0x02c9 }, { 0x055d, 0x055d }, { 0x060c, 0x060c },
        { 0x2002, 0x200b }, { 0x2013, 0x2014 }, { 0x2016, 0x2016 }, { 0x2018, 0x2018 },
        { 0x201c, 0x201d }, { 0x2022, 0x2022 }, { 0x2025, 0x2027 }, { 0x2039, 0x203a },
        { 0x2045, 0x2046 }, { 0x207d, 0x207e }, { 0x208d, 0x208e }, { 0x226a, 0x226b },
        { 0x2574, 0x2574 }, { 0x3001, 0x3003 }, { 0x3005, 0x3005 }, { 0x3008, 0x3011 },
        { 0x3014, 0x301b }, { 0x301d, 0x301e }, { 0x3041, 0x3041 }, { 0x3043, 0x3043 },
        { 0x3045, 0x3045 }, { 0x3047, 0x3047 }, { 0x3049, 0x3049 }, { 0x3063, 0x3063 },
        { 0x3083, 0x3083 }, { 0x3085, 0x3085 }, { 0x3087, 0x3087 }, { 0x308e, 0x308e },
        { 0x309b, 0x309e }, { 0x30a1, 0x30a1 }, { 0x30a3, 0x30a3 }, { 0x30a5, 0x30a5 },
        { 0x30a7, 0x30a7 }, { 0x30a9, 0x30a9 }, { 0x30c3, 0x30c3 }, { 0x30e3, 0x30e3 },
        { 0x30e5, 0x30e5 }, { 0x30e7, 0x30e7 }, { 0x30ee, 0x30ee }, { 0x30f5, 0x30f6 },
        { 0x30fc, 0x30fe }, { 0xfd3e, 0xfd3f }, { 0xfe30, 0xfe31 }, { 0xfe33, 0xfe44 },
        { 0xfe4f, 0xfe51 }, { 0xfe59, 0xfe5e }, { 0xff08, 0xff09 }, { 0xff0c, 0xff0c },
        { 0xff0e, 0xff0e }, { 0xff1c, 0xff1c }, { 0xff1e, 0xff1e }, { 0xff3b, 0xff3b },
        { 0xff3d, 0xff3d }, { 0xff40, 0xff40 }, { 0xff5b, 0xff5e }, { 0xff61, 0xff64 },
        { 0xff67, 0xff70 }, { 0xff9e, 0xff9f }, { 0xffe9, 0xffe9 }, { 0xffeb, 0xffeb },
    };
    // binary search
    RANGE range = { ch, ch };
    return !!bsearch(&range, s_ranges, _countof(s_ranges), sizeof(RANGE), RangeCompare);
}

// This function is an application-defined callback function.
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nc-winuser-editwordbreakprocw
static INT CALLBACK
EditWordBreakProcW(LPWSTR lpch, INT index, INT count, INT code)
{
    switch (code)
    {
        case WB_ISDELIMITER:
            return IsWordBreak(lpch[index]);
        case WB_LEFT:
        {
            if (index)
                --index;
            while (index && !IsWordBreak(lpch[index]))
                --index;
            return index;
        }
        case WB_RIGHT:
        {
            if (!count)
                break;
            while (index < count && lpch[index] && !IsWordBreak(lpch[index]))
                ++index;
            return index;
        }
    }
    return 0;
}

static LRESULT CALLBACK
EditSubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam,
                 UINT_PTR uSubclassID, DWORD_PTR dwData)
{
    CAutoComplete *pThis = reinterpret_cast<CAutoComplete *>(dwData);
    return pThis->EditWndProc(hwnd, uMsg, wParam, lParam);
}

LRESULT CAutoComplete::EditWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    LRESULT ret;
    HWND hwndGotFocus;
    switch (uMsg)
    {
        case WM_CHAR:
            return OnEditChar(wParam, lParam);
        case WM_CUT: case WM_PASTE: case WM_CLEAR:
            ret = ::DefSubclassProc(hwnd, uMsg, wParam, lParam); // do default
            StartCompletion(TRUE);
            return ret;
        case WM_GETDLGCODE:
            ret = ::DefSubclassProc(hwnd, uMsg, wParam, lParam); // do default
            // some special keys need default processing. we handle them here
            switch (wParam)
            {
                case VK_RETURN:
                    if (IsWindowVisible() || ::GetKeyState(VK_CONTROL) < 0)
                        OnEditKeyDown(VK_RETURN, 0);
                    break;
                case VK_TAB:
                    if (IsWindowVisible() && UseTab())
                        ret |= DLGC_WANTALLKEYS; // we want all keys to manipulate the list
                    break;
                case VK_ESCAPE:
                    if (IsWindowVisible())
                        ret |= DLGC_WANTALLKEYS; // we want all keys to manipulate the list
                    break;
                default:
                {
                    ret |= DLGC_WANTALLKEYS; // we want all keys to manipulate the list
                    break;
                }
            }
            return ret;
        case WM_KEYDOWN:
            if (OnEditKeyDown(wParam, lParam))
                return TRUE; // eat
            break;
        case WM_SETFOCUS:
            m_bEditHasFocus = TRUE;
            break;
        case WM_KILLFOCUS:
            // hide the list if lost focus
            hwndGotFocus = (HWND)wParam;
            if (hwndGotFocus != m_hwndEdit && hwndGotFocus != m_hWnd)
                HideDropDown();
            m_bEditHasFocus = FALSE;
            break;
        case WM_IME_NOTIFY:
            if (wParam == IMN_OPENCANDIDATE)
                HideDropDown();
            break;
        case WM_SETTEXT:
            if (!m_bInSetText)
                HideDropDown(); // it's mechanical WM_SETTEXT
            break;
        case WM_DESTROY:
        {
            if (m_fnOldWordBreakProc)
            {
                ::SendMessageW(hwnd, EM_SETWORDBREAKPROC, 0, reinterpret_cast<LPARAM>(m_fnOldWordBreakProc));
                m_fnOldWordBreakProc = NULL;
            }
            ::RemoveWindowSubclass(hwnd, EditSubclassProc, 0);
            if (::IsWindow(m_hWnd))
                PostMessageW(WM_CLOSE, 0, 0);
            // remove reference to m_hwndEdit
            Release();
            break;
        }
    }

    return ::DefSubclassProc(hwnd, uMsg, wParam, lParam); // do default
}

//////////////////////////////////////////////////////////////////////////////
// CACListView

CACListView::CACListView() : m_pDropDown(NULL), m_cyItem(CY_ITEM)
{
}

HWND CACListView::Create(HWND hwndParent)
{
    ATLASSERT(m_hWnd == NULL);
    DWORD dwStyle = WS_CHILD | /*WS_VISIBLE |*/ WS_CLIPSIBLINGS | LVS_NOCOLUMNHEADER |
                    LVS_OWNERDATA | LVS_OWNERDRAWFIXED | LVS_SINGLESEL | LVS_REPORT;
    HWND hWnd = ::CreateWindowExW(0, GetWndClassName(), L"Internet Explorer", dwStyle,
                                  0, 0, 0, 0, hwndParent, NULL,
                                  _AtlBaseModule.GetModuleInstance(), NULL);
    SubclassWindow(hWnd); // do subclass to handle messages
    // set extended listview style
    DWORD exstyle = LVS_EX_ONECLICKACTIVATE | LVS_EX_FULLROWSELECT | LVS_EX_TRACKSELECT;
    SetExtendedListViewStyle(exstyle, exstyle);
    // insert one column (needed to insert items)
    LV_COLUMNW column = { LVCF_FMT | LVCF_WIDTH };
    column.fmt = LVCFMT_LEFT;
    column.cx = CX_LIST - ::GetSystemMetrics(SM_CXVSCROLL);
    InsertColumn(0, &column);
    return m_hWnd;
}

// set font handle
VOID CACListView::SetFont(HFONT hFont)
{
    SendMessageW(WM_SETFONT, (WPARAM)hFont, TRUE); // set font

    // get listview item height
    m_cyItem = CY_ITEM;
    HDC hDC = GetDC();
    if (hDC)
    {
        HGDIOBJ hFontOld = ::SelectObject(hDC, hFont);
        TEXTMETRICW tm;
        if (::GetTextMetricsW(hDC, &tm))
        {
            m_cyItem = (tm.tmHeight * 3) / 2; // 3/2 of text height
        }
        ::SelectObject(hDC, hFontOld);
        ReleaseDC(hDC);
    }
}

// get the number of visible items
INT CACListView::GetVisibleCount()
{
    if (m_cyItem <= 0) // avoid "division by zero"
        return 0;
    CRect rc;
    GetClientRect(&rc);
    return rc.Height() / m_cyItem;
}

// get the text of an item
CStringW CACListView::GetItemText(INT iItem)
{
    // NOTE: LVS_OWNERDATA doesn't support LVM_GETITEMTEXT.
    ATLASSERT(m_pDropDown);
    ATLASSERT(GetStyle() & LVS_OWNERDATA);
    return m_pDropDown->GetItemText(iItem);
}

// get the item index from position
INT CACListView::ItemFromPoint(INT x, INT y)
{
    LV_HITTESTINFO hittest;
    hittest.pt.x = x;
    hittest.pt.y = y;
    return HitTest(&hittest);
}

// get current selection
INT CACListView::GetCurSel()
{
    return GetNextItem(-1, LVNI_ALL | LVNI_SELECTED);
}

// set current selection
VOID CACListView::SetCurSel(INT iItem)
{
    if (iItem == -1)
        SetItemState(-1, 0, LVIS_SELECTED); // select none
    else
        SetItemState(iItem, LVIS_SELECTED, LVIS_SELECTED);
}

// select the specific position (in client coordinates)
VOID CACListView::SelectHere(INT x, INT y)
{
    SetCurSel(ItemFromPoint(x, y));
}

// WM_LBUTTONUP / WM_MBUTTONUP / WM_RBUTTONUP @implemented
LRESULT CACListView::OnButtonUp(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
{
    TRACE("CACListView::OnButtonUp(%p)\n", this);
    return 0; // eat
}

// WM_LBUTTONDOWN @implemented
// This message is posted when the user pressed the left mouse button while the cursor is inside.
LRESULT CACListView::OnLButtonDown(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
{
    TRACE("CACListView::OnLButtonDown(%p)\n", this);
    ATLASSERT(m_pDropDown);
    INT iItem = ItemFromPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
    if (iItem != -1)
    {
        m_pDropDown->SelectItem(iItem); // select the item
        CStringW strText = GetItemText(iItem); // get text of item
        m_pDropDown->SetEditText(strText); // set text
        m_pDropDown->SetEditSel(0, strText.GetLength()); // select all
        m_pDropDown->HideDropDown(); // hide
    }
    return 0;
}

// WM_MBUTTONDOWN / WM_RBUTTONDOWN @implemented
LRESULT CACListView::OnMRButtonDown(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
{
    TRACE("CACListView::OnMRButtonDown(%p)\n", this);
    return 0; // eat
}

// WM_MOUSEWHEEL @implemented
LRESULT CACListView::OnMouseWheel(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
{
    TRACE("CACListView::OnMouseWheel(%p)\n", this);
    ATLASSERT(m_pDropDown);
    LRESULT ret = DefWindowProcW(uMsg, wParam, lParam); // do default
    m_pDropDown->UpdateScrollBar();
    return ret;
}

// WM_NCHITTEST
LRESULT CACListView::OnNCHitTest(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
{
    TRACE("CACListView::OnNCHitTest(%p)\n", this);
    ATLASSERT(m_pDropDown);
    POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; // in screen coordinates
    ScreenToClient(&pt); // into client coordinates
    HWND hwndTarget = m_pDropDown->ChildWindowFromPoint(pt);
    if (hwndTarget != m_hWnd)
        return HTTRANSPARENT; // pass through (for resizing the drop-down window)
    bHandled = FALSE; // do default
    return 0;
}

//////////////////////////////////////////////////////////////////////////////
// CACScrollBar

HWND CACScrollBar::Create(HWND hwndParent)
{
    ATLASSERT(m_hWnd == NULL);
    DWORD dwStyle = WS_CHILD | /*WS_VISIBLE |*/ SBS_BOTTOMALIGN | SBS_VERT;
    m_hWnd = ::CreateWindowExW(0, GetWndClassName(), NULL, dwStyle,
                               0, 0, 0, 0, hwndParent, NULL,
                               _AtlBaseModule.GetModuleInstance(), NULL);
    // we don't subclass because no message handling is needed
    return m_hWnd;
}

//////////////////////////////////////////////////////////////////////////////
// CACSizeBox

HWND CACSizeBox::Create(HWND hwndParent)
{
    ATLASSERT(m_hWnd == NULL);
    DWORD dwStyle = WS_CHILD | /*WS_VISIBLE |*/ SBS_SIZEBOX;
    HWND hWnd = ::CreateWindowExW(0, GetWndClassName(), NULL, dwStyle,
                                  0, 0, 0, 0, hwndParent, NULL,
                                  _AtlBaseModule.GetModuleInstance(), NULL);
    SubclassWindow(hWnd); // do subclass to handle message
    return m_hWnd;
}

VOID CACSizeBox::SetStatus(BOOL bDowner, BOOL bLongList)
{
    // set flags
    m_bDowner = bDowner;
    m_bLongList = bLongList;

    if (bLongList)
    {
        SetWindowRgn(NULL, TRUE); // reset window region
        return;
    }

    RECT rc;
    GetWindowRect(&rc); // get size-box size
    ::OffsetRect(&rc, -rc.left, -rc.top); // window regions use special coordinates
    ATLASSERT(rc.left == 0 && rc.top == 0);

    // create a trianglar region
    HDC hDC = ::CreateCompatibleDC(NULL);
    ::BeginPath(hDC);
    if (m_bDowner)
    {
        ::MoveToEx(hDC, rc.right, 0, NULL);
        ::LineTo(hDC, rc.right, rc.bottom);
        ::LineTo(hDC, 0, rc.bottom);
        ::LineTo(hDC, rc.right, 0);
    }
    else
    {
        ::MoveToEx(hDC, rc.right, rc.bottom, NULL);
        ::LineTo(hDC, rc.right, 0);
        ::LineTo(hDC, 0, 0);
        ::LineTo(hDC, rc.right, rc.bottom);
    }
    ::EndPath(hDC);
    HRGN hRgn = ::PathToRegion(hDC);
    ::DeleteDC(hDC);

    SetWindowRgn(hRgn, TRUE); // set the trianglar region
}

// WM_ERASEBKGND
LRESULT CACSizeBox::OnEraseBkGnd(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
{
    return TRUE; // do nothing (for quick drawing)
}

// WM_NCHITTEST
LRESULT CACSizeBox::OnNCHitTest(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
{
    return HTTRANSPARENT; // pass through
}

// WM_PAINT
LRESULT CACSizeBox::OnPaint(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
{
    CRect rc;
    GetClientRect(&rc);

    PAINTSTRUCT ps;
    HDC hDC = BeginPaint(&ps);
    if (!hDC)
        return 0;

    // fill background
    ::FillRect(hDC, &rc, ::GetSysColorBrush(COLOR_3DFACE));

    // draw size-box
    INT cxy = rc.Width();
    for (INT shift = 0; shift < 2; ++shift)
    {
        // choose pen color
        INT iColor = ((shift == 0) ? COLOR_HIGHLIGHTTEXT : COLOR_3DSHADOW);
        HPEN hPen = ::CreatePen(PS_SOLID, 1, ::GetSysColor(iColor));
        HGDIOBJ hPenOld = ::SelectObject(hDC, hPen);
        // do loop to draw the slanted lines
        for (INT delta = cxy / 4; delta < cxy; delta += cxy / 4)
        {
            // draw a grip line
            if (m_bDowner)
            {
                ::MoveToEx(hDC, rc.right, rc.top + delta + shift, NULL);
                ::LineTo(hDC, rc.left + delta + shift, rc.bottom);
            }
            else
            {
                ::MoveToEx(hDC, rc.left + delta + shift, rc.top, NULL);
                ::LineTo(hDC, rc.right, rc.bottom - delta - shift);
            }
        }
        // delete pen
        ::SelectObject(hDC, hPenOld);
        ::DeleteObject(hPen);
    }

    EndPaint(&ps);
    return 0;
}

//////////////////////////////////////////////////////////////////////////////
// CAutoComplete public methods

CAutoComplete::CAutoComplete()
    : m_bInSetText(FALSE), m_bInSelectItem(FALSE), m_bEditHasFocus(FALSE)
    , m_bDowner(TRUE), m_dwOptions(ACO_AUTOAPPEND | ACO_AUTOSUGGEST)
    , m_bEnabled(TRUE), m_hwndCombo(NULL), m_hFont(NULL), m_bResized(FALSE)
    , m_hwndEdit(NULL), m_fnOldEditProc(NULL), m_fnOldWordBreakProc(NULL)
    , m_hThread(NULL), m_pThread(NULL)
{
}

HWND CAutoComplete::CreateDropDown()
{
    ATLASSERT(m_hWnd == NULL);
    DWORD dwStyle = WS_POPUP | /*WS_VISIBLE |*/ WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_BORDER;
    DWORD dwExStyle = WS_EX_TOOLWINDOW | WS_EX_TOPMOST | WS_EX_NOPARENTNOTIFY;
    Create(NULL, NULL, NULL, dwStyle, dwExStyle);
    TRACE("CAutoComplete::CreateDropDown(%p): m_hWnd=%p, m_hwndEdit=%p\n",
          this, m_hWnd, m_hwndEdit);
    return m_hWnd;
}

CAutoComplete::~CAutoComplete()
{
    TRACE("CAutoComplete::~CAutoComplete(%p)\n", this);
    if (m_hThread)
    {
        CloseHandle(m_hThread);
        m_hThread = NULL;
    }
    if (m_hFont)
    {
        ::DeleteObject(m_hFont);
        m_hFont = NULL;
    }
    // quit holding them
    m_pEnum.Release();
    m_pACList.Release();
}

inline BOOL CAutoComplete::CanAutoSuggest() const
{
    return !!(m_dwOptions & ACO_AUTOSUGGEST) && m_bEnabled;
}

inline BOOL CAutoComplete::CanAutoAppend() const
{
    return !!(m_dwOptions & ACO_AUTOAPPEND) && m_bEnabled;
}

inline BOOL CAutoComplete::UseTab() const
{
    return !!(m_dwOptions & ACO_USETAB) && m_bEnabled;
}

inline BOOL CAutoComplete::IsComboBoxDropped() const
{
    if (!::IsWindow(m_hwndCombo))
        return FALSE;
    return (BOOL)::SendMessageW(m_hwndCombo, CB_GETDROPPEDSTATE, 0, 0);
}

inline BOOL CAutoComplete::FilterPrefixes() const
{
    return !!(m_dwOptions & ACO_FILTERPREFIXES) && m_bEnabled;
}

inline INT CAutoComplete::GetItemCount() const
{
    return m_outerList.GetSize();
}

CStringW CAutoComplete::GetItemText(INT iItem) const
{
    if (iItem < 0 || m_outerList.GetSize() <= iItem)
        return L"";
    return m_outerList[iItem];
}

inline CStringW CAutoComplete::GetEditText() const
{
    WCHAR szText[L_MAX_URL_LENGTH];
    if (::GetWindowTextW(m_hwndEdit, szText, _countof(szText)))
        return szText;
    return L"";
}

VOID CAutoComplete::SetEditText(LPCWSTR pszText)
{
    m_bInSetText = TRUE; // don't hide drop-down
    ::CallWindowProcW(m_fnOldEditProc, m_hwndEdit, WM_SETTEXT, 0, reinterpret_cast<LPARAM>(pszText));
    m_bInSetText = FALSE;
}

inline CStringW CAutoComplete::GetStemText(const CStringW& strText) const
{
    INT ich = strText.ReverseFind(L'\\');
    if (ich == -1)
        return L""; // no stem
    return strText.Left(ich + 1);
}

VOID CAutoComplete::SetEditSel(INT ich0, INT ich1)
{
    ::CallWindowProcW(m_fnOldEditProc, m_hwndEdit, EM_SETSEL, ich0, ich1);
}

VOID CAutoComplete::ShowDropDown()
{
    if (!m_hWnd || !CanAutoSuggest())
        return;

    INT cItems = GetItemCount();
    if (cItems == 0 || ::GetFocus() != m_hwndEdit || IsComboBoxDropped())
    {
        // hide the drop-down if necessary
        HideDropDown();
        return;
    }

    RepositionDropDown();
}

VOID CAutoComplete::HideDropDown()
{
    ShowWindow(SW_HIDE);
}

VOID CAutoComplete::SelectItem(INT iItem)
{
    m_hwndList.SetCurSel(iItem);
    if (iItem != -1)
        m_hwndList.EnsureVisible(iItem, FALSE);
}

VOID CAutoComplete::DoAutoAppend(PAC_THREAD pThread)
{
    if (!CanAutoAppend()) // can we auto-append?
        return; // don't append

    CStringW strText = GetEditText(); // get the text
    if (strText.IsEmpty())
        return; // don't append

    INT cItems = m_outerList.GetSize(); // get the number of items
    if (cItems == 0)
        return; // don't append

    // get the common string
    CStringW strCommon;
    BOOL bFound = FALSE;
    for (INT iItem = 0; iItem < cItems; ++iItem)
    {
        const CStringW& strItem = m_outerList[iItem]; // get the text of the item

        CStringW strBody;
        if (DropPrefix(strItem, strBody) &&
            ::StrCmpNIW(strBody, strText, strText.GetLength()) == 0)
        {
            if (!bFound)
            {
                bFound = TRUE;
                strCommon = strBody;
                continue;
            }
            for (INT ich = 0; ich < strBody.GetLength(); ++ich)
            {
                if (strCommon.GetLength() <= ich)
                    break;
                if (ChrCmpIW(strCommon[ich], strBody[ich]) != 0)
                {
                    strCommon = strCommon.Left(ich);
                    break;
                }
            }
            continue;
        }

        if (::StrCmpNIW(strItem, strText, strText.GetLength()) == 0)
        {
            if (!bFound)
            {
                bFound = TRUE;
                strCommon = strItem;
                continue;
            }

            for (INT ich = 0; ich < strItem.GetLength(); ++ich)
            {
                if (strCommon.GetLength() <= ich)
                    break;
                if (ChrCmpIW(strCommon[ich], strItem[ich]) != 0)
                {
                    strCommon = strCommon.Left(ich);
                    break;
                }
            }
        }
    }

    if (strCommon.IsEmpty() || strCommon.GetLength() <= strText.GetLength())
        return; // no suggestion

    // append suggestion
    SetEditText(strCommon);

    // select the appended suggestion
    SetEditSel(strText.GetLength(), strCommon.GetLength());
}

// go back a word ([Ctrl]+[Backspace])
VOID CAutoComplete::DoBackWord()
{
    // get current selection
    INT ich0, ich1;
    ::CallWindowProcW(m_fnOldEditProc, m_hwndEdit, EM_GETSEL,
                      reinterpret_cast<WPARAM>(&ich0), reinterpret_cast<LPARAM>(&ich1));
    if (ich0 <= 0 || ich0 != ich1) // there is selection or left-side end
        return; // don't do anything
    // get text
    CStringW str = GetEditText();
    // extend the range
    while (ich0 > 0 && IsWordBreak(str[ich0 - 1]))
        --ich0;
    while (ich0 > 0 && !IsWordBreak(str[ich0 - 1]))
        --ich0;
    // select range
    SetEditSel(ich0, ich1);
    // replace selection with empty text (this is actually deletion)
    ::CallWindowProcW(m_fnOldEditProc, m_hwndEdit, EM_REPLACESEL, TRUE, (LPARAM)L"");
}

VOID CAutoComplete::UpdateScrollBar()
{
    // copy scroll info from m_hwndList to m_hwndScrollBar
    SCROLLINFO si = { sizeof(si), SIF_ALL };
    m_hwndList.GetScrollInfo(SB_VERT, &si);
    m_hwndScrollBar.SetScrollInfo(SB_CTL, &si, FALSE);

    // show/hide scroll bar
    INT cVisibles = m_hwndList.GetVisibleCount();
    INT cItems = m_hwndList.GetItemCount();
    BOOL bShowScroll = (cItems > cVisibles);
    m_hwndScrollBar.ShowWindow(bShowScroll ? SW_SHOWNOACTIVATE : SW_HIDE);
    if (bShowScroll)
        m_hwndScrollBar.InvalidateRect(NULL, FALSE); // redraw
}

BOOL CAutoComplete::OnEditKeyDown(WPARAM wParam, LPARAM lParam)
{
    TRACE("CAutoComplete::OnEditKeyDown(%p, %p)\n", this, wParam);

    UINT vk = (UINT)wParam; // virtual key
    switch (vk)
    {
        case VK_UP: case VK_DOWN:
        case VK_PRIOR: case VK_NEXT:
            // is suggestion available?
            if (!CanAutoSuggest())
                return FALSE; // do default
            if (IsWindowVisible())
                return OnListUpDown(vk);
            break;
        case VK_ESCAPE:
        {
            // is suggestion available?
            if (!CanAutoSuggest())
                return FALSE; // do default
            if (IsWindowVisible())
            {
                SetEditText(m_strText); // revert the edit text
                // select the end
                INT cch = m_strText.GetLength();
                SetEditSel(cch, cch);
                HideDropDown(); // hide
                return TRUE; // eat
            }
            break;
        }
        case VK_RETURN:
        {
            if (::GetKeyState(VK_CONTROL) < 0)
            {
                // quick edit
                CStringW strText = GetEditText();
                SetEditText(GetQuickEdit(strText));
            }
            else
            {
                // if item is selected, then update the edit text
                INT iItem = m_hwndList.GetCurSel();
                if (iItem != -1)
                {
                    CStringW strText = GetItemText(iItem);
                    SetEditText(strText);
                }
            }
            // select all
            INT cch = ::CallWindowProcW(m_fnOldEditProc, m_hwndEdit, WM_GETTEXTLENGTH, 0, 0);
            SetEditSel(0, cch);
            // hide
            HideDropDown();
            break;
        }
        case VK_TAB:
        {
            // ACO_USETAB
            if (IsWindowVisible() && UseTab())
            {
                if (GetKeyState(VK_SHIFT) < 0)
                    return OnListUpDown(VK_UP);
                else
                    return OnListUpDown(VK_DOWN);
            }
            break;
        }
        case VK_DELETE:
        {
            // is suggestion available?
            if (!CanAutoSuggest())
                return FALSE; // do default
            ::DefSubclassProc(m_hwndEdit, WM_KEYDOWN, VK_DELETE, 0); // do default
            StartCompletion(FALSE);
            return TRUE; // eat
        }
        case VK_BACK:
        {
            if (::GetKeyState(VK_CONTROL) < 0)
            {
                DoBackWord();
                return TRUE; // eat
            }
            break;
        }
    }
    return FALSE; // default
}

LRESULT CAutoComplete::OnEditChar(WPARAM wParam, LPARAM lParam)
{
    TRACE("CACEditCtrl::OnEditChar(%p, %p)\n", this, wParam);
    if (wParam == L'\n' || wParam == L'\t')
        return 0; // eat
    LRESULT ret = ::DefSubclassProc(m_hwndEdit, WM_CHAR, wParam, lParam); // do default
    if (CanAutoSuggest() || CanAutoAppend())
        StartCompletion(wParam != VK_BACK);
    return ret;
}

VOID CAutoComplete::OnListSelChange()
{
    // update EDIT text
    INT iItem = m_hwndList.GetCurSel();
    CStringW text = ((iItem != -1) ? GetItemText(iItem) : m_strText);
    SetEditText(text);
    // ensure the item visible
    m_hwndList.EnsureVisible(iItem, FALSE);
    // select the end
    INT cch = text.GetLength();
    SetEditSel(cch, cch);
}

BOOL CAutoComplete::OnListUpDown(UINT vk)
{
    if (!CanAutoSuggest())
        return FALSE; // default

    if ((m_dwOptions & ACO_UPDOWNKEYDROPSLIST) && !IsWindowVisible())
    {
        ShowDropDown();
        return TRUE; // eat
    }

    INT iItem = m_hwndList.GetCurSel(); // current selection
    INT cItems = m_hwndList.GetItemCount(); // the number of items
    switch (vk)
    {
        case VK_UP:
            if (iItem == -1)
                iItem = cItems - 1;
            else if (iItem == 0)
                iItem = -1;
            else
                --iItem;
            m_hwndList.SetCurSel(iItem);
            break;
        case VK_DOWN:
            if (iItem == -1)
                iItem = 0;
            else if (iItem == cItems - 1)
                iItem = -1;
            else
                ++iItem;
            m_hwndList.SetCurSel(iItem);
            break;
        case VK_PRIOR:
            if (iItem == -1)
            {
                iItem = cItems - 1;
            }
            else if (iItem == 0)
            {
                iItem = -1;
            }
            else
            {
                iItem -= m_hwndList.GetVisibleCount() - 1;
                if (iItem < 0)
                    iItem = 0;
            }
            m_hwndList.SetCurSel(iItem);
            break;
        case VK_NEXT:
            if (iItem == -1)
            {
                iItem = 0;
            }
            else if (iItem == cItems - 1)
            {
                iItem = -1;
            }
            else
            {
                iItem += m_hwndList.GetVisibleCount() - 1;
                if (iItem > cItems)
                    iItem = cItems - 1;
            }
            m_hwndList.SetCurSel(iItem);
            break;
        default:
        {
            ATLASSERT(FALSE);
            break;
        }
    }

    return TRUE; // eat
}

//////////////////////////////////////////////////////////////////////////////
// CAutoComplete IAutoComplete methods

// @implemented
STDMETHODIMP CAutoComplete::Enable(BOOL fEnable)
{
    TRACE("(%p)->Enable(%d)\n", this, fEnable);
    m_bEnabled = fEnable;
    return S_OK;
}

STDMETHODIMP
CAutoComplete::Init(HWND hwndEdit, IUnknown *punkACL,
                    LPCOLESTR pwszRegKeyPath, LPCOLESTR pwszQuickComplete)
{
    TRACE("(%p)->Init(0x%08lx, %p, %s, %s)\n",
          this, hwndEdit, punkACL, debugstr_w(pwszRegKeyPath), debugstr_w(pwszQuickComplete));
    // sanity check
    if (m_hwndEdit || !punkACL)
        return E_FAIL;
    if (!hwndEdit)
        return E_INVALIDARG;
    // do subclass textbox to watch messages
    m_fnOldEditProc = reinterpret_cast<WNDPROC>(::GetWindowLongPtrW(hwndEdit, GWLP_WNDPROC));
    if (!m_fnOldEditProc)
        return E_FAIL;
    if (!::SetWindowSubclass(hwndEdit, EditSubclassProc, 0, reinterpret_cast<DWORD_PTR>(this)))
        return E_FAIL;
    m_hwndEdit = hwndEdit;
    m_bEditHasFocus = (::GetFocus() == hwndEdit);
    // add reference to m_hwndEdit
    AddRef();
    // set word break procedure
    m_fnOldWordBreakProc = reinterpret_cast<EDITWORDBREAKPROCW>(
        ::SendMessageW(m_hwndEdit, EM_SETWORDBREAKPROC, 0,
                       reinterpret_cast<LPARAM>(EditWordBreakProcW)));
    // save position
    ::GetWindowRect(m_hwndEdit, &m_rcEdit);

    // get an IEnumString
    punkACL->QueryInterface(IID_IEnumString, (VOID **)&m_pEnum);
    TRACE("m_pEnum: %p\n", static_cast<void *>(m_pEnum));

    // get an IACList
    punkACL->QueryInterface(IID_IACList, (VOID **)&m_pACList);
    TRACE("m_pACList: %p\n", static_cast<void *>(m_pACList));

    UpdateDropDownState(); // create/hide the drop-down window if necessary

    // load quick completion info
    LoadQuickComplete(pwszRegKeyPath, pwszQuickComplete);

    // any combobox for m_hwndEdit?
    m_hwndCombo = NULL;
    HWND hwndParent = ::GetParent(m_hwndEdit);
    WCHAR szClass[16];
    if (::GetClassNameW(hwndParent, szClass, _countof(szClass)))
    {
        if (::StrCmpIW(szClass, WC_COMBOBOXW) == 0 ||
            ::StrCmpIW(szClass, WC_COMBOBOXEXW) == 0)
        {
            m_hwndCombo = hwndParent; // get combobox
        }
    }

    return S_OK;
}

//////////////////////////////////////////////////////////////////////////////
// CAutoComplete IAutoComplete2 methods

// @implemented
STDMETHODIMP CAutoComplete::GetOptions(DWORD *pdwFlag)
{
    TRACE("(%p) -> (%p)\n", this, pdwFlag);
    if (pdwFlag)
    {
        *pdwFlag = m_dwOptions;
        return S_OK;
    }
    return E_INVALIDARG;
}

// @implemented
STDMETHODIMP CAutoComplete::SetOptions(DWORD dwFlag)
{
    TRACE("(%p) -> (0x%x)\n", this, dwFlag);
    m_dwOptions = dwFlag;

    if (m_dwOptions & ACO_SEARCH)
        FIXME(" ACO_SEARCH not supported\n");
    if (m_dwOptions & ACO_FILTERPREFIXES)
        FIXME(" ACO_FILTERPREFIXES not supported\n");
    if (m_dwOptions & ACO_RTLREADING)
        FIXME(" ACO_RTLREADING not supported\n");

    UpdateDropDownState(); // create/hide the drop-down window if necessary
    return S_OK;
}

//////////////////////////////////////////////////////////////////////////////
// CAutoComplete IAutoCompleteDropDown methods

// @implemented
STDMETHODIMP CAutoComplete::GetDropDownStatus(DWORD *pdwFlags, LPWSTR *ppwszString)
{
    BOOL dropped = m_hwndList.IsWindowVisible();

    if (pdwFlags)
        *pdwFlags = (dropped ? ACDD_VISIBLE : 0);

    if (ppwszString)
    {
        *ppwszString = NULL;

        if (dropped)
        {
            // get selected item
            INT iItem = m_hwndList.GetCurSel();
            if (iItem >= 0)
            {
                // get the text of item
                CStringW strText = m_hwndList.GetItemText(iItem);

                // store to *ppwszString
                SHStrDupW(strText, ppwszString);
                if (*ppwszString == NULL)
                    return E_OUTOFMEMORY;
            }
        }
    }

    return S_OK;
}

STDMETHODIMP CAutoComplete::ResetEnumerator()
{
    FIXME("(%p): stub\n", this);

    Reset();
    m_hwndList.SendMessageW(LVM_SETITEMCOUNT, 0, 0);
    m_outerList.RemoveAll();
    return S_OK;
}

//////////////////////////////////////////////////////////////////////////////
// CAutoComplete IEnumString methods

// @implemented
STDMETHODIMP CAutoComplete::Next(ULONG celt, LPOLESTR *rgelt, ULONG *pceltFetched)
{
    TRACE("(%p, %d, %p, %p)\n", this, celt, rgelt, pceltFetched);
    if (rgelt)
        *rgelt = NULL;
    if (*pceltFetched)
        *pceltFetched = 0;
    if (celt != 1 || !rgelt || !pceltFetched || !m_pEnum)
        return E_INVALIDARG;

    LPWSTR pszText = NULL;
    HRESULT hr = m_pEnum->Next(1, &pszText, pceltFetched);
    if (hr == S_OK)
        *rgelt = pszText;
    else
        ::CoTaskMemFree(pszText);
    return hr;
}

// @implemented
STDMETHODIMP CAutoComplete::Skip(ULONG celt)
{
    TRACE("(%p, %d)\n", this, celt);
    return E_NOTIMPL;
}

// @implemented
STDMETHODIMP CAutoComplete::Reset()
{
    TRACE("(%p)\n", this);
    if (m_pEnum)
        return m_pEnum->Reset();
    return E_FAIL;
}

// @implemented
STDMETHODIMP CAutoComplete::Clone(IEnumString **ppOut)
{
    TRACE("(%p, %p)\n", this, ppOut);
    if (ppOut)
        *ppOut = NULL;
    return E_NOTIMPL;
}

//////////////////////////////////////////////////////////////////////////////
// CAutoComplete protected methods

VOID CAutoComplete::UpdateDropDownState()
{
    if (CanAutoSuggest())
    {
        // create the drop-down window if not existed
        if (!m_hWnd)
            CreateDropDown();
    }
    else
    {
        // hide if existed
        if (m_hWnd)
            ShowWindow(SW_HIDE);
    }
}

// calculate the positions of the controls
VOID CAutoComplete::CalcRects(BOOL bDowner, RECT& rcList, RECT& rcScrollBar, RECT& rcSizeBox) const
{
    // get the client rectangle
    RECT rcClient;
    GetClientRect(&rcClient);

    // the list
    rcList = rcClient;
    rcList.right = rcList.left + CX_LIST;

    // the scroll bar
    rcScrollBar = rcClient;
    rcScrollBar.left = rcClient.right - GetSystemMetrics(SM_CXVSCROLL);
    if (bDowner)
    {
        rcScrollBar.top = 0;
        rcScrollBar.bottom = rcClient.bottom - GetSystemMetrics(SM_CYHSCROLL);
    }
    else
    {
        rcScrollBar.top = GetSystemMetrics(SM_CYHSCROLL);
    }

    // the size box
    rcSizeBox = rcClient;
    rcSizeBox.left = rcClient.right - GetSystemMetrics(SM_CXVSCROLL);
    if (bDowner)
    {
        rcSizeBox.top = rcClient.bottom - GetSystemMetrics(SM_CYHSCROLL);
    }
    else
    {
        rcSizeBox.top = 0;
        rcSizeBox.bottom = rcClient.top + GetSystemMetrics(SM_CYHSCROLL);
    }
}

VOID CAutoComplete::LoadQuickComplete(LPCWSTR pwszRegKeyPath, LPCWSTR pwszQuickComplete)
{
    m_strQuickComplete.Empty();

    if (pwszRegKeyPath)
    {
        CStringW strPath = pwszRegKeyPath;
        INT ichSep = strPath.ReverseFind(L'\\'); // find separator
        if (ichSep != -1) // found the last separator
        {
            // split by the separator
            CStringW strKey = strPath.Left(ichSep);
            CStringW strName = strPath.Mid(ichSep + 1);

            // load from registry
            WCHAR szValue[MAX_PATH] = L"";
            DWORD cbValue = sizeof(szValue), dwType = REG_NONE;
            SHRegGetUSValueW(pwszRegKeyPath, strName, &dwType,
                             szValue, &cbValue, FALSE, NULL, 0);
            if (szValue[0] != 0 && cbValue != 0 &&
                (dwType == REG_SZ || dwType == REG_EXPAND_SZ))
            {
                m_strQuickComplete = szValue;
            }
        }
    }

    if (pwszQuickComplete && m_strQuickComplete.IsEmpty())
    {
        m_strQuickComplete = pwszQuickComplete;
    }
}

CStringW CAutoComplete::GetQuickEdit(LPCWSTR pszText) const
{
    if (pszText[0] == 0 || m_strQuickComplete.IsEmpty())
        return pszText;

    // m_strQuickComplete will be "www.%s.com" etc.
    CStringW ret;
    ret.Format(m_strQuickComplete, pszText);
    return ret;
}

VOID CAutoComplete::RepositionDropDown()
{
    // If Edit has no focus, don't open auto-complete
    if (!m_bEditHasFocus)
    {
        TRACE("!m_bEditHasFocus\n");
        return;
    }

    // get nearest monitor from m_hwndEdit
    HMONITOR hMon = ::MonitorFromWindow(m_hwndEdit, MONITOR_DEFAULTTONEAREST);
    ATLASSERT(hMon != NULL);
    if (hMon == NULL)
        return;

    // get nearest monitor info
    MONITORINFO mi = { sizeof(mi) };
    if (!::GetMonitorInfo(hMon, &mi))
    {
        ATLASSERT(FALSE);
        return;
    }

    // get count and item height
    INT cItems = GetItemCount();
    INT cyItem = m_hwndList.m_cyItem;
    ATLASSERT(cyItem > 0);

    // get m_hwndEdit position
    RECT rcEdit;
    ::GetWindowRect(m_hwndEdit, &rcEdit);
    INT x = rcEdit.left, y = rcEdit.bottom;

    // get list extent
    RECT rcMon = mi.rcMonitor;
    INT cx = rcEdit.right - rcEdit.left, cy = cItems * cyItem;
    BOOL bLongList = FALSE;
    if (cy > CY_LIST)
    {
        cy = INT(CY_LIST / cyItem) * cyItem;
        bLongList = TRUE;
    }

    // convert rectangle for frame
    RECT rc = { 0, 0, cx, cy };
    AdjustWindowRectEx(&rc, GetStyle(), FALSE, GetExStyle());
    cy = rc.bottom - rc.top;

    if (!m_bResized)
    {
        // is the drop-down window a 'downer' or 'upper'?
        // NOTE: 'downer' is below the EDIT control. 'upper' is above the EDIT control.
        m_bDowner = (rcEdit.bottom + cy < rcMon.bottom);
    }

    // adjust y and cy
    if (m_bDowner)
    {
        if (rcMon.bottom < y + cy)
        {
            cy = ((rcMon.bottom - y) / cyItem) * cyItem;
            bLongList = TRUE;
        }
    }
    else
    {
        if (rcEdit.top < rcMon.top + cy)
        {
            cy = ((rcEdit.top - rcMon.top) / cyItem) * cyItem;
            bLongList = TRUE;
        }
        y = rcEdit.top - cy;
    }

    // set status
    m_hwndSizeBox.SetStatus(m_bDowner, bLongList);

    if (m_bResized) // already resized?
        PostMessageW(WM_SIZE, 0, 0); // re-layout
    else
        MoveWindow(x, y, cx, cy); // move

    // show without activation
    ShowWindow(SW_SHOWNOACTIVATE);
}

VOID
CAutoComplete::ExtractInnerList(CSimpleArray<CStringW>& outerList,
                                const CSimpleArray<CStringW>& innerList,
                                const CString& strText)
{
    for (INT iItem = 0; iItem < innerList.GetSize(); ++iItem)
    {
        if (m_pThread || !m_hThread)
            break;

        const CStringW& strTarget = innerList[iItem];
        if (DoesMatch(strTarget, strText))
        {
            outerList.Add(strTarget);

            if (outerList.GetSize() >= MAX_ITEM_COUNT)
                break;
        }
    }
}

VOID CAutoComplete::ReLoadInnerList(PAC_THREAD pThread)
{
    pThread->m_innerList.RemoveAll(); // clear contents

    if (!m_pEnum || pThread->m_strText.IsEmpty())
        return;

    // reload the items
    LPWSTR pszItem;
    ULONG cGot;
    HRESULT hr;
    CSimpleArray<CStringW>& innerList = pThread->m_innerList;
    while (!m_pThread && m_hThread)
    {
        // get next item
        hr = m_pEnum->Next(1, &pszItem, &cGot);
        if (hr != S_OK)
            break;

        innerList.Add(pszItem); // append item to innerList
        ::CoTaskMemFree(pszItem); // free
    }
}

VOID CAutoComplete::StartCompletion(BOOL bAppendOK)
{
    TRACE("CAutoComplete::StartCompletion(%p, %d)\n", this, bAppendOK);

    if (!m_pEnum || (!CanAutoSuggest() && !CanAutoAppend()))
        return;

    ::SendMessageW(m_hWnd, AUTOCOMP_START, bAppendOK, 0);
}

//////////////////////////////////////////////////////////////////////////////
// CAutoComplete message handlers

// WM_CREATE
// This message is sent when the window is about to be created after WM_NCCREATE.
// The return value is -1 (failure) or zero (success).
LRESULT CAutoComplete::OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
{
    TRACE("CAutoComplete::OnCreate(%p)\n", this);

    // set the pointer of CAutoComplete
    m_hwndList.m_pDropDown = this;
    m_hwndScrollBar.m_pDropDown = this;
    m_hwndSizeBox.m_pDropDown = this;

    // create the children
    m_hwndList.Create(m_hWnd);
    if (!m_hwndList)
        return -1; // failure
    m_hwndSizeBox.Create(m_hWnd);
    if (!m_hwndSizeBox)
        return -1; // failure
    m_hwndScrollBar.Create(m_hWnd);
    if (!m_hwndScrollBar)
        return -1; // failure

    // show the controls
    m_hwndList.ShowWindow(SW_SHOWNOACTIVATE);
    m_hwndSizeBox.ShowWindow(SW_SHOWNOACTIVATE);
    m_hwndScrollBar.ShowWindow(SW_SHOWNOACTIVATE);

    // set the list font
    m_hFont = reinterpret_cast<HFONT>(::GetStockObject(DEFAULT_GUI_FONT));
    m_hwndList.SetFont(m_hFont);

    // add reference so we won't be deleted during message processing
    AddRef();
    return 0; // success
}

// WM_NCDESTROY
LRESULT CAutoComplete::OnNCDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
{
    TRACE("CAutoComplete::OnNCDestroy(%p)\n", this);

    // hide
    if (IsWindowVisible())
        HideDropDown();

    // clear CAutoComplete pointers
    m_hwndList.m_pDropDown = NULL;
    m_hwndScrollBar.m_pDropDown = NULL;
    m_hwndSizeBox.m_pDropDown = NULL;

    // destroy controls
    m_hwndList.DestroyWindow();
    m_hwndScrollBar.DestroyWindow();
    m_hwndSizeBox.DestroyWindow();

    // clean up
    m_hwndCombo = NULL;

    // Tell ATL to clean up
    bHandled = 0;

    return 0;
}

VOID CAutoComplete::OnFinalMessage(HWND)
{
    // The message loop is finished, now we can safely destruct!
    Release();
}

// WM_EXITSIZEMOVE
// This message is sent once to a window after it has exited the moving or sizing mode.
LRESULT CAutoComplete::OnExitSizeMove(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
{
    TRACE("CAutoComplete::OnExitSizeMove(%p)\n", this);
    m_bResized = TRUE; // remember resized

    ModifyStyle(WS_THICKFRAME, 0); // remove thick frame to resize
    // frame changed
    UINT uSWP_ = SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED | SWP_NOACTIVATE;
    SetWindowPos(NULL, 0, 0, 0, 0, uSWP_);

    ::SetFocus(m_hwndEdit); // restore focus
    return 0;
}

// WM_DRAWITEM @implemented
// This message is sent to the owner window to draw m_hwndList.
LRESULT CAutoComplete::OnDrawItem(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
{
    LPDRAWITEMSTRUCT pDraw = reinterpret_cast<LPDRAWITEMSTRUCT>(lParam);
    ATLASSERT(pDraw != NULL);
    ATLASSERT(m_hwndList.GetStyle() & LVS_OWNERDRAWFIXED);

    // sanity check
    if (pDraw->CtlType != ODT_LISTVIEW || pDraw->hwndItem != m_hwndList)
        return FALSE;

    // item rectangle
    RECT rcItem = pDraw->rcItem;

    // get info
    UINT iItem = pDraw->itemID; // the index of item
    CStringW strItem = m_hwndList.GetItemText(iItem); // get text of item

    // draw background and set text color
    HDC hDC = pDraw->hDC;
    BOOL bSelected = (pDraw->itemState & ODS_SELECTED);
    if (bSelected)
    {
        ::FillRect(hDC, &rcItem, ::GetSysColorBrush(COLOR_HIGHLIGHT));
        ::SetTextColor(hDC, ::GetSysColor(COLOR_HIGHLIGHTTEXT));
    }
    else
    {
        ::FillRect(hDC, &rcItem, ::GetSysColorBrush(COLOR_WINDOW));
        ::SetTextColor(hDC, ::GetSysColor(COLOR_WINDOWTEXT));
    }

    // draw text
    rcItem.left += ::GetSystemMetrics(SM_CXBORDER);
    HGDIOBJ hFontOld = ::SelectObject(hDC, m_hFont);
    const UINT uDT_ = DT_LEFT | DT_NOPREFIX | DT_SINGLELINE | DT_VCENTER;
    ::SetBkMode(hDC, TRANSPARENT);
    ::DrawTextW(hDC, strItem, -1, &rcItem, uDT_);
    ::SelectObject(hDC, hFontOld);

    return TRUE;
}

// WM_GETMINMAXINFO @implemented
// This message is sent to a window when the size or position of the window is about to change.
LRESULT CAutoComplete::OnGetMinMaxInfo(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
{
    // restrict minimum size
    LPMINMAXINFO pInfo = reinterpret_cast<LPMINMAXINFO>(lParam);
    pInfo->ptMinTrackSize.x = ::GetSystemMetrics(SM_CXVSCROLL);
    pInfo->ptMinTrackSize.y = ::GetSystemMetrics(SM_CYHSCROLL);
    return 0;
}

// WM_MEASUREITEM @implemented
// This message is sent to the owner window to get the item extent of m_hwndList.
LRESULT CAutoComplete::OnMeasureItem(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
{
    LPMEASUREITEMSTRUCT pMeasure = reinterpret_cast<LPMEASUREITEMSTRUCT>(lParam);
    ATLASSERT(pMeasure != NULL);
    if (pMeasure->CtlType != ODT_LISTVIEW)
        return FALSE;
    if (!m_hwndList)
        return FALSE;
    ATLASSERT(m_hwndList.GetStyle() & LVS_OWNERDRAWFIXED);
    pMeasure->itemHeight = m_hwndList.m_cyItem; // height of item
    return TRUE;
}

// WM_MOUSEACTIVATE @implemented
// The return value of this message specifies whether the window should be activated or not.
LRESULT CAutoComplete::OnMouseActivate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
{
    return MA_NOACTIVATE; // don't activate by mouse
}

// WM_NCACTIVATE
// This message is sent to a window to indicate an active or inactive state.
LRESULT CAutoComplete::OnNCActivate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
{
    bHandled = FALSE; // do default
    return 0;
}

// WM_NCLBUTTONDOWN
LRESULT CAutoComplete::OnNCLButtonDown(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
{
    switch (wParam)
    {
        case HTBOTTOMRIGHT: case HTTOPRIGHT:
        {
            // add thick frame to resize.
            ModifyStyle(0, WS_THICKFRAME);
            // frame changed
            UINT uSWP_ = SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED | SWP_NOACTIVATE;
            SetWindowPos(NULL, 0, 0, 0, 0, uSWP_);
            break;
        }
    }
    bHandled = FALSE; // do default
    return 0;
}

// WM_NOTIFY
// This message informs the parent window of a control that an event has occurred.
LRESULT CAutoComplete::OnNotify(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
{
    LPNMHDR pnmh = reinterpret_cast<LPNMHDR>(lParam);
    ATLASSERT(pnmh != NULL);

    switch (pnmh->code)
    {
        case NM_DBLCLK: // double-clicked
        {
            TRACE("NM_DBLCLK\n");
            HideDropDown();
            break;
        }
        case NM_HOVER: // mouse is hovering
        {
            POINT pt;
            ::GetCursorPos(&pt); // get cursor position in screen coordinates
            m_hwndList.ScreenToClient(&pt); // into client coordinates
            INT iItem = m_hwndList.ItemFromPoint(pt.x, pt.y);
            if (iItem != -1)
            {
                m_bInSelectItem = TRUE; // don't respond
                m_hwndList.SetCurSel(iItem); // select
                m_bInSelectItem = FALSE;
            }
            return TRUE; // eat
        }
        case LVN_GETDISPINFOA: // for user's information only
        {
            TRACE("LVN_GETDISPINFOA\n");
            if (pnmh->hwndFrom != m_hwndList)
                break;

            LV_DISPINFOA *pDispInfo = reinterpret_cast<LV_DISPINFOA *>(pnmh);
            LV_ITEMA *pItem = &pDispInfo->item;
            INT iItem = pItem->iItem;
            if (iItem == -1)
                break;

            CStringW strText = GetItemText(iItem);
            if (pItem->mask & LVIF_TEXT)
                SHUnicodeToAnsi(strText, pItem->pszText, pItem->cchTextMax);
            break;
        }
        case LVN_GETDISPINFOW: // for user's information only
        {
            TRACE("LVN_GETDISPINFOW\n");
            if (pnmh->hwndFrom != m_hwndList)
                break;

            LV_DISPINFOW *pDispInfo = reinterpret_cast<LV_DISPINFOW *>(pnmh);
            LV_ITEMW *pItem = &pDispInfo->item;
            INT iItem = pItem->iItem;
            if (iItem == -1)
                break;

            CStringW strText = GetItemText(iItem);
            if (pItem->mask & LVIF_TEXT)
                StringCbCopyW(pItem->pszText, pItem->cchTextMax, strText);
            break;
        }
        case LVN_HOTTRACK: // enabled by LVS_EX_TRACKSELECT
        {
            TRACE("LVN_HOTTRACK\n");
            LPNMLISTVIEW pListView = reinterpret_cast<LPNMLISTVIEW>(pnmh);
            INT iItem = pListView->iItem;
            TRACE("LVN_HOTTRACK: iItem:%d\n", iItem);
            m_hwndList.SetCurSel(iItem);
            m_hwndList.EnsureVisible(iItem, FALSE);
            return TRUE;
        }
        case LVN_ITEMACTIVATE: // enabled by LVS_EX_ONECLICKACTIVATE
        {
            TRACE("LVN_ITEMACTIVATE\n");
            LPNMITEMACTIVATE pItemActivate = reinterpret_cast<LPNMITEMACTIVATE>(pnmh);
            INT iItem = pItemActivate->iItem;
            TRACE("LVN_ITEMACTIVATE: iItem:%d\n", iItem);
            if (iItem != -1) // the item is clicked
            {
                SelectItem(iItem);
                HideDropDown();
            }
            break;
        }
        case LVN_ITEMCHANGED: // item info is changed
        {
            TRACE("LVN_ITEMCHANGED\n");
            LPNMLISTVIEW pListView = reinterpret_cast<LPNMLISTVIEW>(pnmh);
            if (pListView->uChanged & LVIF_STATE) // selection changed
            {
                // listview selection changed
                if (!m_bInSelectItem)
                {
                    OnListSelChange();
                }
                UpdateScrollBar();
            }
            break;
        }
    }

    return 0;
}

// WM_NCHITTEST @implemented
// The return value is indicating the cursor shape and the behaviour.
LRESULT CAutoComplete::OnNCHitTest(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
{
    TRACE("CAutoComplete::OnNCHitTest(%p)\n", this);
    POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; // in screen coordinates
    ScreenToClient(&pt); // into client coordinates
    if (ChildWindowFromPoint(pt) == m_hwndSizeBox) // hit?
    {
        // allow resizing (with cursor shape)
        return m_bDowner ? HTBOTTOMRIGHT : HTTOPRIGHT;
    }
    bHandled = FALSE; // do default
    return 0;
}

// WM_SIZE @implemented
// This message is sent when the window size is changed.
LRESULT CAutoComplete::OnSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
{
    // calculate the positions of the controls
    CRect rcList, rcScrollBar, rcSizeBox;
    CalcRects(m_bDowner, rcList, rcScrollBar, rcSizeBox);

    // reposition the controls in smartest way
    UINT uSWP_ = SWP_NOACTIVATE | SWP_NOCOPYBITS;
    HDWP hDWP = ::BeginDeferWindowPos(3);
    hDWP = ::DeferWindowPos(hDWP, m_hwndScrollBar, HWND_TOP,
                            rcScrollBar.left, rcScrollBar.top,
                            rcScrollBar.Width(), rcScrollBar.Height(), uSWP_);
    hDWP = ::DeferWindowPos(hDWP, m_hwndSizeBox, m_hwndScrollBar,
                            rcSizeBox.left, rcSizeBox.top,
                            rcSizeBox.Width(), rcSizeBox.Height(), uSWP_);
    hDWP = ::DeferWindowPos(hDWP, m_hwndList, m_hwndSizeBox,
                            rcList.left, rcList.top,
                            rcList.Width(), rcList.Height(), uSWP_);
    ::EndDeferWindowPos(hDWP);

    UpdateScrollBar();
    return 0;
}

// WM_SHOWWINDOW
LRESULT CAutoComplete::OnShowWindow(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
{
    // hook mouse events
    BOOL bShow = (BOOL)wParam;
    if (bShow)
    {
        if (s_hWatchWnd != m_hWnd && ::IsWindowVisible(s_hWatchWnd))
            ::ShowWindowAsync(s_hWatchWnd, SW_HIDE);
        s_hWatchWnd = m_hWnd; // watch this

        // unhook mouse if any
        if (s_hMouseHook)
        {
            HHOOK hHookOld = s_hMouseHook;
            s_hMouseHook = NULL;
            ::UnhookWindowsHookEx(hHookOld);
        }

        // hook mouse
        s_hMouseHook = ::SetWindowsHookEx(WH_MOUSE, MouseProc, NULL, ::GetCurrentThreadId());
        ATLASSERT(s_hMouseHook != NULL);

        // set timer
        SetTimer(WATCH_TIMER_ID, WATCH_INTERVAL, NULL);

        bHandled = FALSE; // do default
        return 0;
    }
    else
    {
        // kill timer
        KillTimer(WATCH_TIMER_ID);

        s_hWatchWnd = NULL; // unwatch

        // unhook mouse if any
        if (s_hMouseHook)
        {
            HHOOK hHookOld = s_hMouseHook;
            s_hMouseHook = NULL;
            ::UnhookWindowsHookEx(hHookOld);
        }

        LRESULT ret = DefWindowProcW(uMsg, wParam, lParam); // do default

        if (m_hwndCombo)
            ::InvalidateRect(m_hwndCombo, NULL, TRUE); // redraw

        m_outerList.RemoveAll(); // no use
        return ret;
    }
}

// WM_TIMER
LRESULT CAutoComplete::OnTimer(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
{
    if (wParam != WATCH_TIMER_ID) // sanity check
        return 0;

    // if the textbox is dead, then kill the timer
    if (!::IsWindow(m_hwndEdit))
    {
        KillTimer(WATCH_TIMER_ID);
        return 0;
    }

    // m_hwndEdit is moved?
    RECT rcEdit;
    ::GetWindowRect(m_hwndEdit, &rcEdit);
    if (!::EqualRect(&rcEdit, &m_rcEdit))
    {
        // if so, hide
        HideDropDown();

        m_rcEdit = rcEdit; // update rectangle
        m_bResized = FALSE; // clear flag
    }

    return 0;
}

// WM_VSCROLL
// This message is sent when a scroll event occurs.
LRESULT CAutoComplete::OnVScroll(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
{
    TRACE("CAutoComplete::OnVScroll(%p)\n", this);
    WORD code = LOWORD(wParam);
    switch (code)
    {
        case SB_THUMBPOSITION: case SB_THUMBTRACK:
        {
            // get the scrolling info
            INT nPos = HIWORD(wParam);
            SCROLLINFO si = { sizeof(si), SIF_ALL };
            m_hwndList.GetScrollInfo(SB_VERT, &si);

            // scroll the list-view by CListView::EnsureVisible
            INT cItems = m_hwndList.GetItemCount();
            // iItem : cItems == (nPos - si.nMin) : (si.nMax - si.nMin).
            INT iItem = cItems * (nPos - si.nMin) / (si.nMax - si.nMin);
            if (nPos > si.nPos)
            {
                iItem += m_hwndList.GetVisibleCount();
                if (iItem >= cItems)
                    iItem = cItems - 1;
            }
            m_hwndList.EnsureVisible(iItem, FALSE);

            // update scrolling position of m_hwndScrollBar
            si.fMask = SIF_POS;
            m_hwndList.GetScrollInfo(SB_VERT, &si);
            m_hwndScrollBar.SetScrollInfo(SB_VERT, &si, FALSE);
            break;
        }
        default:
        {
            // pass it to m_hwndList
            m_hwndList.SendMessageW(WM_VSCROLL, wParam, lParam);
            UpdateScrollBar();
            break;
        }
    }
    return 0;
}

static inline PAC_THREAD
InterlockedExchangeThreadData(volatile PAC_THREAD *Target, PAC_THREAD Value)
{
    return reinterpret_cast<PAC_THREAD>(
        ::InterlockedExchangePointer(reinterpret_cast<volatile PVOID *>(Target), Value));
}

static unsigned __stdcall AutoCompThreadProc(void *arg)
{
    CAutoComplete* pThis = reinterpret_cast<CAutoComplete*>(arg);
    pThis->AutoCompThreadProc();
    return 0;
}

VOID CAutoComplete::AutoCompThreadProc()
{
    for (;;)
    {
        PAC_THREAD pThread = InterlockedExchangeThreadData(&m_pThread, NULL);
        if (!pThread)
            break;
        DoThreadWork(pThread);
    }
}

VOID CAutoComplete::DoThreadWork(PAC_THREAD pThread)
{
    if (pThread->m_bExpand || m_innerList.GetSize() == 0)
    {
        ReLoadInnerList(pThread);
    }
    else
    {
        pThread->m_innerList = m_innerList;
    }

    if (m_pThread || !m_hThread)
    {
        delete pThread;
        return;
    }

    ExtractInnerList(pThread->m_outerList, pThread->m_innerList, pThread->m_strText);

    if (m_pThread || !m_hThread)
    {
        delete pThread;
        return;
    }

    DoSort(pThread->m_outerList);
    DoUniqueAndTrim(pThread->m_outerList);

    if (m_pThread || !m_hThread ||
        !::PostMessageW(m_hWnd, AUTOCOMP_FINISH, 0, (LPARAM)pThread))
    {
        delete pThread;
    }
}

// AUTOCOMP_START
LRESULT CAutoComplete::OnAutoCompStart(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
{
    BOOL bAppendOK = (BOOL)wParam;

    CStringW strText = GetEditText();
    if (m_strText.CompareNoCase(strText) == 0)
    {
        // no change
        return 0;
    }

    PAC_THREAD pThread = new AC_THREAD { this, bAppendOK, strText };

    // if previous text was empty
    if (m_strText.IsEmpty())
    {
        pThread->m_bReset = TRUE;
    }
    m_strText = strText;

    // do expand the items if the stem is changed
    CStringW strStemText = GetStemText(pThread->m_strText);
    if (m_strStemText.CompareNoCase(strStemText) != 0)
    {
        pThread->m_bReset = TRUE;
        pThread->m_bExpand = !strStemText.IsEmpty();
        m_strStemText = strStemText;
    }

    // reset if necessary
    if (pThread->m_bReset && m_pEnum)
    {
        HRESULT hr = m_pEnum->Reset(); // IEnumString::Reset
        TRACE("m_pEnum->Reset(%p): 0x%08lx\n",
              static_cast<IUnknown *>(m_pEnum), hr);
    }

    // update ac list if necessary
    if (pThread->m_bExpand && m_pACList)
    {
        HRESULT hr = m_pACList->Expand(strStemText); // IACList::Expand
        TRACE("m_pACList->Expand(%p, %S): 0x%08lx\n",
              static_cast<IUnknown *>(m_pACList),
              static_cast<LPCWSTR>(strStemText), hr);
    }

    PAC_THREAD pOld = InterlockedExchangeThreadData(&m_pThread, pThread);
    if (pOld)
        delete pOld;

    BOOL bDoStart = FALSE;
    if (m_hThread)
    {
        if (WaitForSingleObject(m_hThread, 0) != WAIT_TIMEOUT)
        {
            CloseHandle(m_hThread);
            m_hThread = NULL;
            bDoStart = TRUE;
        }
    }
    else
    {
        bDoStart = TRUE;
    }

    if (bDoStart)
        m_hThread = (HANDLE)_beginthreadex(NULL, 0, ::AutoCompThreadProc, this, 0, NULL);

    return 0;
}

// AUTOCOMP_FINISH
LRESULT CAutoComplete::OnAutoCompFinish(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
{
    PAC_THREAD pThread = reinterpret_cast<PAC_THREAD>(lParam);
    if (m_pThread == NULL)
    {
        FinishCompletion(pThread);
    }
    CloseHandle(m_hThread);
    m_hThread = NULL;
    delete pThread;
    return 0;
}

VOID CAutoComplete::FinishCompletion(PAC_THREAD pThread)
{
    if (m_pThread || !m_hThread)
        return;

    if (!CanAutoSuggest() && !CanAutoAppend())
        return;

    // set inner list
    m_innerList = pThread->m_innerList;

    // set the items of the virtual listview
    m_outerList = pThread->m_outerList; // FIXME: We need more speed!
    m_hwndList.SendMessageW(LVM_SETITEMCOUNT, m_outerList.GetSize(), 0);

    // save text
    m_strText = pThread->m_strText;
    m_strStemText = GetStemText(m_strText);

    if (CanAutoSuggest()) // can we auto-suggest?
    {
        m_bInSelectItem = TRUE; // don't respond
        SelectItem(-1); // select none
        m_bInSelectItem = FALSE;

        if (m_outerList.GetSize() > 0)
            RepositionDropDown();
        else
            HideDropDown();
    }

    if (CanAutoAppend() && pThread->m_bAppendOK) // can we auto-append?
    {
        DoAutoAppend(pThread);
    }
}
