#include "init.h"
#include <emptyvc.h>
#include <regstr.h>
#include "general.h"
#include "dlg.h"
#include "emptyvol.h"
#include "parseinf.h"

#define MAX_DRIVES                 26   // there are 26 letters only

// {8369AB20-56C9-11d0-94E8-00AA0059CE02}
const CLSID CLSID_EmptyControlVolumeCache = {
                            0x8369ab20, 0x56c9, 0x11d0, 
                            0x94, 0xe8, 0x0, 0xaa, 0x0,
                            0x59, 0xce, 0x2};

/******************************************************************************
    class CEmptyControlVolumeCache
******************************************************************************/

class CEmptyControlVolumeCache : public IEmptyVolumeCache
{
public:
    // IUnknown Methods
    STDMETHODIMP QueryInterface(REFIID iid, void** ppv);
    STDMETHODIMP_(ULONG) AddRef();
    STDMETHODIMP_(ULONG) Release();

    // IEmptyVolumeCache Methods
    STDMETHODIMP Initialize(HKEY hRegKey, LPCWSTR pszVolume,
        LPWSTR *ppszDisplayName, LPWSTR *ppszDescription, DWORD *pdwFlags);
    STDMETHODIMP GetSpaceUsed(DWORDLONG *pdwSpaceUsed,
        IEmptyVolumeCacheCallBack *picb);
    STDMETHODIMP Purge(DWORDLONG dwSpaceToFree,
        IEmptyVolumeCacheCallBack *picb);
    STDMETHODIMP ShowProperties(HWND hwnd);
    STDMETHODIMP Deactivate(DWORD *pdwFlags);

// Attributes
public:
    static HRESULT IsControlExpired(HANDLE hControl, BOOL fUseCache = TRUE);

// Implementation
public:
    // Constructor and destructor
    CEmptyControlVolumeCache();
    virtual ~CEmptyControlVolumeCache();

protected:
        // implementation data helpers

    // Note. Write operations are only perfomed by the private functions
    //       prefixed cpl_XXX. Read access is not restricted.
    LPCACHE_PATH_NODE m_pPathsHead,
                      m_pPathsTail;

    // Note. Write operations are only perfomed by the private functions
    //       prefixed chl_XXX. Read access is not restricted.
    LPCONTROL_HANDLE_NODE m_pControlsHead,
                          m_pControlsTail;

    WCHAR     m_szVol[4];
    DWORDLONG m_dwTotalSize;
    ULONG     m_cRef;

        // implementation helper routines

    // cpl prefix stands for CachePathsList
    HRESULT cpl_Add(LPCTSTR pszCachePath);
    void    cpl_Remove();
    HRESULT cpl_CreateForVolume(LPCWSTR pszVolume = NULL);

    // chl prefix stands for ControlHandlesList
    HRESULT chl_Find(HANDLE hControl,
        LPCONTROL_HANDLE_NODE *rgp = NULL, UINT nSize = 1) const;
    HRESULT chl_Add(HANDLE hControl);
    void    chl_Remove(LPCONTROL_HANDLE_NODE rgp[2]);
    HRESULT chl_Remove(HANDLE hControl = NULL);
    HRESULT chl_CreateForPath(LPCTSTR pszCachePath,
        DWORDLONG *pdwUsedInFolder = NULL);

    friend HRESULT _stdcall EmptyControl_CreateInstance(IUnknown *pUnkOuter,
        REFIID riid, LPVOID* ppv);

//  friend BOOL CALLBACK EmptyControl_PropertiesDlgProc(HWND hDlg,
//      UINT msg, WPARAM wp, LPARAM lp);
};


STDAPI EmptyControl_CreateInstance(IUnknown *pUnkOuter, REFIID riid, LPVOID* ppv)
{
    *ppv = NULL;

    if (pUnkOuter != NULL)
        return CLASS_E_NOAGGREGATION;

    CEmptyControlVolumeCache *pCRC = new CEmptyControlVolumeCache;
    if (pCRC == NULL)
        return E_OUTOFMEMORY;

    HRESULT hr = pCRC->QueryInterface(riid, ppv);
    pCRC->Release();

    return hr;
}


