#include "private.h"
#include "offline.h"
#include "updateui.h"

//xnotfmgr - most of this file can probably get nuked

#define MAX_CAPTION     128

#undef  TF_THISMODULE
#define TF_THISMODULE   TF_UPDATEAGENT

typedef CLSID   COOKIE, *PCOOKIE;

#define SUBITEM_SIZE    4
#define SUBITEM_URL     3
#define SUBITEM_STATUS  2
#define SUBITEM_IMAGE   1

ColInfoType colDlg[] = {
    {0, IDS_NAME_COL,   30, LVCFMT_LEFT},
    {1, 0,              3,  LVCFMT_LEFT}, 
    {2, IDS_STATUS_COL, 10, LVCFMT_LEFT},
    {3, IDS_URL_COL,    40, LVCFMT_LEFT},
    {4, IDS_SIZE_COL,   7,  LVCFMT_RIGHT}
};

#define ILI_SUCCEEDED       0
#define ILI_FAILED          1
#define ILI_UPDATING        2
#define ILI_PENDING         3
#define ILI_SKIPPED         4
#define ILI_SITE            5
#define ILI_CHANNEL         6
#define ILI_DESKTOP         7

const int g_aIconResourceID[] = {
        IDI_STAT_SUCCEEDED,
        IDI_STAT_FAILED, 
        IDI_STAT_UPDATING,
        IDI_STAT_PENDING, 
        IDI_STAT_SKIPPED,
        IDI_WEBDOC,
        IDI_CHANNEL,
        IDI_DESKTOPITEM
};

#define MAX_DLG_COL     ARRAYSIZE(colDlg)

//struct for saving window state in registry
typedef struct _PROG_PERSIST_STATE
{
    short cbSize;
    char bDetails;
    char bAdjustWindowPos;
    RECT rWindow;
    int colOrder [MAX_DLG_COL];
    int colWidth [MAX_DLG_COL];
} PROG_PERSIST_STATE;
extern void ResizeDialog(HWND hDlg, BOOL bShowDetail);  //in update.cpp

const TCHAR c_szProgressWindowSettings[] = TEXT("Progress Preferences");

const UINT CookieSeg = 32;

CCookieItemMap::CCookieItemMap()
{
    _map = NULL;
}

CCookieItemMap::~CCookieItemMap()
{
    SAFELOCALFREE (_map);
}

STDMETHODIMP CCookieItemMap::Init(UINT size)
{
    //  Free old junk.
    SAFELOCALFREE (_map);

    _lParamNext = 0;
    _count = 0;

    if (size == 0)
        _capacity = CookieSeg;
    else
        _capacity = size;


    _map = (CookieItemMapEntry * )MemAlloc(LPTR, sizeof (CookieItemMapEntry) * _capacity);
    if (!_map)  {
        DBG("Failed to allocate memory");
        _capacity = 0;
        return E_OUTOFMEMORY;
    }

    
    return S_OK;
}

STDMETHODIMP CCookieItemMap::ResetMap(void)
{
    _count = 0;
    return S_OK;
}

STDMETHODIMP CCookieItemMap::FindCookie(LPARAM lParam, CLSID * pCookie)
{
    ASSERT(pCookie);

    UINT    i;
    for (i = 0; i < _count; i ++)   {
        if (_map[i]._id == lParam)    {
            * pCookie = _map[i]._cookie;
            return S_OK;
        }
    }

    *pCookie = CLSID_NULL;
    return E_FAIL;
}

STDMETHODIMP CCookieItemMap::FindLParam(CLSID * pCookie, LPARAM * pLParam)
{
    ASSERT(pCookie && pLParam);

    UINT    i;
    for (i = 0; i < _count; i ++)   {
        if (_map[i]._cookie == *pCookie)    {
            * pLParam = _map[i]._id;
            return S_OK;
        }
    }

    *pLParam = (LPARAM)-1;
    return E_FAIL;
}

