#include "privcpp.h"

// Constructor
CPackage_IPersistStorage::CPackage_IPersistStorage(CPackage *pPackage) : 
    _pPackage(pPackage)
{
    ASSERT(_cRef == 0); 
    ASSERT(_psState == PSSTATE_UNINIT);
}

CPackage_IPersistStorage::~CPackage_IPersistStorage()
{
    DebugMsg(DM_TRACE,"CPackage_IPersistStorage destroyed with ref count %d",_cRef);
}

//////////////////////////////////
//
// IUnknown Methods...
//
HRESULT CPackage_IPersistStorage::QueryInterface(REFIID iid, void ** ppv)
{
    return _pPackage->QueryInterface(iid,ppv);  // delegate to CPackage
}

ULONG CPackage_IPersistStorage::AddRef(void) 
{
    _cRef++;                                    // interface ref count for debug
    return _pPackage->AddRef();                 // delegate to CPackage
}

ULONG CPackage_IPersistStorage::Release(void)
{
    _cRef--;                                    // interface ref count for debug
    return _pPackage->Release();                // delegate to CPackage
}

//////////////////////////////////
//
// IPersistStorage Methods...
//
HRESULT CPackage_IPersistStorage::GetClassID(LPCLSID pClassID)
{
    DebugMsg(DM_TRACE, "pack ps - GetClassID() called.");
    
    if (_psState == PSSTATE_UNINIT)  
        return E_UNEXPECTED;
    
    if (pClassID == NULL)
        return E_INVALIDARG;
    
    *pClassID = CLSID_CPackage;
    return S_OK;
}

HRESULT CPackage_IPersistStorage::IsDirty(void)
{
    DebugMsg(DM_TRACE,
        "pack ps - IsDirty() called. _fIsDirty==%d",
        (INT)_pPackage->_fIsDirty);
    if (_psState == PSSTATE_UNINIT)  
        return E_UNEXPECTED;
    
    return (_pPackage->_fIsDirty ? S_OK : S_FALSE);
}

