/**************************************************************\
    FILE: addrlist.cpp

    DESCRIPTION:
        This is a class that all Address Lists can inherite
    from.  This will give them the IAddressList interface
    so they can work in the AddressBand/Bar.
\**************************************************************/

#include "priv.h"
#include "util.h"
#include "itbdrop.h"
#include "autocomp.h"
#include "addrlist.h"
#include "apithk.h"
#include "shui.h"
#include "shlguid.h"

CAddressList::CAddressList() : _cRef(1)
{
    // This needs to be allocated in Zero Inited Memory.
    // Assert that all Member Variables are inited to Zero.
    ASSERT(!_pbp);
}

CAddressList::~CAddressList()
{
    if (_pbp)
        _pbp->Release();
    if (_pbs)
        _pbs->Release();
    if (_pshuUrl)
        delete _pshuUrl;
}

ULONG CAddressList::AddRef()
{
    _cRef++;
    return _cRef;
}

ULONG CAddressList::Release()
{
    ASSERT(_cRef > 0);
    _cRef--;

    if (_cRef > 0)
        return _cRef;

    delete this;
    return 0;
}

HRESULT CAddressList::QueryInterface(REFIID riid, void **ppvObj)
{
    if (IsEqualIID(riid, IID_IUnknown) ||
        IsEqualIID(riid, IID_IWinEventHandler) ||
        IsEqualIID(riid, IID_IAddressList))
    {
        *ppvObj = SAFECAST(this, IAddressList*);
    }
    else
    {
        *ppvObj = NULL;
        return E_NOINTERFACE;
    }

    AddRef();
    return S_OK;
}


//================================
//  ** IWinEventHandler Interface ***

/****************************************************\
    FUNCTION: OnWinEvent

    DESCRIPTION:
        This function will give receive events from
    the parent ShellToolbar.
\****************************************************/
HRESULT CAddressList::OnWinEvent(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT *plres)
{
    LRESULT lres = 0;

    switch (uMsg)
    {
    case WM_COMMAND:
        lres = _OnCommand(wParam, lParam);
        break;

    case WM_NOTIFY:
        lres = _OnNotify((LPNMHDR)lParam);
        break;
    }

    if (plres)
        *plres = lres;

    return S_OK;
}


//================================
// *** IAddressList Interface ***

/****************************************************\
    FUNCTION: Connect

    DESCRIPTION:
        We are either becoming the selected list for
    the AddressBand's combobox, or lossing this status.
    We need to populate or unpopulate the combobox
    as appropriate.
\****************************************************/
HRESULT CAddressList::Connect(BOOL fConnect, HWND hwnd, IBrowserService * pbs, IBandProxy * pbp, IAutoComplete * pac)
{
    HRESULT hr = S_OK;

    ASSERT(hwnd);
    _fVisible = fConnect;
    _hwnd = hwnd;

    // Copy the (IBandProxy *) parameter
    if (_pbp)
        _pbp->Release();
    _pbp = pbp;
    if (_pbp)
        _pbp->AddRef();

    if (_pbs)
        _pbs->Release();
    _pbs = pbs;
    if (_pbs)
        _pbs->AddRef();

    if (fConnect)
    {
        //
        // Initial combobox parameters.
        //
        _InitCombobox();
    }
    else
    {
        // Remove contents of the List
        _PurgeComboBox();
    }

    return hr;
}


/****************************************************\
    FUNCTION: _InitCombobox

    DESCRIPTION:
        Prepare the combo box for this list.  This normally
    means that the indenting and icon are either turned
    on or off.
\****************************************************/
void CAddressList::_InitCombobox()
{
     SendMessage(_hwnd, CB_SETDROPPEDWIDTH, 200, 0L);
     SendMessage(_hwnd, CB_SETEXTENDEDUI, TRUE, 0L);
     SendMessage(_hwnd, CBEM_SETEXTENDEDSTYLE, CBES_EX_NOSIZELIMIT, CBES_EX_NOSIZELIMIT);
}


/****************************************************\
    FUNCTION: _PurgeComboBox

    DESCRIPTION:
        Removes all items from the combobox.
\****************************************************/
void CAddressList::_PurgeComboBox()
{
    if (_hwnd)
    {
        SendMessage(_hwnd, CB_RESETCONTENT, 0, 0L);
    }
}