STDMETHODIMP CCookieItemMap::AddCookie(CLSID * pCookie, LPARAM * pLParam)
{
    HRESULT hr = FindLParam(pCookie, pLParam);
    if (S_OK == hr)
        return S_FALSE;

    ASSERT(_count <= _capacity);
    ASSERT(CookieSeg);

    if (_count == _capacity)   {
        UINT    newSize = CookieSeg + _capacity;
        void * newBuf = MemReAlloc(_map, newSize * sizeof(CookieItemMapEntry), LHND);

        if (!newBuf)    {
            DBG("AddCookie::Failed to reallocate buffer");
            return E_OUTOFMEMORY;
        }

        _map = (CookieItemMapEntry *)newBuf;
        _capacity = newSize;
    }

    _map[_count]._cookie = *pCookie;
    _map[_count]._id = _lParamNext;
    _count ++;

    *pLParam = _lParamNext;
    _lParamNext ++;
    return S_OK;
}

STDMETHODIMP CCookieItemMap::DelCookie(CLSID * pCookie)
{
    ASSERT(pCookie);

    UINT    i;
    for (i = 0; i < _count; i ++)   {
        if (_map[i]._cookie == *pCookie)    {
            if (i == (_count - 1))  {
                _count --;
                return S_OK;
            } else  {
                _count --;
                _map[i]._cookie = _map[_count]._cookie;
                _map[i]._id = _map[_count]._id;
                return S_OK;
            }
        }
    }

    return S_FALSE;
}

///////////////////////////////////////////////////////////////////////////
//
// Other members
//

int CALLBACK CUpdateDialog::SortUpdatingToTop  (LPARAM lParam1,
                                                LPARAM lParam2,
                                                LPARAM lParamSort)
{
    //lparams are cookies; lparamsort is source object
    CUpdateDialog * pUpdater = (CUpdateDialog*)lParamSort;
    if (!pUpdater)
        return 0;
    if (!pUpdater->m_pController)
        return 0;

    CLSID cookie;
    pUpdater->cookieMap.FindCookie (lParam1, &cookie);
    PReportMap pEntry1 = pUpdater->m_pController->FindReportEntry (&cookie);
    pUpdater->cookieMap.FindCookie (lParam2, &cookie);
    PReportMap pEntry2 = pUpdater->m_pController->FindReportEntry (&cookie);
    
    //in progress precedes all else
    if (pEntry1->status == ITEM_STAT_UPDATING)
        return (pEntry2->status == ITEM_STAT_UPDATING ? 0 : -1);

    if (pEntry2->status == ITEM_STAT_UPDATING)
        return 1;

    //queued precedes succeeded or skipped
    if (pEntry1->status == ITEM_STAT_QUEUED || pEntry1->status == ITEM_STAT_PENDING)
        return ((pEntry2->status == ITEM_STAT_QUEUED
              || pEntry2->status == ITEM_STAT_PENDING) ? 0 : -1);

    if (pEntry2->status == ITEM_STAT_QUEUED || pEntry2->status == ITEM_STAT_PENDING)
        return 1;

    return 0;   //don't care
}


BOOL CUpdateDialog::SelectFirstUpdatingSubscription()
{
    LV_ITEM lvi = {0};
    lvi.iSubItem = SUBITEM_IMAGE;
    lvi.mask = LVIF_IMAGE;

    int cItems = ListView_GetItemCount (m_hLV);
    for (lvi.iItem = 0; lvi.iItem < cItems; lvi.iItem++)
    {
        ListView_GetItem (m_hLV, &lvi);
        if (lvi.iImage == ILI_UPDATING)
        {
            ListView_SetItemState (m_hLV, lvi.iItem, LVIS_SELECTED, LVIS_SELECTED);
            return TRUE;
        }
    }

    return FALSE;
}


