// Rn3DLoader.cpp : Implementation of CRn3DLoader
// This is probably the main object as it represents the 3DLoader itself.
// You can see it in the Link Editor when you execute the 
// Rs3DLoader.Create3DLoader() script command.

#include "stdafx.h"
#include "resource.h"
#include "Rn3DLoader.h"
#include "RcUpdate3DLoader.h"


// CRn3DLoader

// OnGetValue
// Called when the connector's value is required. If this node should
// to do a computation, place it here
STDMETHODIMP CRn3DLoader::OnGetValue(CONID conID, IRsDataCache* pDataCache, VARIANT* pVtData, RtBool* pHandled)
{
    HRESULT hr;

    if (m_pEncapsUnkn == NULL)
        return E_FAIL;

    // Call Encapsulator OnGetValue method
    if (FAILED(hr = m_pEncapsUnkn->OnGetValue(conID, pDataCache, pVtData, pHandled)))
        return hr;

    return S_OK;
}

// OnSetValue
// Called when a connector's value is changing. For example it is
// possible to check and change the value here if it is out of range.
STDMETHODIMP CRn3DLoader::OnSetValue(CONID conID, IRsDataCache* pDataCache, VARIANT* pvtData, RtBool* pHandled)
{
    HRESULT hr;

    if (m_pEncapsUnkn == NULL)
        return E_FAIL;

    // Call Encapsulator OnSetValue method
    if (FAILED(hr = m_pEncapsUnkn->OnSetValue(conID, pDataCache, pvtData, pHandled)))
        return hr;

    // Handle particular connector here if necessary.
    if (conID == CON_3DLOADER_NODE_DIR_PATH)
    {
        // The input parameter has changed, so set the flag and send update command.
        // In OnSetValue, OnInvalidate and actually all other callbacks you never
        // should access the Dependency Graph. The only exception is the OnGetValue
        // method, but in this case you can read only node's own connectors anyway.
        // Therefore we have to create a command and do it through it.
        m_dwFlags |= RN3DLDRFLG_3DLNODE_STNG_CHANGED;
        SendUpdateCommand();
    }
    else if (conID == CON_ENCAPS_OWNER) 
    {
        if (pvtData->uintVal == EMSG_POST_LOAD)
        {
            // Check if all the connectors exist
            if (FAILED(hr = AddConsIfNecessary()))
                return hr;

            m_dwFlags |= RN3DLDRFLG_3DLNODE_POST_LOAD;

            SendUpdateCommand();    // Send the first update command "manually"
        }
    }

    return S_OK;
}

// OnDefaultValue
// Called to set default value for specific connector. This method is
// automatically called after OnLoadDefault method for each connector too.
STDMETHODIMP CRn3DLoader::OnDefaultValue(CONID conID, VARIANT* pVtData, RtBool* pHandled)
{
    HRESULT hr;

    if (m_pEncapsUnkn == NULL)
        return E_FAIL;

    // Call Encapsulator OnSetValue method
    if (FAILED(hr = m_pEncapsUnkn->OnDefaultValue(conID, pVtData, pHandled)))
        return hr;

    // Set default values for the connectors.
    if (conID == CON_3DLOADER_NODE_DIR_PATH)
    {
        CComVariant defVal("");
        if (FAILED(hr = defVal.Detach(pVtData)))
            return hr;
        *pHandled = TRUE;
    }

    return S_OK;
}

// OnInvalidate
// Called if a connector was invalidated
STDMETHODIMP CRn3DLoader::OnInvalidate(CONID conID, RtBool* pHandled)
{
    HRESULT hr;

    if (m_pEncapsUnkn == NULL)
        return E_FAIL;

    // Call Encapsulator OnInvalidate method
    if (FAILED(hr = m_pEncapsUnkn->OnInvalidate(conID, pHandled)))
        return hr;

    if (conID == CON_3DLOADER_NODE_DIR_PATH)
    {
        m_dwFlags |= RN3DLDRFLG_3DLNODE_STNG_CHANGED;
        SendUpdateCommand();
    }

    return S_OK;
}

