#ifndef _CDFAGENT_H
#define _CDFAGENT_H

#include "msxml.h"

class CProcessElement;
class CProcessRoot;

class CUrlTrackingCache;

class CRunDeliveryAgentSink
{
public:
    // OnAgentProgress not currently called
    virtual HRESULT OnAgentProgress()
                    { return E_NOTIMPL; }
    // OnAgentEnd called when agent is complete. fSynchronous means that StartAgent call
    //  has not yet returned; hrResult will be returned from StartAgent
    virtual HRESULT OnAgentEnd(const SUBSCRIPTIONCOOKIE *pSubscriptionCookie, 
                               long lSizeDownloaded, HRESULT hrResult, LPCWSTR wszResult,
                               BOOL fSynchronous)
                    { return E_NOTIMPL; }
};

class CProcessElementSink
{
public:
    virtual HRESULT OnChildDone(CProcessElement *pChild, HRESULT hr) = 0;
    virtual LPCWSTR GetBaseUrl() = 0;   // Returned pointer doesn't need to get freed
    virtual BOOL    IsGlobalLog() = 0;
};

typedef struct CDF_TIME
{
    WORD   wDay;
    WORD   wHour;
    WORD   wMin;
    WORD   wReserved;
    DWORD  dwConvertedMinutes;      // Day/Hour/Min in Minutes
} CDF_TIME;


//////////////////////////////////////////////////////////////////////////
//
// Channel Agent object
//
//////////////////////////////////////////////////////////////////////////
class CChannelAgent : public CDeliveryAgent,
                      public CUrlDownloadSink,
                      public CProcessElementSink
{
    friend CProcessElement; // for SendUpdateProgress
    friend CProcessRoot;    // for laziness
protected:
// properties
    LPWSTR      m_pwszURL;
    DWORD       m_dwChannelFlags;

// used during updating
    CUrlDownload    *m_pCurDownload;
    IExtractIcon    *m_pChannelIconHelper;

    BOOL            m_fHasInitCookie;   // One time deal, don't try again.

    VARIANT         m_varChange;

    GROUPID         m_llCacheGroupID;
    GROUPID         m_llOldCacheGroupID;

    // other agent flags
    enum {
        FLAG_CDFCHANGED  =  0x80000000  // did the CDF change?
    };

private:
    ~CChannelAgent(void);

public:
    CChannelAgent(void);

    // CUrlDownloadSink
    HRESULT     OnAuthenticate(HWND *phwnd, LPWSTR *ppszUsername, LPWSTR *ppszPassword);
    HRESULT     OnDownloadComplete(UINT iID, int iError);

    // CProcessElementSink
    HRESULT     OnChildDone(CProcessElement *pChild, HRESULT hr);
    LPCWSTR     GetBaseUrl() { return GetUrl(); }
    BOOL        IsGlobalLog() { return FALSE; }

    // virtual functions overriding CDeliveryAgent
    HRESULT     AgentPause(DWORD dwFlags);
    HRESULT     AgentResume(DWORD dwFlags);
    HRESULT     AgentAbort(DWORD dwFlags);
    STDMETHODIMP GetIconLocation(UINT, LPTSTR, UINT, int *, UINT *);
    STDMETHODIMP Extract(LPCTSTR, UINT, HICON *, HICON *, UINT);

    LPCWSTR     GetUrl() { return m_pwszURL; }
    ISubscriptionItem *GetStartItem() { return m_pSubscriptionItem; }

    BOOL        IsChannelFlagSet(DWORD dwFlag) { return dwFlag & m_dwChannelFlags; }

    void        SetScreenSaverURL(LPCWSTR pwszURL);

protected:
    // CDeliveryAgent overrides
    HRESULT     ModifyUpdateEnd(ISubscriptionItem *pEndItem, UINT *puiRes);
    HRESULT     StartOperation();
    HRESULT     StartDownload();
    void        CleanUp();

    // Used during updates
    CProcessRoot   *m_pProcess;
    LPWSTR      m_pwszScreenSaverURL;

public:
    DWORD           m_dwMaxSizeKB;
};