DWORD CUpdateDialog::SetSiteDownloadSize (PCOOKIE pCookie, DWORD dwNewSize)
{
    //returns previous size, for bookkeeping purposes
    HRESULT             hr;

    TCHAR               szKSuffix[10];
    //  Need enough room for DWORD as string + K suffix
    TCHAR               szBuf[ARRAYSIZE(szKSuffix) + 11];

    if (dwNewSize == -1)            //shouldn't happen anymore but if it does,
        return -1;                  //deal gracefully

    ASSERT(pCookie);
    LPARAM itemParam;
    hr = cookieMap.FindLParam(pCookie, &itemParam);
    if (S_OK != hr)
    {
        return dwNewSize;
    }

    LV_ITEM     lvi = {0};
    LV_FINDINFO lvfi = {0};

    lvfi.flags = LVFI_PARAM;
    lvfi.lParam = itemParam;

    lvi.iItem = ListView_FindItem(m_hLV, -1, &lvfi);
    if (lvi.iItem == -1)
        return dwNewSize;

    lvi.iSubItem = SUBITEM_SIZE;
    lvi.mask = LVIF_TEXT;
    lvi.pszText = szBuf;
    lvi.cchTextMax = sizeof(szBuf);

    ListView_GetItem(m_hLV, &lvi);
    DWORD dwOldSize = StrToInt (szBuf);

    MLLoadString (IDS_SIZE_KB, szKSuffix, ARRAYSIZE(szKSuffix));
    wnsprintf (szBuf, ARRAYSIZE(szBuf), "%d%s", dwNewSize, szKSuffix);
    ListView_SetItem(m_hLV, &lvi);

    return dwOldSize;
}


//  This method is actually called from the second thread(only). So far
//  I haven't found any sync problem yet. We only change the internal state
//  of this object after it's creation in this method, so we won't mess
//  it up. About UI, there is a chance when we try to disable 'Skip'
//  button, we may come across another request from the primary thread. Since
//  these 2 requests are both designated to disable the button, it won't
//  matter anyway.

STDMETHODIMP CUpdateDialog::RefreshStatus(PCOOKIE pCookie, LPTSTR name, STATUS newStat, LPTSTR extraInfo)
{
    HRESULT             hr;
    TCHAR               szBuf[MAX_URL];
    
    ASSERT(pCookie);
    LPARAM itemParam;
    hr = cookieMap.FindLParam(pCookie, &itemParam);
    if (S_OK != hr) {
        if (name)  {
            hr = AddItem(pCookie, name, newStat);
            if (S_OK != hr) {
                return E_FAIL;
            }
            hr = cookieMap.FindLParam(pCookie, &itemParam);
            if (S_OK != hr) {
                ASSERT(0);
                return E_FAIL;
            }
        } else  {
            return hr;
        }
    }

    LV_ITEM     lvi = {0};
    LV_FINDINFO lvfi = {0};

    lvfi.flags = LVFI_PARAM;
    lvfi.lParam = itemParam;

    lvi.iItem = ListView_FindItem(m_hLV, -1, &lvfi);
    if (lvi.iItem == -1)
        return E_FAIL;

    lvi.iSubItem = SUBITEM_STATUS;
    lvi.mask = LVIF_TEXT;
    
    ASSERT ((UINT)newStat <= ITEM_STAT_ABORTED);
    if (newStat == ITEM_STAT_UPDATING && extraInfo != NULL) //url available, use it
    {
        TCHAR szFormat[MAX_URL];
        MLLoadString (IDS_ITEM_STAT_UPDATING_URL, szFormat, ARRAYSIZE(szFormat));
        wnsprintf (szBuf, ARRAYSIZE(szBuf), szFormat, extraInfo);
    }
    else
    {
        MLLoadString(IDS_ITEM_STAT_IDLE + newStat, szBuf, ARRAYSIZE(szBuf));
    }

    lvi.pszText = szBuf;
    ListView_SetItem(m_hLV, &lvi);

    lvi.iSubItem = SUBITEM_IMAGE;
    lvi.mask = LVIF_IMAGE;
    
    switch (newStat)  {
        case ITEM_STAT_QUEUED:
        case ITEM_STAT_PENDING:
            lvi.iImage = ILI_PENDING;
            break;
        case ITEM_STAT_UPDATING:
            lvi.iImage = ILI_UPDATING;
            //move to top of list -- t-mattgi
            //this happens in sort callback function -- just force resort
            //after we update the LV control
            break;
        case ITEM_STAT_SUCCEEDED:
            lvi.iImage = ILI_SUCCEEDED;
            if (ListView_GetItemState(m_hLV, lvi.iItem, LVIS_SELECTED))
                Button_Enable(GetDlgItem(m_hDlg, IDCMD_SKIP), FALSE);
            break;
        case ITEM_STAT_SKIPPED:
            if (ListView_GetItemState(m_hLV, lvi.iItem, LVIS_SELECTED))
                Button_Enable(GetDlgItem(m_hDlg, IDCMD_SKIP), FALSE);
            lvi.iImage = ILI_SKIPPED;
            break;
        default:
            lvi.iImage = ILI_FAILED;
            break;
    }
    ListView_SetItem(m_hLV, &lvi);

    //force resort since item statuses changed
    ListView_SortItems (m_hLV, SortUpdatingToTop, this);

    return hr;
}