/****************************************************\
    FUNCTION: _OnCommand

    DESCRIPTION:
        This function will handle WM_COMMAND messages.
\****************************************************/
LRESULT CAddressList::_OnCommand(WPARAM wParam, LPARAM lParam)
{
    UINT uCmd = GET_WM_COMMAND_CMD(wParam, lParam);

    switch (uCmd)
    {
        case CBN_DROPDOWN:
            {
                HCURSOR hCursorOld = SetCursor(LoadCursor(NULL, IDC_WAIT));

                //
                // DOH! The user wants to see the full combo contents.
                // Better go fill it in now.
                //
                _Populate();
                SetCursor(hCursorOld);
            }
            break;
    }
    return 0;
}


void ChangeUrl(LPCTSTR pszUrl, LPTSTR pszDisplayName, DWORD cchSize)
{
    TCHAR szTemp[MAX_URL_STRING];

    StrCpyN(szTemp, pszUrl, ARRAYSIZE(szTemp));     // This is necessary because pszUrl points into the buffer of pszDisplayName
    StrCpyN(pszDisplayName, szTemp, cchSize);
}


/****************************************************\
    FUNCTION: NavigationComplete

    DESCRIPTION:
        Update the URL in the Top of the list.
\****************************************************/
HRESULT CAddressList::NavigationComplete(LPVOID pvCShellUrl)
{
    HRESULT hr = S_OK;
    TCHAR szDisplayName[MAX_URL_STRING];
    CShellUrl * psu = (CShellUrl *) pvCShellUrl;
    LPITEMIDLIST pidl;
    ASSERT(pvCShellUrl);
    hr = psu->GetDisplayName(szDisplayName, SIZECHARS(szDisplayName));

    if (SUCCEEDED(hr))
    {
        //
        // Don't display the url to internal error pages.  The url that should get
        // displayed is appended after the #.
        //
        // All error urls start with res:// so do a quick check first.
        //
        BOOL fChangeURL = TRUE;
        if (TEXT('r') == szDisplayName[0] && TEXT('e') == szDisplayName[1])
        {
            if (IsErrorUrl(szDisplayName))
            {
                TCHAR* pszUrl = StrChr(szDisplayName, TEXT('#'));
                if (pszUrl)
                {
                    pszUrl += 1;

                    DWORD dwScheme = GetUrlScheme(pszUrl);
                    fChangeURL = ((URL_SCHEME_HTTP == dwScheme) ||
                                  (URL_SCHEME_HTTPS == dwScheme) ||
                                  (URL_SCHEME_FTP == dwScheme) ||
                                  (URL_SCHEME_GOPHER == dwScheme));

                    // Don't blast in the stuff after the # into address bar
                    // unless it is a 'safe' url.  If it's not safe leave the
                    // addressbar alone to preserve what the user typed in.
                    //
                    // The issue here is that a web page could navigate to our internal
                    // error page with "format c:" after the '#'.  The error page
                    // suggests that the user refreshed the page which would be very bad!
                    if (fChangeURL)
                    {
                        ChangeUrl(pszUrl, szDisplayName, ARRAYSIZE(szDisplayName));
                    }
                }
            }
        }

        if (fChangeURL)
        {
            SHRemoveURLTurd(szDisplayName);
            SHCleanupUrlForDisplay(szDisplayName);
 
            hr = psu->GetPidl(&pidl);
            if (SUCCEEDED(hr))
            {
                COMBOBOXEXITEM cbexItem = {0};
                cbexItem.mask = CBEIF_TEXT | CBEIF_IMAGE | CBEIF_SELECTEDIMAGE;
                cbexItem.iItem = -1;
                cbexItem.pszText = szDisplayName;
                cbexItem.cchTextMax = ARRAYSIZE(szDisplayName);

                hr = _GetPidlIcon(pidl, &(cbexItem.iImage), &(cbexItem.iSelectedImage));
                SendMessage(_hwnd, CBEM_SETITEM, (WPARAM)0, (LPARAM)(LPVOID)&cbexItem);

                ILFree(pidl);
            }
        }
    }

    TraceMsg(TF_BAND|TF_GENERAL, "CAddressList: NavigationComplete(), URL=%s", szDisplayName);
    return hr;
}