//////////////////////////////////////////////////////////////////////////
//
// CRunDeliveryAgent object
// Will run a delivery agent and host it for you
// Create, call Init, then call StartAgent
// Use static function SafeRelease to safely release this class.
//
//////////////////////////////////////////////////////////////////////////
class CRunDeliveryAgent : public ISubscriptionAgentEvents
{
protected:
    virtual ~CRunDeliveryAgent();

    CRunDeliveryAgentSink *m_pParent;

    ULONG           m_cRef;

    ISubscriptionItem         *m_pItem;
    ISubscriptionAgentControl *m_pAgent;

    HRESULT     m_hrResult;
    BOOL        m_fInStartAgent;

    CLSID       m_clsidDest;

    void        CleanUp();

public:
    CRunDeliveryAgent();

    HRESULT Init(CRunDeliveryAgentSink *pParent, ISubscriptionItem *pItem, REFCLSID rclsidDest);

    void LeaveMeAlone() { m_pParent = NULL; }

inline static void SafeRelease(CRunDeliveryAgent * &pThis)
{ if (pThis) { pThis->m_pParent=NULL; pThis->Release(); pThis=NULL; } }

static HRESULT CreateNewItem(ISubscriptionItem **ppItem, REFCLSID rclsidAgent);

    // StartAgent will return E_PENDING if agent is running. Otherwise it will return
    //  synchronous result code from agent.
    HRESULT     StartAgent();

    HRESULT     AgentPause(DWORD dwFlags);
    HRESULT     AgentResume(DWORD dwFlags);
    HRESULT     AgentAbort(DWORD dwFlags);

    // IUnknown members
    STDMETHODIMP         QueryInterface(REFIID riid, void **ppunk);
    STDMETHODIMP_(ULONG) AddRef();
    STDMETHODIMP_(ULONG) Release();

    // ISubscriptionAgentEvents members
    STDMETHODIMP UpdateBegin(const SUBSCRIPTIONCOOKIE *pSubscriptionCookie);
    STDMETHODIMP UpdateProgress(const SUBSCRIPTIONCOOKIE *pSubscriptionCookie, 
                        long lSizeDownloaded, long lProgressCurrent, long lProgressMax,
                        HRESULT hrStatus, LPCWSTR wszStatus);
    STDMETHODIMP UpdateEnd(const SUBSCRIPTIONCOOKIE *pSubscriptionCookie, 
                            long lSizeDownloaded,
                            HRESULT hrResult, LPCWSTR wszResult);
    STDMETHODIMP ReportError(const SUBSCRIPTIONCOOKIE *pSubscriptionCookie, 
                             HRESULT hrError, LPCWSTR wszError);
};

class CChannelAgentHolder : public CRunDeliveryAgent,
                            public IServiceProvider
{
protected:
    ~CChannelAgentHolder();

public:
    CChannelAgentHolder(CChannelAgent *pChannelAgent, CProcessElement *pProcess);

    // IUnknown
    STDMETHODIMP        QueryInterface(REFIID riid, void **ppunk);
    STDMETHODIMP_(ULONG) AddRef();
    STDMETHODIMP_(ULONG) Release();

    // ServiceProvider
    STDMETHODIMP        QueryService(REFGUID guidService, REFIID riid, void **ppvObject);

protected:
    CChannelAgent *m_pChannelAgent;
    CProcessElement *m_pProcess;
};


//////////////////////////////////////////////////////////////////////////
//
// Process Element objects
//
// User of this class
//  1) Creates & passes in self & element
//  2) Calls Run
//  3) If E_PENDING, will receive call back "OnChildDone"
//
// The purpose of this class is simply to allow us to save our state of
//  walking the XML OM, so that we can host another deliver agent
//  (webcrawler). This requires us to return out to the thread's message
//  pump after sending the "agent start" to the web crawler.
// The if a webcrawl is initiated the class creates its own sink. Classes
//  also keep references to their spawned enumerations in case of an
//  abort, which comes from the root element (CProcessRoot instance)
//
//////////////////////////////////////////////////////////////////////////
class CProcessElement : public CProcessElementSink, public CRunDeliveryAgentSink
{
public:
    CProcessElement(CProcessElementSink *pParent, CProcessRoot *pRoot, IXMLElement *pEle);
    ~CProcessElement();