CUpdateDialog::CUpdateDialog()
{
    m_bInitialized = FALSE;
}

CUpdateDialog::~CUpdateDialog()
{
}

STDMETHODIMP CUpdateDialog::CleanUp()
{
    if (! m_ThreadID || !m_bInitialized)
        return S_OK;

    if (m_hDlg)
    {
        PersistStateToRegistry (m_hDlg);

        DestroyWindow(m_hDlg);
        m_hDlg = NULL;
    }
    PostThreadMessage(m_ThreadID, WM_QUIT, 0, 0);
    return S_OK;
}

STDMETHODIMP CUpdateDialog::Init(HWND hParent, CUpdateController * pController)
{
    ASSERT(m_ThreadID);
    ASSERT(g_hInst);
    ASSERT(pController);
    if (m_bInitialized) {
        ASSERT(0);
        return S_FALSE;
    }

    if (FAILED(cookieMap.Init()))
        return E_FAIL;

    HWND hDlg, hLV;

    m_pController = pController;
    hDlg = CreateDialogParam(MLGetHinst(), MAKEINTRESOURCE(IDD_PROGRESS), hParent, UpdateDlgProc, (LPARAM)this);
    if (!hDlg)
        return E_FAIL;

    hLV = GetDlgItem(hDlg, IDL_SUBSCRIPTION);
    if (!hLV)   {
        EndDialog(hDlg, FALSE);
        return E_FAIL;
    }

    HIMAGELIST  hImage;
    HICON       hIcon;

    hImage = ImageList_Create(GetSystemMetrics(SM_CXSMICON),
                              GetSystemMetrics(SM_CXSMICON),
                              ILC_MASK,    
                              ARRAYSIZE(g_aIconResourceID),
                              0);

    if (hImage == NULL) {
        TraceMsg(TF_ALWAYS, TEXT("CUpdateDialog::Init - Failed to create ImageList"));
        return E_FAIL;
    }

    for (int i = 0; i < ARRAYSIZE(g_aIconResourceID); i ++) {
        if (g_aIconResourceID[i] == IDI_DESKTOPITEM)
        {
            hinstSrc = MLGetHinst();
        }
        else
        {
            hinstSrc = g_hInst;
        }
        
        hIcon = LoadIcon(hinstSrc, MAKEINTRESOURCE(g_aIconResourceID[i]));
        if (hIcon == NULL)  {
            ImageList_Destroy(hImage);
            DBG("CUpdateDialog::Init - Failed to load icon");
            return E_FAIL;
        }
        ImageList_AddIcon(hImage, hIcon);
        DestroyIcon(hIcon);
    }
                            
    ListView_SetImageList(hLV, hImage, LVSIL_SMALL);

    LV_COLUMN   lvc;
    TEXTMETRIC  tm;
    HDC         hdc;

    hdc = GetDC(hDlg);
    if (!hdc)   {
        EndDialog(hDlg, FALSE);
        return E_FAIL;
    }
    GetTextMetrics(hdc, &tm);
    ReleaseDC(hDlg, hdc);

    lvc.mask = LVCF_WIDTH | LVCF_TEXT | LVCF_FMT;

    PROG_PERSIST_STATE state;
    GetPersistentStateFromRegistry(state, tm.tmAveCharWidth);
    
    for (UINT ui = 0; ui < MAX_DLG_COL; ui ++)
    {
        int colIndex;

        TCHAR   szCaption[MAX_CAPTION];
        if (colDlg[ui].ids)
            MLLoadString(colDlg[ui].ids, szCaption, MAX_CAPTION);
        else
            szCaption[0] = (TCHAR)0;

        lvc.pszText = szCaption;
        lvc.cx = state.colWidth[ui];
        lvc.fmt = colDlg[ui].iFmt;
        colIndex = ListView_InsertColumn(hLV, ui, & lvc);
        if ( -1 == colIndex)    {
            ASSERT(0);
            EndDialog(hDlg, FALSE);
            return E_FAIL;
        }
    }

    ListView_SetColumnOrderArray(hLV, MAX_DLG_COL, state.colOrder);

    SendMessage (hLV, LVM_SETEXTENDEDLISTVIEWSTYLE,
        LVS_EX_HEADERDRAGDROP | LVS_EX_SUBITEMIMAGES,
        LVS_EX_HEADERDRAGDROP | LVS_EX_SUBITEMIMAGES);

    SendMessage (hDlg, WM_SETICON, ICON_BIG, (LPARAM)LoadIcon (g_hInst, MAKEINTRESOURCE (IDI_DOWNLOAD)));
    SendMessage (hDlg, WM_SETICON, ICON_SMALL, (LPARAM)LoadIcon (g_hInst, MAKEINTRESOURCE (IDI_DOWNLOAD)));

    if (state.bAdjustWindowPos)
    {
        //adjust size of *details view* to stored state; if we're not in
        //details view, we have to go there temporarily.  (The non-details
        //view will pick up the position from the details view when we switch back.)
        m_bDetail = TRUE;
        ResizeDialog (hDlg, m_bDetail);

        //don't move dialog, just resize it and center it
        //convert right, bottom coordinates to width, height
        state.rWindow.right -= state.rWindow.left;
        state.rWindow.bottom -= state.rWindow.top;
        //calculate left, top to center dialog
        state.rWindow.left = (GetSystemMetrics (SM_CXSCREEN) - state.rWindow.right) / 2;
        state.rWindow.top = (GetSystemMetrics (SM_CYSCREEN) - state.rWindow.bottom) / 2;
        MoveWindow (hDlg, state.rWindow.left, state.rWindow.top,
                    state.rWindow.right, state.rWindow.bottom, TRUE);

        //REVIEW: this centers the details view, then if they don't want details,
        //leaves the small dialog with its upper left where the upper left of the
        //big dialog is when it's centered.  I could center it in whatever view
        //it's really in, but if the details view is resized to a fairly large window
        //and we bring it up centered in non-details, then when they click details
        //the position will be the same and the window will potentially extend offscreen
        //to the right and bottom.

        //set back to non-details view if that was how it was last used
        if (!state.bDetails)
        {
            m_bDetail = state.bDetails;
            ResizeDialog (hDlg, m_bDetail);
        }
    }
    
    m_bInitialized = TRUE;
    m_hDlg = hDlg;
    m_hLV  = hLV;
    m_hParent = hParent;
    m_cDlKBytes = 0;
    m_cDlDocs = 0;

    return S_OK;
}