/*******************************************************************
    FUNCTION: _MoveAddressToTopOfList

    PARAMETERS:
        iSel - index of item in combo box to move

    DESCRIPTION:
        Moves the specified selection in the combo box
    to be the first item in the combo box
********************************************************************/
BOOL CAddressList::_MoveAddressToTopOfList(int iSel)
{
    BOOL fRet = FALSE;

    ASSERT(iSel >= 0);   // must have valid index

    COMBOBOXEXITEM cbexItem = {0};
    TCHAR szAddress[MAX_URL_STRING+1];

    cbexItem.mask = CBEIF_TEXT | CBEIF_IMAGE | CBEIF_SELECTEDIMAGE;
    cbexItem.pszText = szAddress;
    cbexItem.cchTextMax = ARRAYSIZE(szAddress);
    cbexItem.iItem = iSel;

    
    // get the specified item from combo box
    if (SendMessage(_hwnd,CBEM_GETITEM,0,(LPARAM) &cbexItem)) {

        SendMessage(_hwnd, CBEM_DELETEITEM, (WPARAM)iSel, (LPARAM)0);

        // re-insert it at index 0 to put it at the top
        cbexItem.iItem = 0;

        // sending CBEM_INSERTITEM should return the index we specified
                // (0) if successful
        fRet = (SendMessage(_hwnd, CBEM_INSERTITEM, (WPARAM)0,
            (LPARAM)(LPVOID)&cbexItem) == 0);
    }

    return fRet;
}



/*******************************************************************
    FUNCTION: _ComboBoxInsertURL

    DESCRIPTION:
        Adds the specified URL to the top of the address bar
    combo box.  Limits the number of URLs in combo box to
    nMaxComboBoxSize.
********************************************************************/
void CAddressList::_ComboBoxInsertURL(LPCTSTR pszURL, int cchStrSize, int nMaxComboBoxSize)
{
    // Since we own it and it's populated,
    // we will add it directly to the ComboBox.
    int iPrevInstance;

    int iImage, iSelectedImage ;

    COMBOBOXEXITEM cbexItem = {0};
    cbexItem.mask = CBEIF_TEXT | CBEIF_IMAGE | CBEIF_SELECTEDIMAGE;
    cbexItem.iItem = 0;

    cbexItem.cchTextMax = cchStrSize;
    cbexItem.pszText = (LPTSTR)pszURL;

    _GetUrlUI(NULL, (LPTSTR)pszURL, &iImage, &iSelectedImage);

    cbexItem.iImage = iImage;
    cbexItem.iSelectedImage = iSelectedImage;


    iPrevInstance = (int)SendMessage(_hwnd, CB_FINDSTRINGEXACT, (WPARAM)-1,  (LPARAM)pszURL);
    if (iPrevInstance != CB_ERR) {
        _MoveAddressToTopOfList(iPrevInstance);
        return;
    }

    // insert the URL as the first item in combo box
    SendMessage(_hwnd, CBEM_INSERTITEM, (WPARAM)0, (LPARAM)(LPVOID)&cbexItem);

    // limit number of items in combo box to nMaxComboBoxSize
    if (ComboBox_GetCount(_hwnd) > nMaxComboBoxSize)
    {
        // if we're ever over the limit, we should only be over the limit
        // by exactly one item
        ASSERT(ComboBox_GetCount(_hwnd) == nMaxComboBoxSize+1);

        // if over the limit, delete the least recently used
        // (the one with the highest index)

        SendMessage(_hwnd, CBEM_DELETEITEM, (WPARAM)nMaxComboBoxSize, (LPARAM)0);

    }
}


/*******************************************************************
    FUNCTION: SetToListIndex

    DESCRIPTION:
        This function will set the CShellUrl parameter to the item
    in the Drop Down list that is indexed by nIndex.
********************************************************************/
HRESULT CAddressList::SetToListIndex(int nIndex, LPVOID pvShelLUrl)
{
    HRESULT hr = S_OK;
    TCHAR szBuffer[MAX_URL_STRING];
    CShellUrl * psuURL = (CShellUrl *) pvShelLUrl;

    if (SUCCEEDED(GetCBListIndex(_hwnd, nIndex, szBuffer, SIZECHARS(szBuffer))))
    {
        hr = psuURL->ParseFromOutsideSource(szBuffer, SHURL_FLAGS_NOUI);
        ASSERT(SUCCEEDED(hr));  // We should not have added it to the Drop Down list if it's invalid.
    }

    return hr;
}

HRESULT CAddressList::FileSysChangeAL(DWORD dw, LPCITEMIDLIST *ppidl)
{
    return S_OK;
}