    // From CRunDeliveryAgent
    HRESULT OnAgentEnd(const SUBSCRIPTIONCOOKIE *pSubscriptionCookie, 
                       long lSizeDownloaded, HRESULT hrResult, LPCWSTR wszResult,
                       BOOL fSynchronous);

    typedef HRESULT (CChannelAgent::*PFNHANDLETAG)(LPCWSTR pwszTagName, IXMLElement *pEle);
    typedef struct
    {
        LPCWSTR         pwszTagName;
        PFNHANDLETAG    pfnHandleTag;
    } TAGTABLE;

    // E_FAIL, E_PENDING, or S_OK
    virtual HRESULT    Run();

    // Called when E_PENDING DoChild returns (from m_pCurChild)
    HRESULT     OnChildDone(CProcessElement *pChild, HRESULT hr);


    HRESULT     Pause(DWORD dwFlags);
    HRESULT     Resume(DWORD dwFlags);
    HRESULT     Abort(DWORD dwFlags);

    IXMLElement *GetCurrentElement() { return m_pChildElement; }

protected:
    // Returns E_PENDING, or S_OK if enumeration complete
    HRESULT     DoEnumeration();

    // E_PENDING if webcrawl pending
    HRESULT     DoChild(CProcessElement *pChild);

    // Should return E_PENDING, or S_OK if processing done
    // Can return E_ABORT to abort entire CDF processing
    virtual HRESULT ProcessItemInEnum(LPCWSTR pwszTagName, IXMLElement *pItem) = 0;

    // Called by DoEnumeration when it's done. Return value ignored.
    virtual HRESULT EnumerationComplete() { return S_OK; }

    // E_PENDING, or E_FAIL
    HRESULT     DoDeliveryAgent(ISubscriptionItem *pItem, REFCLSID rclsid, LPCWSTR pwszURL=NULL);
    HRESULT     DoWebCrawl(IXMLElement *pItem, LPCWSTR pwszURL=NULL);
    HRESULT     DoSoftDist(IXMLElement *pItem);

    BOOL    ShouldDownloadLogo(IXMLElement *pLogo);

    // If relative url, will combine with most recent base URL
    // *ppwszRetUrl should be NULL & will be LocalAlloced if needed
    HRESULT     CombineWithBaseUrl(LPCWSTR pwszUrl, LPWSTR *ppwszRetUrl);

    // Returned pointer doesn't need to get freed
    LPCWSTR     GetBaseUrl() { return m_pParent->GetBaseUrl(); }
    BOOL        IsGlobalLog() { return m_pParent->IsGlobalLog(); }

    CProcessRoot    *m_pRoot;

    CProcessElement *m_pCurChild;
    IXMLElementCollection *m_pCollection;
    long            m_lIndex;
    long            m_lMax;
    BOOL            m_fStartedEnumeration;
    BOOL            m_fSentEnumerationComplete;

    IXMLElement    *m_pElement;
    IXMLElement    *m_pChildElement;

    CProcessElementSink *m_pParent;

    CRunDeliveryAgent   *m_pRunAgent;
};

class CProcessRoot : public CProcessElement
{
public:
    CProcessRoot(CChannelAgent *pParent, IXMLElement *pRoot);
    ~CProcessRoot();

    CChannelAgent      *m_pChannelAgent;
    DWORD               m_dwCurSizeKB;
    int                 m_iTotalStarted;
    BOOL                m_fMaxSizeExceeded;

protected:
    ISubscriptionItem  *m_pDefaultStartItem;
    CUrlTrackingCache  *m_pTracking;

public:
    HRESULT     CreateStartItem(ISubscriptionItem **ppItem);
    IUnknown   *DefaultStartItem() { return m_pDefaultStartItem; }