BOOL CUpdateDialog::PersistStateToRegistry (HWND hDlg)
{
    PROG_PERSIST_STATE state;

    state.cbSize = sizeof(state);
    state.bDetails = m_bDetail;
    state.bAdjustWindowPos = TRUE;
    //save position and size from *details view* -- if we're not there,
    //we'll have to switch temporarily.
    BOOL bTempDetail = m_bDetail;
    if (!bTempDetail)
    {
        ShowWindow (hDlg, SW_HIDE);
        m_bDetail = TRUE;
        ResizeDialog (hDlg, m_bDetail);
    }
    GetWindowRect (hDlg, &state.rWindow);
    if (!bTempDetail)
    {
        m_bDetail = FALSE;
        ResizeDialog (hDlg, m_bDetail);
        ShowWindow (hDlg, SW_SHOW);
    }

    HWND hLV = GetDlgItem (hDlg, IDL_SUBSCRIPTION);
    ListView_GetColumnOrderArray (hLV, MAX_DLG_COL, state.colOrder);
    for (int i=0; i<MAX_DLG_COL; i++)
        state.colWidth[i] = ListView_GetColumnWidth (hLV, i);

    HKEY key;
    DWORD dwDisposition;
    if (ERROR_SUCCESS != RegCreateKeyEx (HKEY_CURRENT_USER, c_szRegKey, 0, NULL, REG_OPTION_NON_VOLATILE,
                                         KEY_WRITE, NULL, &key, &dwDisposition))
        return FALSE;

    RegSetValueEx (key, c_szProgressWindowSettings, 0, REG_BINARY, (LPBYTE)&state, sizeof(state));

    RegCloseKey (key);

    return TRUE;
}