// OnInitialize
// Called when the node is created.
// We create here all the necessary stuff for this node
STDMETHODIMP CRn3DLoader::OnInitialize(void)
{
    HRESULT hr;
    
    // Remember Encapsulator's IRsUnknownNode interface
    if (FAILED(hr = m_pUnkAgg->QueryInterface(DEF_GUIDNAME(IRsUnknownNode), (void **)&m_pEncapsUnkn)))
        return hr;

    // Call Encapsulator Initialize method
    if (FAILED(hr = m_pEncapsUnkn->OnInitialize()))
        return hr;

    // Create 3DLoader function set and initialize it
    CRp3DLoader::Create3DLoaderFnSet(&m_sp3DLoaderFnSet);
    if (m_sp3DLoaderFnSet == NULL)
        return E_FAIL;
    
    if (FAILED(hr = m_sp3DLoaderFnSet->Assign(m_pUnkAgg)))
        return hr;

    // Now get other function sets that could we find helpful
    CComPtr<IRpKernel> spKernelPackage;
    CRp3DLoader::GetKernelPackage(&spKernelPackage);
    if (spKernelPackage == NULL)
        return E_UNEXPECTED;

    CComPtr<IRsUnknownPackage> spPackage;
    spKernelPackage->FindPackage(DEF_GUIDNAME(CRpFileIOPck), &spPackage);
    CComQIPtr<IRpFileIOPck> spRpFileIOPck = spPackage;
    if (spRpFileIOPck == NULL)
        return E_FAIL;  // This node needs FileIO package too!

    spRpFileIOPck->CreateDiskFileSupport(&m_spRfFileSupport);
    if (m_spRfFileSupport == NULL)
        return E_FAIL;

    spRpFileIOPck->GetFileIOGenericHelper(&m_spFileIOGeneric);
    if (m_spFileIOGeneric == NULL)
        return E_FAIL;

    return S_OK;
}

// Add connectors for Rn3DLoader node if they do not exist
HRESULT CRn3DLoader::AddConsIfNecessary(void)
{
    HRESULT hr;

    CComQIPtr<IRsBaseNode> spBase = m_pUnkAgg;   // Get the BaseNode interface
    if (spBase == NULL) {
        ATLASSERT(!"Node not agregate IRsBaseNode!");
        return E_UNEXPECTED;
    }

    // Connectors base parameter table:
    RtConDesc sDirPath = {&CON_3DLOADER_NODE_DIR_PATH, L"FocusDir", L"Focused directory", VT_BSTR, NULL, CONFLG_IN};

    // Add connectors if necessary:
    CComPtr<IRsConnector> spDirPath;
    spBase->FindConById(CON_3DLOADER_NODE_DIR_PATH, &spDirPath);
    if (spDirPath == NULL)
    {
        if (FAILED(hr = spBase->AddNewConnector(&sDirPath, &spDirPath)))
            return hr;
    }

    return S_OK;
}

// OnLoadDefault
// It is called only once, when the node is created and initialized.
// Its purpose is to create and initialize connectors. When the node
// is de-serialized, this method is not called, the connectors will be
// created and initialized by the de-serialization process.
STDMETHODIMP CRn3DLoader::OnLoadDefault(void)
{
    HRESULT hr;

    if (m_pEncapsUnkn == NULL)
        return E_UNEXPECTED;

    // Call Encapsulator OnLoadDefault method
    if (FAILED(hr = m_pEncapsUnkn->OnLoadDefault()))
        return hr;

    if (FAILED(hr = AddConsIfNecessary()))
        return hr;

    m_dwFlags |= RN3DLDRFLG_3DLNODE_POST_LOAD;

    return S_OK;
}

// OnDestroy
// Called before the node will be destroyed. All the memory references
// should be released here.
STDMETHODIMP CRn3DLoader::OnDestroy(void)
{
    HRESULT hr;

    // Deactivate timer if it is still active
    DeactivateTimer();

    // Call Encapsulator Destroy method
    if (m_pEncapsUnkn != NULL)
        hr = m_pEncapsUnkn->OnDestroy();
    else
        hr = S_OK;

    m_pEncapsUnkn.Release();
    m_sp3DLoaderFnSet.Release();
    m_spRfFileSupport.Release();

    return hr;
}

// create aggregated CRn3DLoader (CRn3DLoader agregates Encapsulator)
HRESULT CRn3DLoader::FinalConstruct()
{
    CComPtr<IRpKernel> spKernelPackage;
    HRESULT hr = CRp3DLoader::GetKernelPackage(&spKernelPackage);
    if (FAILED(hr))
        return hr;
    
    if (FAILED(hr = spKernelPackage->CreateEncapsulator(GetControllingUnknown(), &m_pUnkAgg)))
        return hr;
    
	return S_OK;
}

//////////////////////////////////////////////////////////////////////
// Timer functions and methods
// The node is monitoring the focused directory for changes. The timer
// is used to do this job together with the animation during thumbnails and
// directory synchronization.
// Much better would be to use separate thread for this and the StartDirectoryChangesMonitor
// method of the IRfFileSupport function set, but for this sample timer
// is enough
HRESULT CRn3DLoader::DeactivateTimer(void)
{
    if (m_hTimerQueue != INVALID_HANDLE_VALUE)
    {
        DeleteTimerQueueTimer(m_hTimerQueue, m_hSyncTimer, INVALID_HANDLE_VALUE);
        m_hTimerQueue = INVALID_HANDLE_VALUE;
        m_hSyncTimer = INVALID_HANDLE_VALUE;
        m_dwSyncTime = 0;
    }

    m_dwFlags &= ~RN3DLDRFLG_3DLNODE_UPD_CMD_SENT;

    return S_OK;
}