/////////////////////////////////////////////////////////////////////////////
// CEmptyControlVolumeCache constructor and destructor

CEmptyControlVolumeCache::CEmptyControlVolumeCache()
{
    DllAddRef();

    m_pPathsHead = m_pPathsTail = NULL;
    m_pControlsHead = m_pControlsTail = NULL;

    m_szVol[0] = L'\0';
    m_dwTotalSize = 0;
    m_cRef = 1;
}

CEmptyControlVolumeCache::~CEmptyControlVolumeCache()
{
    ASSERT(m_cRef == 0);
    cpl_Remove();
    chl_Remove();

    DllRelease();
}


/////////////////////////////////////////////////////////////////////////////
// CEmptyControlVolumeCache attributes


// CEmptyControlVolumeCache::IsControlExpired
// Check if a control has not been accessed for more than N days. If there is
// no registry entry, default is DEFAULT_DAYS_BEFORE_EXPIRE.
//
// Parameters: fUseCache can be used to not go to the registry for the value
// of N above.
//
// Returns: either the Win32 error converted to HRESULT or
//          S_OK if control is expired and S_FALSE if not;
//
// Used by: only by CEmptyControlVolumeCache::chl_CreateForPath
//
HRESULT CEmptyControlVolumeCache::IsControlExpired(HANDLE hControl,
    BOOL fUseCache /*= TRUE*/)
{
    SYSTEMTIME    stNow;
    FILETIME      ftNow;
    FILETIME      ftLastAccess;
    LARGE_INTEGER timeExpire;
    HRESULT       hr = S_OK;

    ASSERT(hControl != NULL && hControl != INVALID_HANDLE_VALUE);

    // don't expire controls with uncertain access time.
    if (FAILED(GetLastAccessTime(hControl, &ftLastAccess)))
        return S_FALSE;
 
    //----- Time calculations (wierd looking) -----
    // Add to last access date the length of time before a control expires
    timeExpire.LowPart  = ftLastAccess.dwLowDateTime;
    timeExpire.HighPart = ftLastAccess.dwHighDateTime;
    timeExpire.QuadPart += (((CCacheItem*)hControl)->GetExpireDays() * 864000000000L); //24*3600*10^7

    GetLocalTime(&stNow);
    SystemTimeToFileTime(&stNow, &ftNow);

    return CompareFileTime((FILETIME*)&timeExpire, &ftNow) <= 0 ?
        S_OK : S_FALSE;
}


/////////////////////////////////////////////////////////////////////////////
// CEmptyControlVolumeCache CachePathsList routines

// CEmptyControlVolumeCache::cpl_Add
// Check if a control has not been accessed for more than N days. If there is
// no registry entry, default is DEFAULT_DAYS_BEFORE_EXPIRE.
//
// Parameters: a cache folder path to add.
//
// Returns: E_OUTOFMEMORY or
//          S_FALSE if path is already in the list or S_OK if added.
//
// Used by: only by CEmptyControlVolumeCache::cpl_CreateForVolume
//
HRESULT CEmptyControlVolumeCache::cpl_Add(LPCTSTR pszCachePath)
{
    LPCACHE_PATH_NODE pNode;

    ASSERT(pszCachePath != NULL);

    for (pNode = m_pPathsHead; pNode != NULL; pNode = pNode->pNext)
        if (lstrcmpi(pNode->szCachePath, pszCachePath) == 0)
            break;
    if (pNode != NULL)
        return S_FALSE;

    pNode = new CACHE_PATH_NODE;
    if (pNode == NULL)
        return E_OUTOFMEMORY;

    lstrcpyn(pNode->szCachePath, pszCachePath, MAX_PATH);
    pNode->pNext = NULL;
    if (m_pPathsHead == NULL)
        m_pPathsHead = pNode;
    else
        m_pPathsTail->pNext = pNode;
    m_pPathsTail = pNode;

    return S_OK;
}