BOOL CUpdateDialog::GetPersistentStateFromRegistry (PROG_PERSIST_STATE& state, int iCharWidth)
{
    HKEY key;
    DWORD dwType;
    DWORD dwSize = sizeof(state);
    RegOpenKeyEx (HKEY_CURRENT_USER, c_szRegKey, 0, KEY_READ, &key);
    
    LONG result = RegQueryValueEx (key, c_szProgressWindowSettings,
            0, &dwType, (LPBYTE)&state, &dwSize);

    if (ERROR_SUCCESS != result || dwType != REG_BINARY || dwSize != sizeof(state))
    {
        state.cbSize = 0;       //flag as error
    }

    if (state.cbSize != sizeof(state))  //error or incorrect registry format/version
    {   //state not saved in registry; use defaults
        int i;

        state.bDetails = FALSE;
        state.bAdjustWindowPos = FALSE;

        state.colOrder[0] = 1;
        state.colOrder[1] = 0;
        for (i=2; i<MAX_DLG_COL; i++)
            state.colOrder[i] = i;

        for (i=0; i<MAX_DLG_COL; i++)
            state.colWidth[i] = colDlg[i].cchCol * iCharWidth;
    }

    RegCloseKey (key);
    return TRUE;
}


STDMETHODIMP CUpdateDialog::Show(BOOL bShow)
{
    if (!m_bInitialized)    {
        ASSERT(0);
        return E_FAIL;
    } 

    ASSERT(m_hDlg);

    ShowWindow(m_hDlg, (bShow)?SW_SHOW:SW_HIDE);
    ShowWindow(m_hLV, (bShow)?SW_SHOW:SW_HIDE);
    return NOERROR;
}

STDMETHODIMP CUpdateDialog::ResetDialog(void)
{
    if (!m_bInitialized)    {
        return S_OK;
    }

    ASSERT(m_hLV);
    ListView_DeleteAllItems(m_hLV);
    cookieMap.ResetMap();
    m_bInitialized = FALSE;

    return S_OK;
}

STDMETHODIMP CUpdateDialog::IItem2Cookie(const int iItem, CLSID * pCookie)
{
    LV_ITEM item = {0};
    HRESULT hr;
    ASSERT(pCookie);

    item.iItem = iItem;
    item.iSubItem = 0;
    item.mask = LVIF_PARAM;

    if (!ListView_GetItem(m_hLV, &item))    {
        return E_FAIL;
    }
    
    hr = cookieMap.FindCookie(item.lParam, pCookie);
    ASSERT(SUCCEEDED(hr));

    return hr;
}

STDMETHODIMP CUpdateDialog::GetSelectedCookies(CLSID * pCookies, UINT * pCount)
{
    if (!m_bInitialized)
        return E_INVALIDARG;
    
    ASSERT(pCookies && pCount);
    int     index = -1;

    *pCount = 0;

    index = ListView_GetNextItem(m_hLV, index, LVNI_ALL | LVNI_SELECTED);
    while (-1 != index) {
        if (FAILED(IItem2Cookie(index, pCookies + *pCount)))    {
            ASSERT(0);
            return S_FALSE;
        }

        index = ListView_GetNextItem(m_hLV, index, LVNI_ALL | LVNI_SELECTED);
        *pCount = *pCount + 1;
    }

    return S_OK;
}