HRESULT CRn3DLoader::ActivateTimer(RtDWORD dwSyncTime)
{
    if (dwSyncTime == 0)
        return DeactivateTimer();

    if (m_hTimerQueue == INVALID_HANDLE_VALUE)
    {
        m_hTimerQueue = CreateTimerQueue();
        if (m_hTimerQueue != INVALID_HANDLE_VALUE) 
        {
            CreateTimerQueueTimer(&m_hSyncTimer, m_hTimerQueue, SyncCallback, 
                                  this, dwSyncTime, dwSyncTime, WT_EXECUTEINTIMERTHREAD);

            m_dwSyncTime = dwSyncTime;
        }
    } 
    else if (m_dwSyncTime != dwSyncTime)
    {
        ChangeTimerQueueTimer(m_hTimerQueue, m_hSyncTimer, dwSyncTime, dwSyncTime);
        m_dwSyncTime = dwSyncTime;
    }

    return S_OK;
}

VOID CALLBACK CRn3DLoader::SyncCallback(PVOID lpParameter, BOOLEAN TimerOrWaitFired)
{
    CRn3DLoader *pCRn3DLoader = (CRn3DLoader *)lpParameter;

    if (pCRn3DLoader == NULL)
        return;

    // We cannot access the Dependency Graph from timer thread so
    // we will use the synchronization command again.
    pCRn3DLoader->SendUpdateCommand();
}

HRESULT CRn3DLoader::SendUpdateCommand(void)
{
    HRESULT hr;

    if (m_dwFlags & RN3DLDRFLG_3DLNODE_UPD_CMD_SENT)
        return S_OK;

    // Check whether the node is in Dependency Graph
    CComQIPtr<IRsBaseNode> spBase = m_pUnkAgg;
    if (spBase == NULL)
        return E_UNEXPECTED;

    CComBSTR bstrFullName;
    spBase->GetFullName(NULL, &bstrFullName);
    if (bstrFullName.Length() == 0)
        return S_FALSE;

    CComPtr<IRpKernel> spKernelPckg;
    CRp3DLoader::GetKernelPackage(&spKernelPckg);
    if (spKernelPckg == NULL)
        return E_UNEXPECTED;

    CComQIPtr<IRnKernel> spKernel;
    spKernelPckg->GetKernelNode(&spKernel);
    CComQIPtr<IRnEncapsulator> spKernelEnc = spKernel;
    if (spKernelEnc == NULL)
        return S_FALSE;

    if (spKernelEnc->FindNodeByName(bstrFullName, NULL) != S_OK)
        return S_FALSE;
    
    // Create command and send it
    CComPtr<IRsNode> spCmdNode;
    CRp3DLoader::CreateCmdObj(DEF_GUIDNAME_PTR(IRcUpdate3DLoader), &spCmdNode);
    if (spCmdNode == NULL)
        return E_FAIL;

    spCmdNode->SetValue(CON_CMD_3DLOADERUPDCMD_NODE_ID, CComVariant(bstrFullName));

    m_dwFlags |= RN3DLDRFLG_3DLNODE_UPD_CMD_SENT;
    if (FAILED(hr = CRp3DLoader::SendCommand(spCmdNode)))
	    m_dwFlags &= ~RN3DLDRFLG_3DLNODE_UPD_CMD_SENT;

    return hr;
}