HRESULT CPackage_IPersistStorage::InitNew(IStorage *pstg)
{
    HRESULT hr;

    DebugMsg(DM_TRACE, "pack ps - InitNew() called.");

    if (_psState != PSSTATE_UNINIT)  
        return E_UNEXPECTED;

    if (!pstg)  
        return E_POINTER;

    // Create a stream to save the package and cache the pointer.  By doing 
    // this now we ensure being able to save in low memory conditions.
    //
    hr = pstg->CreateStream(SZCONTENTS,STGM_DIRECT | STGM_CREATE | 
                            STGM_READWRITE | STGM_SHARE_EXCLUSIVE, 0, 0, 
                            &_pPackage->_pstm);
    if (SUCCEEDED(hr))
    {
        hr = WriteFmtUserTypeStg(pstg, (CLIPFORMAT)_pPackage->_cf,SZUSERTYPE);
        if (SUCCEEDED(hr))
        {     
            _pPackage->_fIsDirty = TRUE;
            _psState = PSSTATE_SCRIBBLE;
            
            _pPackage->_pIStorage = pstg;       // cache the IStorage pointer
            _pPackage->_pIStorage->AddRef();    // but don't forget to addref it!

            DebugMsg(DM_TRACE, "            leaving InitNew()");
        }
        else
        {
            _pPackage->_pstm->Release();
            _pPackage->_pstm = NULL;
        }
    }

    return hr;
}

    
HRESULT CPackage_IPersistStorage::Load(IStorage *pstg)
{
    HRESULT     hr;
    LPSTREAM    pstm = NULL;         // package contents
    CLSID       clsid;

    DebugMsg(DM_TRACE, "pack ps - Load() called.");

    if (_psState != PSSTATE_UNINIT) {
        DebugMsg(DM_TRACE,"            wrong state!!");
        return E_UNEXPECTED;
    }
    
    if (!pstg) {
        DebugMsg(DM_TRACE,"            bad pointer!!");
        return E_POINTER;
    }
    
    
    // check to make sure this is one of our storages
    hr = ReadClassStg(pstg, &clsid);
    if (SUCCEEDED(hr) &&
        (clsid != CLSID_CPackage && clsid != CLSID_OldPackage) || FAILED(hr))
    {
        DebugMsg(DM_TRACE,"            bad storage type!!");
        return E_UNEXPECTED;
    }
    
    hr = pstg->OpenStream(SZCONTENTS,0, STGM_DIRECT | STGM_READWRITE | 
                          STGM_SHARE_EXCLUSIVE, 0, &pstm);
    if (FAILED(hr)) {
        DebugMsg(DM_TRACE,"            couldn't open contents stream!!");
        DebugMsg(DM_TRACE,"            hr==%Xh",hr);
        goto ErrRet;
    }

    hr = _pPackage->PackageReadFromStream(pstm);
    if (FAILED(hr))
        goto ErrRet;
    
    _pPackage->_pstm      = pstm;
    _pPackage->_pIStorage = pstg;
    pstg->AddRef();
    
    _psState = PSSTATE_SCRIBBLE;
    _pPackage->_fIsDirty = FALSE;
    _pPackage->_fLoaded  = TRUE;
    
    DebugMsg(DM_TRACE, "            leaving Load()");
    return S_OK;
    
ErrRet:
    if (pstm)
        pstm->Release();
    return hr;
}

    
HRESULT CPackage_IPersistStorage::Save(IStorage *pstg, BOOL fSameAsLoad)
{
    HRESULT     hr;
    LPSTREAM    pstm=NULL;

    DebugMsg(DM_TRACE, "pack ps - Save() called.");
    
    // must come here from scribble state
    if ((_psState != PSSTATE_SCRIBBLE) && fSameAsLoad) {
        DebugMsg(DM_TRACE,"            bad state!!");
        return E_UNEXPECTED;
    }
    
    // must have an IStorage if not SameAsLoad
    if (!pstg && !fSameAsLoad) {
        DebugMsg(DM_TRACE,"            bad pointer!!");
        return E_POINTER;
    }
    
    // hopefully, the container calls WriteClassStg with our CLSID before
    // we get here, that way we can overwrite that and write out the old
    // packager's CLSID so that the old packager can read new packages.
    //
    if (FAILED(WriteClassStg(pstg,CLSID_OldPackage))) {
        DebugMsg(DM_TRACE,
            "            couldn't write CLSID to storage!!");
        return E_FAIL;
    }
    
    // 
    // ok, we have four possible ways we could be calling Save:
    //          1. we're creating a new package and saving to the same
    //             storage we received in InitNew
    //          2. We're creating a new package and saving to a different
    //             storage than we received in InitNew
    //          3. We were loaded by a container and we're saving to the
    //             same stream we received in Load
    //          4. We were loaded by a container and we're saving to a
    //             different stream than we received in Load
    //
    

    //////////////////////////////////////////////////////////////////
    //
    // Same Storage as Load
    //
    //////////////////////////////////////////////////////////////////
    
    if (fSameAsLoad) {          

        DebugMsg(DM_TRACE,"            Same as load.");
        
        LARGE_INTEGER   li = {0,0};
        pstm = _pPackage->_pstm;

        // If we're not dirty, so there's nothing new to save.
        
        if (FALSE == _pPackage->_fIsDirty) {
            DebugMsg(DM_TRACE, "            not saving cause we're not dirty!!");
            return S_OK;
        }
        
        // if we are dirty, set the seek pointer to the beginning and write
        // the package information to the stream
        hr = pstm->Seek(li, STREAM_SEEK_SET, NULL);
        if (FAILED(hr)) {
            DebugMsg(DM_TRACE, "            couldn't set contents pointer!!");
            return hr;
        }
        
        // case 1: new package
        if (!_pPackage->_fLoaded) {
            switch(_pPackage->_panetype) {
                LPTSTR temp;
                case PEMBED:
                    // if haven't created a temp file yet, then use the the
                    // file to be packaged to get our file contents from,
                    // otherwise we just use the temp file, because if we
                    // have a temp file, it contains the most recent info.
                    //
                    temp = _pPackage->_pEmbed->pszTempName;
            
                    if (!_pPackage->_pEmbed->pszTempName) {
                        DebugMsg(DM_TRACE, "      case 1a:not loaded, using initFile.");
                        _pPackage->_pEmbed->pszTempName = _pPackage->_pEmbed->fd.cFileName;
                    }
                    else {
                        DebugMsg(DM_TRACE, "      case 1b:not loaded, using tempfile.");
                    }
            
                    hr = _pPackage->PackageWriteToStream(pstm);
                    
                    // reset our temp name back, since we might have changed it
                    // basically, this just sets it to NULL if it was before
                    _pPackage->_pEmbed->pszTempName = temp;
                    break;

                case CMDLINK:
                    // nothing screwy to do here...just write out the info
                    // which we already have in memory.
                    hr = _pPackage->PackageWriteToStream(pstm);
                    break;
            }
            
            if (FAILED(hr))
                return hr;
        }
    
        // case 3: loaded package
        else {
            hr = _pPackage->PackageWriteToStream(pstm);
        }
    }
    
    //////////////////////////////////////////////////////////////////
    //
    // NEW Storage  
    //
    //////////////////////////////////////////////////////////////////
    
    else {

        DebugMsg(DM_TRACE,"            NOT same as load.");
        hr = pstg->CreateStream(SZCONTENTS,STGM_DIRECT | STGM_CREATE | 
                                STGM_READWRITE | STGM_SHARE_EXCLUSIVE, 0, 0, 
                                &pstm);
        if (FAILED(hr)) {
            DebugMsg(DM_TRACE, "            couldn't create contents stream!!");
            return hr;
        }
        WriteFmtUserTypeStg(pstg, (CLIPFORMAT)_pPackage->_cf,SZUSERTYPE);
        
        // case 2:
        if (!_pPackage->_fLoaded) {
            switch(_pPackage->_panetype) {
                LPTSTR temp;
                case PEMBED:
                    temp = _pPackage->_pEmbed->pszTempName;
            
                    if (!_pPackage->_pEmbed->pszTempName) {
                        DebugMsg(DM_TRACE, "      case 2a:not loaded, using initFile.");
                        _pPackage->_pEmbed->pszTempName = _pPackage->_pEmbed->fd.cFileName;
                    }            
                    else {
                        DebugMsg(DM_TRACE, "      case 2b:not loaded, using tempfile.");
                    }
            
                    hr = _pPackage->PackageWriteToStream(pstm);
            
                    // reset our temp name back, since we might have changed it
                    // basically, this just sets it to NULL if it was before
                    _pPackage->_pEmbed->pszTempName = temp;
                    break;

                case CMDLINK:
                    // nothing interesting to do here, other than write out
                    // the package.
                    hr = _pPackage->PackageWriteToStream(pstm);
                    break;
            }

            if (FAILED(hr)) 
                goto ErrRet;
            
        }
        
        // case 4:
        else {
            if (_pPackage->_panetype == PEMBED && _pPackage->_pEmbed->pszTempName ) {
                DebugMsg(DM_TRACE,"    case 4a:loaded, using tempfile.");
                hr = _pPackage->PackageWriteToStream(pstm);
                if (FAILED(hr))
                    goto ErrRet;
            }
            else {
                DebugMsg(DM_TRACE,"    case 4b:loaded, using stream.");
                ULARGE_INTEGER uli = { 0xFFFFFFFF, 0xFFFFFFFF };
                LARGE_INTEGER li = {0,0};
                
                hr = _pPackage->_pstm->Seek(li,STREAM_SEEK_SET,NULL);
                if (FAILED(hr))
                    goto ErrRet;
                
                hr = _pPackage->_pstm->CopyTo(pstm,uli,NULL,NULL);
                if (FAILED(hr))
                    goto ErrRet;
            }
        }
    ErrRet:
        UINT u = pstm->Release();
        DebugMsg(DM_TRACE,"          new stream released.  cRef==%d.",u);
        if (FAILED(hr))
            return hr;
    }
    
    _psState = PSSTATE_ZOMBIE;
    
    DebugMsg(DM_TRACE, "            leaving Save()");
    return S_OK;
}

    
HRESULT CPackage_IPersistStorage::SaveCompleted(IStorage *pstg)
{
    HRESULT     hr;
    LPSTREAM    pstm;
    
    DebugMsg(DM_TRACE, "pack ps - SaveCompleted() called.");

    // must be called from no-scribble or hands-off state
    if (!(_psState == PSSTATE_ZOMBIE || _psState == PSSTATE_HANDSOFF))
        return E_UNEXPECTED;
    
    // if we're hands-off, we'd better get a storage to re-init from
    if (!pstg && _psState == PSSTATE_HANDSOFF)
        return E_UNEXPECTED;
    
    // if pstg is NULL then we have everything we need, otherwise we must
    // release our held pointers and reinitialize.
    //
    if (pstg != NULL) {
        DebugMsg(DM_TRACE, "            getting new storage.");
        hr = pstg->OpenStream(SZCONTENTS, 0, STGM_DIRECT | STGM_READWRITE | 
                              STGM_SHARE_EXCLUSIVE, 0, &pstm);
        if (FAILED(hr))
            return hr;

        if (_pPackage->_pstm != NULL)
            _pPackage->_pstm->Release();
        
        // this will be reinitialized when it's needed
        if (_pPackage->_pstmFileContents != NULL) {
            _pPackage->_pstmFileContents->Release();
            _pPackage->_pstmFileContents = NULL;
        }
        
        if (_pPackage->_pIStorage != NULL)
            _pPackage->_pIStorage->Release();

        _pPackage->_pstm = pstm;
        _pPackage->_pIStorage = pstg;
        _pPackage->_pIStorage->AddRef();
    }
    
    _psState = PSSTATE_SCRIBBLE;
    _pPackage->_fIsDirty = FALSE;
    DebugMsg(DM_TRACE, "            leaving SaveCompleted()");
    return S_OK;
}

    
HRESULT CPackage_IPersistStorage::HandsOffStorage(void)
{
    DebugMsg(DM_TRACE, "pack ps - HandsOffStorage() called.");

    // must come from scribble or no-scribble.  a repeated call to 
    // HandsOffStorage is an unexpected error (bug in client).
    //
    if (_psState == PSSTATE_UNINIT || _psState == PSSTATE_HANDSOFF)
        return E_UNEXPECTED;
    
    // release our held pointers
    //
    if (_pPackage->_pstmFileContents != NULL) {
        _pPackage->_pstmFileContents->Release();
        _pPackage->_pstmFileContents = NULL;
    }
    
    if (_pPackage->_pstm != NULL) {
        _pPackage->_pstm->Release();
        _pPackage->_pstm = NULL;
    }
    
    if (_pPackage->_pIStorage != NULL) {
        _pPackage->_pIStorage->Release();
        _pPackage->_pIStorage = NULL;
    }
    
    _psState = PSSTATE_HANDSOFF;
    DebugMsg(DM_TRACE, "            leaving HandsOffStorage()");
    return S_OK;
}