    HRESULT     Run();

    // Called when E_PENDING DoChild returns (from m_pCurChild, a CProcessChannel)
    HRESULT     OnChildDone(CProcessElement *pChild, HRESULT hr);
    HRESULT     OnAgentEnd(const SUBSCRIPTIONCOOKIE *, long, HRESULT, LPCWSTR, BOOL);

    BOOL        IsPaused() { return m_pChannelAgent->IsPaused(); }
    BOOL        IsChannelFlagSet(DWORD dw) { return m_pChannelAgent->IsChannelFlagSet(dw); }

//  HRESULT     ProcessLogin(IXMLElement *pElement);
    HRESULT     DoTrackingFromItem(IXMLElement *pItem, LPCWSTR pwszUrl, BOOL fForceLog);
    HRESULT     DoTrackingFromLog(IXMLElement *pItem);

protected:
    HRESULT     ProcessItemInEnum(LPCWSTR pwszTagName, IXMLElement *pItem);

    LPCWSTR     GetBaseUrl() { return m_pChannelAgent->GetUrl(); }
};

class CProcessChannel : public CProcessElement
{
public:
    CProcessChannel(CProcessElementSink *pParent, CProcessRoot *pRoot, IXMLElement *pItem);
    ~CProcessChannel();
 
    HRESULT     Run();

    void        SetGlobalLogFlag(BOOL flag) { m_fglobalLog = flag; }

protected:
    HRESULT     ProcessItemInEnum(LPCWSTR pwszTagName, IXMLElement *pItem);

    LPCWSTR     GetBaseUrl() { if (m_bstrBaseUrl) return m_bstrBaseUrl; return m_pParent->GetBaseUrl(); }

    BOOL        IsGlobalLog() { return m_fglobalLog; }

    HRESULT     CheckPreCache();

    BOOL        m_fDownloadedHREF;
    BSTR        m_bstrBaseUrl;
    BOOL        m_fglobalLog;
};

class CProcessItem : public CProcessElement
{
public:
    CProcessItem(CProcessElementSink *pParent, CProcessRoot *pRoot, IXMLElement *pItem);
    ~CProcessItem();

protected:
    HRESULT     ProcessItemInEnum(LPCWSTR pwszTagName, IXMLElement *pItem);
    HRESULT     EnumerationComplete();

    BSTR        m_bstrAnchorURL;
    BOOL        m_fScreenSaver;
    BOOL        m_fDesktop;
    BOOL        m_fEmail;
};

class CProcessSchedule : public CProcessElement
{
public:
    CProcessSchedule(CProcessElementSink *pParent, CProcessRoot *pRoot, IXMLElement *pItem);
 
    HRESULT     Run();

protected:
    HRESULT     ProcessItemInEnum(LPCWSTR pwszTagName, IXMLElement *pItem);
    HRESULT     EnumerationComplete();

    CDF_TIME    m_timeInterval;
    CDF_TIME    m_timeEarliest;
    CDF_TIME    m_timeLatest;

    SYSTEMTIME  m_stStartDate;
    SYSTEMTIME  m_stEndDate;

public:
    TASK_TRIGGER m_tt;
};

class CExtractSchedule : public CProcessElement
{
public:
    CExtractSchedule(IXMLElement *pEle, CExtractSchedule *m_pExtractRoot);
    HRESULT     ProcessItemInEnum(LPCWSTR pwszTagName, IXMLElement *pItem);
    HRESULT     GetTaskTrigger(TASK_TRIGGER *ptt);

    virtual HRESULT    Run();

    TASK_TRIGGER m_tt;
    CExtractSchedule *m_pExtractRoot;

protected:    
    LPCWSTR     GetBaseUrl() { return NULL; }
};

#endif // _CDFAGENT_H