/****************************************************\
    FUNCTION: GetCBListIndex

    DESCRIPTION:
        This function will get the text of a specified
    element in the combobox.
\****************************************************/
HRESULT GetCBListIndex(HWND hwnd, int iItem, LPTSTR szAddress, int cchAddressSize)
{
    HRESULT hr = E_FAIL;
    COMBOBOXEXITEM cbexItem = {0};

    cbexItem.mask = CBEIF_TEXT;
    cbexItem.pszText = szAddress;
    cbexItem.cchTextMax = cchAddressSize;
    cbexItem.iItem = iItem;

    if (SendMessage(hwnd, CBEM_GETITEM, 0, (LPARAM) &cbexItem))
        hr = S_OK;

    return hr;
}


// Helper Function
// We need to really becareful of perf in this function.
HRESULT CAddressList::_GetUrlUI(CShellUrl *psu, LPCTSTR szUrl, int *piImage, int *piSelectedImage)
{
    CShellUrl * psuUrl;
    HRESULT hr = E_FAIL;

    if (psu)
        psuUrl = psu;
    else
    {
        psuUrl = new CShellUrl();
        if (psuUrl)
        {
            // Set the parent for error messageboxes.  Note that this could end up disabing the taskbar.
            // If this is deemed to be a problem we can first check to see where the addressbar is hosted.
            psuUrl->SetMessageBoxParent(_hwnd);

            SetDefaultShellPath(psuUrl);
        }
    }

    //Initialize the values to 0
    *piImage = 0;
    *piSelectedImage = 0;

    //if object is not created return with default value
    if (!psuUrl)
        return E_OUTOFMEMORY;

#ifdef DISABLED // Why not show the correct icon for removable drives?
    int iDrive;

    // See if we have a drive specified in the path
    if ((iDrive = PathGetDriveNumber(szUrl)) >= 0)
    {
        // See if the drive is removable ?
        if(DriveType(iDrive) == DRIVE_REMOVABLE)
            hr = S_OK;    //Drive is removable so pass the default icons
    }
#endif

    // Do we still need to get the icons?
    if (FAILED(hr))
    {
        // Yes, so try the fast way first.
        hr = _GetFastPathIcons(szUrl, piImage, piSelectedImage);
        if (FAILED(hr))
        {
            LPITEMIDLIST pidl = NULL;

            // If that failed because it the string probably uses advanced parsing, 
            // let CShellUrl do it the slower but more thurough way.
            hr = psuUrl->ParseFromOutsideSource(szUrl, SHURL_FLAGS_NOUI);
            if(SUCCEEDED(hr))
                hr = psuUrl->GetPidl(&pidl);

            if(SUCCEEDED(hr))
            {
                hr = _GetPidlIcon(pidl, piImage, piSelectedImage);
                ILFree(pidl);
            }
        }
    }

    if (psu != psuUrl)
        delete psuUrl;

    return hr;
}


// IECreateFromPath() and CShellUrl::ParseFromOutsideSource() both
// touch the disk which causes unconnected network cases to be really
// slow.  This will create icons for file system paths w/o hitting
// the disk.
HRESULT CAddressList::_GetFastPathIcons(LPCTSTR pszPath, int *piImage, int *piSelectedImage)
{
    SHFILEINFO shFileInfo = {0};

    // SHGetFileInfo() with those flags will be fast because it's won't filter out
    // garbage passed to it.  So it will think URLs are actually relative paths
    // and accept them.  We will fall back to the slow advanced parser which is still
    // fast with URLs.
    if (PathIsRelative(pszPath))
        return E_FAIL;

    HIMAGELIST himl = (HIMAGELIST) SHGetFileInfo(pszPath, FILE_ATTRIBUTE_DIRECTORY, &shFileInfo, sizeof(shFileInfo), (SHGFI_SYSICONINDEX | SHGFI_SMALLICON | SHGFI_USEFILEATTRIBUTES));
    if (!himl || !shFileInfo.iIcon)
        return E_FAIL;

    *piImage = shFileInfo.iIcon;
    *piSelectedImage = shFileInfo.iIcon;
    // I don't need to free himl.

    return S_OK;
}


HRESULT CAddressList::_GetPidlIcon(LPCITEMIDLIST pidl, int *piImage, int *piSelectedImage)
{
    IShellFolder *psfParent;
    LPCITEMIDLIST pidlChild;
    HRESULT hr = IEBindToParentFolder(pidl, &psfParent, &pidlChild);
    if (SUCCEEDED(hr))
    {
        *piImage = IEMapPIDLToSystemImageListIndex(psfParent, pidlChild, piSelectedImage);
        psfParent->Release();
    }
    return hr;
}