// CEmptyControlVolumeCache::cpl_Remove
// Remove all paths from the internal list.
//
// Parameters: none;
//
// Returns: void;
//
// Used by: several obvious places
//
void CEmptyControlVolumeCache::cpl_Remove()
{
    // remove cache path list
    for (LPCACHE_PATH_NODE pCur = m_pPathsHead;
         m_pPathsHead != NULL;
         pCur = m_pPathsHead) {

        m_pPathsHead = m_pPathsHead->pNext;
        delete[] pCur;
    }
    m_pPathsTail = NULL;
}

// CEmptyControlVolumeCache::cpl_CreateForVolume
// Build a list of paths to cache folders.
//
// Parameters: volume (or drive) where these folders are;
//
// Returns: S_OK or one out of the bunch of obvious errors;
//
// Used by: only by IEmptyVolumeCache::GetSpaceUsed
//
HRESULT CEmptyControlVolumeCache::cpl_CreateForVolume(LPCWSTR pszVolume)
{
    HKEY    hkey = NULL;
    HRESULT hr   = E_FAIL;
    int     iDriveNum;

    ASSERT(pszVolume != NULL);
    iDriveNum = PathGetDriveNumberW(pszVolume);
    if (iDriveNum < 0)
        return E_INVALIDARG;

    if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, REGSTR_PATH_ACTIVEX_CACHE, 0,
            KEY_READ, &hkey) != ERROR_SUCCESS)
        return E_FAIL;

    TCHAR szCachePath[MAX_PATH],
          szValue[MAX_PATH];
    DWORD dwIndex    = 0,
          dwValueLen = MAX_PATH, dwLen = MAX_PATH;

    cpl_Remove();
    while (RegEnumValue(hkey, dwIndex++, szValue, &dwValueLen, NULL, NULL,
               (LPBYTE)szCachePath, &dwLen) == ERROR_SUCCESS) {
        dwLen = dwValueLen = MAX_PATH;

        if (PathGetDriveNumber(szCachePath) != iDriveNum)
            continue;

        // we must have added at least one successfully to get a success code..
        hr = cpl_Add(szCachePath);
        if (FAILED(hr))
            break;
    }
    RegCloseKey(hkey);

    if (FAILED(hr))
        cpl_Remove();

    return hr;
}


/////////////////////////////////////////////////////////////////////////////
// CEmptyControlVolumeCache ControlHandlesList routines

// CEmptyControlVolumeCache::chl_Find
// Find and return a location for the specified handle in the internal list.
// if (rgp == NULL), only result matters;
// if (rgp != NULL),
//     if (nSize == 1), *rgp is going to have found item (if it's there)
//     if (nSize >= 2), *rgp[0] = prev to the found item, and *rgp[1] is the
//                      item.
//
// Parameters: explained above;
//
// Returns: S_OK if the item is found, S_FALSE otherwise or
//          one out of the bunch of obvious errors;
//
// Used by: CEmptyControlVolumeCache::chl_Add and
//          CEmptyControlVolumeCache::chl_Remove
//
HRESULT CEmptyControlVolumeCache::chl_Find(HANDLE hControl,
    LPCONTROL_HANDLE_NODE *rgp /*= NULL*/, UINT nSize /*= 1*/) const
{
    LPCONTROL_HANDLE_NODE pCur,
                          pPrev = NULL;

    ASSERT(hControl != NULL && hControl != INVALID_HANDLE_VALUE);
    for (pCur = m_pControlsHead; pCur != NULL; pCur = pCur->pNext) {
        if (pCur->hControl == hControl)
            break;
        pPrev = pCur;
    }
    if (pCur == NULL)
        pPrev = NULL;                           // zero out possible return

    if (rgp != NULL && nSize > 0)
        if (nSize == 1)
            *rgp = pCur;
        else { /* if (nSize >= 2) */
            rgp[0] = pPrev;
            rgp[1] = pCur;
        }

    return (pCur != NULL) ? S_OK : E_FAIL;
}