// Compare the directory and current thumbnails' representation
// If there are some changes, apply them.
// Returns S_OK if there are some changes and the thumbnails should be re-arranged
// Returns S_FALSE if there are none changes
HRESULT CRn3DLoader::CheckForDirChanges(void)
{
    CComQIPtr<IRnEncapsulator> spThisEnc = m_pEncapsUnkn;
    if (spThisEnc == NULL)
        return E_UNEXPECTED;

    // In all chases check the directory for changes
    LPSAFEARRAY pDirContentArray = NULL;
    CComSafeArray<IUnknown *> saDirContent((ULONG)0);
    m_spRfFileSupport->EnumDirectoryContent(m_bstrDirPath, L"*.*", &pDirContentArray);
    if (pDirContentArray != NULL)
        saDirContent.Attach(pDirContentArray);
    else {
        // If the directory is invalid, stop timer and remove all thumbnails
        // If the directory input parameter will be changed, we will be notified about it
        m_sp3DLoaderFnSet->RemoveAllThumbnails();
        DeactivateTimer();
    }

    // Go through all the items and check them
    BOOL bArrangeThumbnails = FALSE;
    m_sp3DLoaderFnSet->ThumbnailsCheckStart();  // Tell the 3DLoader function set that we start thumbnails update
    ULONG nContentSize = saDirContent.GetCount();
    for (ULONG ulI = 0; ulI < nContentSize; ulI++)
    {
        CComQIPtr<IRsFileInfo> pFileInfo = saDirContent.GetAt(ulI);

        // Skip current path and return path marks
        CComBSTR bstrFileName;
        pFileInfo->get_Name(&bstrFileName);
        if ((bstrFileName == L".") || (bstrFileName == L".."))
            continue;

        // Skip all directories are we are not interesting about them
        DWORD dwFileAttributes = 0;
        pFileInfo->get_Attributes(&dwFileAttributes);
        if (dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
            continue;

        // Check if the file type is known by the file extension
        // Skip all unknown file types
        wchar_t szFileName[_MAX_FNAME] = L"", szFileExtension[_MAX_EXT] = L"";

#if defined(_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES)
        _wsplitpath_s(bstrFileName, NULL, 0, NULL, 0, szFileName, _MAX_FNAME, szFileExtension, _MAX_EXT);
#else
        _wsplitpath(bstrFileName, NULL, NULL, szFileName, szFileExtension);
#endif
        if (m_spFileIOGeneric->FindFileIOTypeByExt(szFileExtension, NULL) != S_OK)
            continue;

        // We are using the 3DLoader encapsulator to maintain the thumbnails.
        // So it is enough use the encapsulator's method FindNodeByName to
        // find the proper thumbnail
        CComPtr<IRsNode> spThumbnailNode;
        spThisEnc->FindNodeByName(bstrFileName, &spThumbnailNode);

        if (spThumbnailNode == NULL) {
            m_sp3DLoaderFnSet->AddThumbnailNode(pFileInfo);
            bArrangeThumbnails = TRUE;
        }
        else
            m_sp3DLoaderFnSet->UpdateThumbnailNode(spThumbnailNode, pFileInfo);
    }

    // Tell the function set that we finished the thumbnails update
    if (m_sp3DLoaderFnSet->ThumbnailsCheckFinish() == S_OK)
        bArrangeThumbnails = TRUE;

    return bArrangeThumbnails ? S_OK : S_FALSE;
}

// This method is called from RcUpdate3DLoader. In other words, the method
// is called from master thread that can access the Dependency Graph too
// as it could be required during the update
STDMETHODIMP CRn3DLoader::Update()
{
    CComQIPtr<IRsBaseNode> spThisBase = m_pUnkAgg;
    if (spThisBase == NULL)
        return E_UNEXPECTED;

    // Check if input parameter has changed
    if (m_dwFlags & (RN3DLDRFLG_3DLNODE_STNG_CHANGED|RN3DLDRFLG_3DLNODE_POST_LOAD))
    {
        m_dwFlags &= ~(RN3DLDRFLG_3DLNODE_STNG_CHANGED|RN3DLDRFLG_3DLNODE_POST_LOAD);
        CComVariant vDirPath;
        spThisBase->GetValue(CON_3DLOADER_NODE_DIR_PATH, &vDirPath);
        CComBSTR bstrDirPath;
        if (SUCCEEDED(vDirPath.ChangeType(VT_BSTR)))
            bstrDirPath = vDirPath.bstrVal;

        if ((bstrDirPath.Length() == 0) || (m_bstrDirPath != bstrDirPath))
        {
            m_sp3DLoaderFnSet->RemoveAllThumbnails();
            m_bstrDirPath = bstrDirPath;
            DeactivateTimer();  // If the directory is invalid, deactivate timer as it is not more necessary
        }

        m_sp3DLoaderFnSet->Add3DLoaderBase();
    }

    // If not re-arranging, then check for directory changes
    if (!(m_dwFlags & RN3DLDRFLG_3DLNODE_ARRANGING))
    {
        if (CheckForDirChanges() == S_OK)   // If there was an thumbnail added or removed, re-arrange them
        {
            m_sp3DLoaderFnSet->ArrangeThumbnails();
            m_dwFlags |= RN3DLDRFLG_3DLNODE_ARRANGING;
            // Activate timer to higher rate as we want continuous animation
            ActivateTimer(RN3DLDCNS_SYNC_TIME_ARRANGING);
        }
    }
    else {
        if (m_sp3DLoaderFnSet->AnimateThumbnails() != S_OK)
        {
            m_dwFlags &= ~RN3DLDRFLG_3DLNODE_ARRANGING;
            // Set timer to a regular time in which only the directory will be checked
            ActivateTimer(RN3DLDCNS_SYNC_TIME);
        }
    }

    m_dwFlags &= ~RN3DLDRFLG_3DLNODE_UPD_CMD_SENT;

    return S_OK;
}