STDMETHODIMP CUpdateDialog::GetSelectionCount(UINT * pCount)
{
    if (!m_bInitialized)
        return E_INVALIDARG;

    ASSERT(pCount);
    * pCount = ListView_GetSelectedCount(m_hLV);

    return S_OK;
}

STDMETHODIMP CUpdateDialog::AddItem(CLSID * pCookie, LPTSTR name, STATUS stat)
{
    HRESULT             hr;
    TCHAR               szBuf[MAX_URL];

    LV_ITEM lvi = {0};
    BOOL    bNew;

    lvi.iSubItem = 0;
    hr = cookieMap.AddCookie(pCookie, &(lvi.lParam));
    if (S_OK == hr) {
        bNew = TRUE;
    } else if (S_FALSE == hr)   {
        bNew = FALSE;
    } else  {
        return hr;
    }

    lvi.pszText = name;
    if (bNew)   {
        lvi.mask = LVIF_TEXT | LVIF_PARAM | LVIF_IMAGE;
        switch (m_pController->GetSubscriptionType(pCookie))
        {
        case SUBSTYPE_CHANNEL:
            lvi.iImage = ILI_CHANNEL;
            break;
        case SUBSTYPE_DESKTOPURL:
        case SUBSTYPE_DESKTOPCHANNEL:
            lvi.iImage = ILI_DESKTOP;
            break;
        case SUBSTYPE_URL:
        default:
            lvi.iImage = ILI_SITE;
            break;
        }
        lvi.iItem = ListView_InsertItem(m_hLV, &lvi);
        if (lvi.iItem == -1)
            return E_FAIL;
        if (lvi.iItem == 0) {
            ListView_SetItemState(m_hLV, 0, LVIS_SELECTED, LVIS_SELECTED);
        }
    } else  {
        LV_FINDINFO lvfi = {0};

        lvfi.flags = LVFI_PARAM;
        lvfi.lParam = lvi.lParam;
    
        lvi.iItem = ListView_FindItem(m_hLV, -1, &lvfi);
        if (lvi.iItem == -1)
            return E_FAIL;

        lvi.mask = LVIF_TEXT;
        ListView_SetItem(m_hLV, &lvi);
    }

    //add subitem for status icon
    lvi.mask = LVIF_IMAGE;
    lvi.iSubItem ++;    //  Icon field.
    switch (stat)  {
        case ITEM_STAT_QUEUED:
        case ITEM_STAT_PENDING:
            lvi.iImage = ILI_PENDING;
            break;
        case ITEM_STAT_UPDATING:
            lvi.iImage = ILI_UPDATING;
            break;
        case ITEM_STAT_SUCCEEDED:
            lvi.iImage = ILI_SUCCEEDED;
            if (ListView_GetItemState(m_hLV, lvi.iItem, LVIS_SELECTED))
                Button_Enable(GetDlgItem(m_hDlg, IDCMD_SKIP), FALSE);
            break;
        case ITEM_STAT_SKIPPED:
            lvi.iImage = ILI_SKIPPED;
            if (ListView_GetItemState(m_hLV, lvi.iItem, LVIS_SELECTED))
                Button_Enable(GetDlgItem(m_hDlg, IDCMD_SKIP), FALSE);
        default:
            lvi.iImage = ILI_FAILED;
            break;
    }
    ListView_SetItem(m_hLV, &lvi);

    //add subitem for status text
    lvi.mask = LVIF_TEXT;
    ASSERT ((UINT)stat <= ITEM_STAT_SUCCEEDED);
    MLLoadString(IDS_ITEM_STAT_IDLE + stat, szBuf, ARRAYSIZE(szBuf));
    lvi.pszText = szBuf;
    lvi.iSubItem ++;
    ListView_SetItem(m_hLV, &lvi);

    //add subitem for URL
    PReportMap prm = m_pController->FindReportEntry (pCookie);
    lvi.pszText = prm->url;
    lvi.iSubItem++;
    ListView_SetItem(m_hLV, &lvi);

    //add subitem for size
    lvi.pszText = TEXT("");
    lvi.iSubItem++;
    ListView_SetItem(m_hLV, &lvi);

    return S_OK;
}