HRESULT CEmptyControlVolumeCache::chl_Add(HANDLE hControl)
{
    LPCONTROL_HANDLE_NODE pNode;
    DWORD                 dwSize;

    // Note. Retail build assumes that handle is not in the list.
    ASSERT(hControl != NULL && hControl != INVALID_HANDLE_VALUE);
    ASSERT(FAILED(chl_Find(hControl)));

    pNode = new CONTROL_HANDLE_NODE;
    if (pNode == NULL)
        return E_OUTOFMEMORY;

    GetControlInfo(hControl, GCI_SIZESAVED, &dwSize, NULL, 0);

    pNode->hControl = hControl;
    pNode->pNext    = NULL;

    if (m_pControlsHead == NULL)
        m_pControlsHead = pNode;
    else {
        ASSERT(m_pControlsHead != NULL);
        m_pControlsTail->pNext = pNode;
    }
    m_pControlsTail = pNode;

    m_dwTotalSize += dwSize;
    return S_OK;
}

void CEmptyControlVolumeCache::chl_Remove(LPCONTROL_HANDLE_NODE rgp[2])
{
    DWORD dwSize;

    if (m_pControlsHead == NULL || (rgp[0] != NULL && rgp[1] == NULL))
        return;

    if (rgp[0] != NULL)
        rgp[0]->pNext = rgp[1]->pNext;
    else {
        rgp[1] = m_pControlsHead;
        m_pControlsHead = m_pControlsHead->pNext;
    }

    if (rgp[1] == m_pControlsTail)
        m_pControlsTail = rgp[0];

    if (GetControlInfo(rgp[1]->hControl, GCI_SIZESAVED, &dwSize, NULL, 0))
    {
        // only proceeed if GetControlInfo succeeds
    
        // Note. This code assumes that the size of a control didn't change since
        //       it was added.
        m_dwTotalSize -= dwSize;
    }
    
    ReleaseControlHandle(rgp[1]->hControl);
    delete rgp[1];
}

HRESULT CEmptyControlVolumeCache::chl_Remove(HANDLE hControl /*= NULL*/)
{
    LPCONTROL_HANDLE_NODE rgp[2] = { NULL, NULL };
    HRESULT hr;

    ASSERT(hControl != INVALID_HANDLE_VALUE);
    if (hControl != NULL) {
        hr = chl_Find(hControl, rgp, 2);
        if (FAILED(hr))
            return hr;

        chl_Remove(rgp);
        return S_OK;
    }

    while (m_pControlsHead != NULL)
        chl_Remove(rgp);

    ASSERT(m_pControlsHead == NULL && m_pControlsTail == NULL);
    return S_OK;
}

// CEmptyControlVolumeCache::chl_CreateForPath
// Calculate the size in bytes taken up by controls in the control cache
// folder specified.
//
// Parameters: pszCachePath is a path to the controls cache folder;
//             pdwSpaceUsed is the result
//
// Used by: only by IEmptyVolumeCache::GetSpaceUsed
//
HRESULT CEmptyControlVolumeCache::chl_CreateForPath(LPCTSTR pszCachePath,
    DWORDLONG *pdwUsedInFolder /*= NULL*/)
{
    DWORDLONG dwCopy;
    HANDLE    hFind    = NULL,
              hControl = NULL;
    LONG      lResult;
    BOOL      fCache   = FALSE;

    dwCopy = m_dwTotalSize;
    for (lResult = FindFirstControl(hFind, hControl, pszCachePath);
         lResult == ERROR_SUCCESS;
         lResult = FindNextControl(hFind, hControl)) {

        lResult = HRESULT_CODE(IsControlExpired(hControl, fCache));
        fCache  = TRUE;
        if (lResult != ERROR_SUCCESS)
            continue;

        lResult = HRESULT_CODE(chl_Add(hControl));
        if (lResult != ERROR_SUCCESS)
            break;
    }
    FindControlClose(hFind);

    if (lResult == ERROR_NO_MORE_ITEMS)
        lResult = ERROR_SUCCESS;

    if (pdwUsedInFolder != NULL) {
        *pdwUsedInFolder = m_dwTotalSize - dwCopy;
    }
    return HRESULT_FROM_WIN32(lResult);
}


/******************************************************************************
    IUnknown Methods
******************************************************************************/