LPITEMIDLIST CAddressList::_GetDragDropPidl(LPNMCBEDRAGBEGINW pnmcbe)
{
    LPITEMIDLIST pidl = NULL;
    CShellUrl *psuUrl = new CShellUrl();
    if (psuUrl)
    {
        // Set the parent for error messageboxes.  Note that this could end up disabing the taskbar.
        // If this is deemed to be a problem we can first check to see where the addressbar is hosted.
        psuUrl->SetMessageBoxParent(_hwnd);

        HRESULT hr = SetDefaultShellPath(psuUrl);
        if (SUCCEEDED(hr))
        {
            hr = psuUrl->ParseFromOutsideSource(pnmcbe->szText, SHURL_FLAGS_NOUI, NULL);
            if (SUCCEEDED(hr))
            {
                hr = psuUrl->GetPidl(&pidl);
            }
        }

        delete psuUrl;
    }
    return pidl;
}

LRESULT CAddressList::_OnDragBeginA(LPNMCBEDRAGBEGINA pnmcbe)
{
    NMCBEDRAGBEGINW  nmcbew;

    nmcbew.hdr = pnmcbe->hdr;
    nmcbew.iItemid = pnmcbe->iItemid;
    SHAnsiToUnicode(pnmcbe->szText, nmcbew.szText, SIZECHARS(nmcbew.szText));

    return _OnDragBeginW(&nmcbew);
}

#ifdef UNIX
extern "C" LPITEMIDLIST UnixUrlToPidl(UINT uiCP, LPCTSTR pszUrl, LPCWSTR pszLocation);
#endif

LRESULT CAddressList::_OnDragBeginW(LPNMCBEDRAGBEGINW pnmcbe)
{
    LPITEMIDLIST pidl = _GetDragDropPidl(pnmcbe);
#ifdef UNIX
    // for UNIX we fake a URL pidl so we create a .url instead of a .lnk
    if (!IsURLChild(pidl, TRUE))
    {
        LPITEMIDLIST pidl1;
        TCHAR szPath[MAX_PATH];
        StrCpyN(szPath, TEXT("file://"), 8);
        SHGetPathFromIDList(pidl, &szPath[7]);
        pidl1 = UnixUrlToPidl(CP_ACP, szPath, NULL);
        if (pidl1)
    {
            ILFree(pidl);
            pidl = pidl1;
        }
    }
#endif
    if (pidl) 
    {
        IOleCommandTarget *pcmdt = NULL;

        IUnknown *punk;
        if (_pbp && SUCCEEDED(_pbp->GetBrowserWindow(&punk)))
        {
            punk->QueryInterface(IID_IOleCommandTarget, (void **)&pcmdt);
            punk->Release(); 
        }

        DoDragDropWithInternetShortcut(pcmdt, pidl, _hwnd);

        if (pcmdt)
            pcmdt->Release();

        ILFree(pidl);
    }

    return 0;
}

// handle WM_NOTIFY messages.
LRESULT CAddressList::_OnNotify(LPNMHDR pnm)
{
    LRESULT lReturn = 0;

    switch (pnm->code)
    {
    case NM_SETCURSOR:
        if (!SendMessage(_hwnd, CBEM_GETEXTENDEDSTYLE, 0, 0) & CBES_EX_NOEDITIMAGE)
        {
            RECT rc;
            POINT pt;
            int cx, cy;
            GetCursorPos(&pt);
            GetClientRect(_hwnd, &rc);
            MapWindowRect(_hwnd, HWND_DESKTOP, &rc);
            ImageList_GetIconSize((HIMAGELIST)SendMessage(_hwnd, CBEM_GETIMAGELIST, 0, 0), &cx, &cy);

            rc.right = rc.left + cx + GetSystemMetrics(SM_CXEDGE);
            if (PtInRect(&rc, pt)) 
            {
                // this means there's an image, which means we can drag
                SetCursor(LoadHandCursor(0));
                return 1;
            }
        }
        break;

        case CBEN_DRAGBEGINA:
            lReturn = _OnDragBeginA((LPNMCBEDRAGBEGINA)pnm);
            break;

        case CBEN_DRAGBEGINW:
            lReturn = _OnDragBeginW((LPNMCBEDRAGBEGINW)pnm);
            break;
    }

    return lReturn;
}