STDMETHODIMP CEmptyControlVolumeCache::QueryInterface(REFIID iid, void** ppv)
{
    if (ppv == NULL)
        return E_POINTER;
    *ppv = NULL;

    if (iid != IID_IUnknown && iid != IID_IEmptyVolumeCache)
        return E_NOINTERFACE;

    *ppv = (void *)this;
    AddRef();
    return S_OK;
}

STDMETHODIMP_(ULONG) CEmptyControlVolumeCache::AddRef()
{
    return (++m_cRef);
}

STDMETHODIMP_(ULONG) CEmptyControlVolumeCache::Release()
{
    if (--m_cRef)
        return m_cRef;

    delete this;
    return 0;   
}


/******************************************************************************
    IEmptyVolumeCache Methods
******************************************************************************/

STDMETHODIMP CEmptyControlVolumeCache::Initialize(HKEY hRegKey,
    LPCWSTR pszVolume, LPWSTR *ppszDisplayName, LPWSTR *ppszDescription,
    DWORD *pdwFlags)
{
    if (pszVolume == NULL)
        return E_POINTER;

    if (ppszDisplayName == NULL || ppszDescription == NULL)
        return E_POINTER;

    if (pdwFlags == NULL)
        return E_POINTER;

    StrCpyNW(m_szVol, pszVolume, ARRAYSIZE(m_szVol));
    cpl_Remove();
    chl_Remove();
    
    if (lstrlenW(m_szVol) == 0) {
        return E_UNEXPECTED;
    }

    if (FAILED(cpl_CreateForVolume(m_szVol))) {
        return E_FAIL;
    }

    *ppszDisplayName = *ppszDescription = NULL;
    *pdwFlags = EVCF_HASSETTINGS | EVCF_ENABLEBYDEFAULT |
        EVCF_ENABLEBYDEFAULT_AUTO;
    return S_OK;
}

STDMETHODIMP CEmptyControlVolumeCache::GetSpaceUsed(DWORDLONG *pdwSpaceUsed,
    IEmptyVolumeCacheCallBack *picb)
{
    LPCACHE_PATH_NODE pCur;
    HRESULT hr = S_OK;

    if (pdwSpaceUsed == NULL) {
        hr = E_POINTER;
        goto LastNotification;
    }
    *pdwSpaceUsed = 0;

    if (lstrlenW(m_szVol) == 0) {
        hr = E_UNEXPECTED;
        goto LastNotification;
    }

    for (pCur = m_pPathsHead; pCur != NULL; pCur = pCur->pNext) {
        DWORDLONG dwlThisItem = 0;
        if (FAILED(chl_CreateForPath(pCur->szCachePath, &dwlThisItem)))
            hr = S_FALSE;                       // at least one failed

        m_dwTotalSize += dwlThisItem;
        
        if (picb != NULL)
            picb->ScanProgress(m_dwTotalSize, 0, NULL);
    }
//  cpl_Remove();                               // because of ShowProperties

    *pdwSpaceUsed = m_dwTotalSize;

LastNotification:
    if (picb != NULL)
        picb->ScanProgress(m_dwTotalSize, EVCCBF_LASTNOTIFICATION, NULL);
    return hr;
}

STDMETHODIMP CEmptyControlVolumeCache::Purge(DWORDLONG dwSpaceToFree,
    IEmptyVolumeCacheCallBack *picb)
{
    LPCONTROL_HANDLE_NODE rgp[2] = { NULL, NULL };
    DWORDLONG dwSpaceFreed;
    HANDLE    hControl;
    DWORD     dwSize;
    HRESULT   hr;

    if (m_pControlsHead == NULL) {
        DWORDLONG dwSpaceUsed;

        hr = GetSpaceUsed(&dwSpaceUsed, picb);
        if (FAILED(hr) || m_pControlsHead == NULL)
            hr = FAILED(hr) ? hr : STG_E_NOMOREFILES;

        if (picb != NULL)
            picb->PurgeProgress(0, dwSpaceToFree, EVCCBF_LASTNOTIFICATION,
                NULL);

        return hr;
    }

    dwSpaceFreed = 0;
    ASSERT(m_pControlsHead != NULL);
    while (m_pControlsHead != NULL) {
        hControl = m_pControlsHead->hControl;
        ASSERT(hControl != NULL && hControl != INVALID_HANDLE_VALUE);

        GetControlInfo(hControl, GCI_SIZESAVED, &dwSize, NULL, 0);

        hr = RemoveControlByHandle2(hControl, FALSE, TRUE);
        if (SUCCEEDED(hr)) {
            dwSpaceFreed += dwSize;

            if (picb != NULL)
                picb->PurgeProgress(dwSpaceFreed, dwSpaceToFree, 0, NULL);
        }
        chl_Remove(rgp);

        if (dwSpaceFreed >= dwSpaceToFree)
            break;
    }

    if (picb != NULL)
        picb->PurgeProgress(dwSpaceFreed, dwSpaceToFree, 0, NULL);

    return S_OK;
}

// Note. This function opens the last cache folder in the internal list.
STDMETHODIMP CEmptyControlVolumeCache::ShowProperties(HWND hwnd)
{
    // Note. (According to SeanF) The codedownload engine will query
    //       ActiveXCache key under HKLM\SOFTWARE\Microsoft\Windows\
    //       CurrentVersion\Internet Settings. The value of this key should
    //       be equal to the last item in the CachePathsList which is why
    //       navigation below is done for the tail.
    if (m_pPathsTail == NULL || m_pPathsTail->szCachePath == NULL)
        return E_UNEXPECTED;

    ShellExecute(hwnd, NULL, m_pPathsTail->szCachePath, NULL, NULL, SW_SHOW);
    return S_OK;
/*
    int iDlgResult;

    iDlgResult = MLDialogBoxWrap(MLGetHinst(), MAKEINTRESOURCE(IDD_PROP_EXPIRE), hwnd,
        EmptyControl_PropertiesDlgProc);

    return iDlgResult == IDOK ? S_OK : S_FALSE;
*/
}

STDMETHODIMP CEmptyControlVolumeCache::Deactivate(DWORD *pdwFlags)
{
    if (pdwFlags == NULL)
        return E_INVALIDARG;
    *pdwFlags = 0;

    return S_OK;
}


/////////////////////////////////////////////////////////////////////////////
// Implementation helpers routines (private)

/*
static void msg_OnInitDialog(HWND hDlg);
static BOOL msg_OnCommand(HWND hDlg, UINT msg, WPARAM wp, LPARAM lp);

static BOOL cmd_OnOK(HWND hDlg);

INT_PTR CALLBACK EmptyControl_PropertiesDlgProc(HWND hDlg,
    UINT msg, WPARAM wp, LPARAM lp)
{
    static MSD rgmsd[] = {
        { WM_INITDIALOG, ms_vh,    (PFN)msg_OnInitDialog },
        { WM_COMMAND,    ms_bwwwl, (PFN)msg_OnCommand    },
        { WM_NULL,       ms_end,   (PFN)NULL             }
    };

    return Dlg_MsgProc(rgmsd, hDlg, msg, wp, lp);
}

void msg_OnInitDialog(HWND hDlg)
{
    UINT nDays;

    CEmptyControlVolumeCache::GetDaysBeforeExpire(&nDays);
    SetDlgItemInt(hDlg, IDC_EDIT_EXPIRE, nDays, FALSE);
}

BOOL msg_OnCommand(HWND hDlg, UINT msg, WPARAM wp, LPARAM lp)
{
    static CMD rgcmd[] = {
        { IDOK, 0, ms_bh,  (PFN)cmd_OnOK },
        { 0,    0, ms_end, (PFN)NULL     }
    };

    return Msg_OnCmd(rgcmd, hDlg, msg, wp, lp);
}

BOOL cmd_OnOK(HWND hDlg)
{
    UINT nDays;
    BOOL fWorked;

    nDays = GetDlgItemInt(hDlg, IDC_EDIT_EXPIRE, &fWorked, FALSE);
    if (!fWorked) {
        MessageBeep(-1);
        SetFocus(GetDlgItem(hDlg, IDC_EDIT_EXPIRE));
        return FALSE;
    }

    CEmptyControlVolumeCache::SetDaysBeforeExpire(nDays);
    return TRUE;
}
*/
