//------------------------------------------------------------------------------
// File: Schedule.cpp
//
// Desc: DirectShow base classes.
//
// Copyright (c) 1996-2001 Microsoft Corporation.  All rights reserved.
//------------------------------------------------------------------------------


#include <streams.h>

#pragma warning(disable:4706)   // C4706: assignment within conditional expression

// DbgLog values (all on LOG_TIMING):
//
// 2 for schedulting, firing and shunting of events
// 3 for wait delays and wake-up times of event thread
// 4 for details of whats on the list when the thread awakes

/* Construct & destructors */

CAMSchedule::CAMSchedule( HANDLE ev )
: CBaseObject(TEXT("CAMSchedule"))
, head(&z, 0), z(0, MAX_TIME)
, m_dwNextCookie(0), m_dwAdviseCount(0)
, m_pAdviseCache(0), m_dwCacheCount(0)
, m_ev( ev ) {
    head.m_dwAdviseCookie = z.m_dwAdviseCookie = 0;
}

CAMSchedule::~CAMSchedule() {
    m_Serialize.Lock();

    // Delete cache
    CAdvisePacket * p = m_pAdviseCache;
    while(p) {
        CAdvisePacket *const p_next = p->m_next;
        delete p;
        p = p_next;
    }

    ASSERT(m_dwAdviseCount == 0);
    // Better to be safe than sorry
    if(m_dwAdviseCount > 0) {
        DumpLinkedList();
        while(!head.m_next->IsZ()) {
            head.DeleteNext();
            --m_dwAdviseCount;
        }
    }

    // If, in the debug version, we assert twice, it means, not only
    // did we have left over advises, but we have also let m_dwAdviseCount
    // get out of sync. with the number of advises actually on the list.
    ASSERT(m_dwAdviseCount == 0);

    m_Serialize.Unlock();
}

/* Public methods */

DWORD CAMSchedule::GetAdviseCount() {
    // No need to lock, m_dwAdviseCount is 32bits & declared volatile
    return m_dwAdviseCount;
}

REFERENCE_TIME CAMSchedule::GetNextAdviseTime() {
    CAutoLock lck(&m_Serialize); // Need to stop the linked list from changing
    return head.m_next->m_rtEventTime;
}

DWORD_PTR CAMSchedule::AddAdvisePacket
( const REFERENCE_TIME & time1
, const REFERENCE_TIME & time2
, HANDLE h, BOOL periodic
) {
    // Since we use MAX_TIME as a sentry, we can't afford to
    // schedule a notification at MAX_TIME
    ASSERT(time1 < MAX_TIME);
    DWORD_PTR Result;
    CAdvisePacket * p;

    m_Serialize.Lock();

    if(m_pAdviseCache) {
        p = m_pAdviseCache;
        m_pAdviseCache = p->m_next;
        --m_dwCacheCount;
    }
    else {
        p = new CAdvisePacket();
    }
    if(p) {
        p->m_rtEventTime = time1; p->m_rtPeriod = time2;
        p->m_hNotify = h; p->m_bPeriodic = periodic;
        Result = AddAdvisePacket(p);
    }
    else Result = 0;

    m_Serialize.Unlock();

    return Result;
}

HRESULT CAMSchedule::Unadvise(DWORD_PTR dwAdviseCookie) {
    HRESULT hr = S_FALSE;
    CAdvisePacket * p_prev = &head;
    CAdvisePacket * p_n;
    m_Serialize.Lock();
    while(p_n = p_prev->Next()) // The Next() method returns NULL when it hits z
    {
        if(p_n->m_dwAdviseCookie == dwAdviseCookie) {
            Delete(p_prev->RemoveNext());
            --m_dwAdviseCount;
            hr = S_OK;
            // Having found one cookie that matches, there should be no more
#ifdef DEBUG
            while(p_n = p_prev->Next()) {
                ASSERT(p_n->m_dwAdviseCookie != dwAdviseCookie);
                p_prev = p_n;
            }
#endif
            break;
        }
        p_prev = p_n;
    };
    m_Serialize.Unlock();
    return hr;
}

REFERENCE_TIME CAMSchedule::Advise( const REFERENCE_TIME & rtTime ) {
    REFERENCE_TIME  rtNextTime;
    CAdvisePacket * pAdvise;

    DbgLog((LOG_TIMING, 2,
        TEXT("CAMSchedule::Advise( %lu ms )"), ULONG(rtTime / (UNITS / MILLISECONDS))));

    CAutoLock lck(&m_Serialize);

#ifdef DEBUG
    if(DbgCheckModuleLevel(LOG_TIMING, 4)) DumpLinkedList();
#endif

    //  Note - DON'T cache the difference, it might overflow 
    while(rtTime >= (rtNextTime = (pAdvise=head.m_next)->m_rtEventTime) &&
        !pAdvise->IsZ()) {
        ASSERT(pAdvise->m_dwAdviseCookie); // If this is zero, its the head or the tail!!

        ASSERT(pAdvise->m_hNotify != INVALID_HANDLE_VALUE);

        if(pAdvise->m_bPeriodic == TRUE) {
            ReleaseSemaphore(pAdvise->m_hNotify,1,NULL);
            pAdvise->m_rtEventTime += pAdvise->m_rtPeriod;
            ShuntHead();
        }
        else {
            ASSERT(pAdvise->m_bPeriodic == FALSE);
            EXECUTE_ASSERT(SetEvent(pAdvise->m_hNotify));
            --m_dwAdviseCount;
            Delete(head.RemoveNext());
        }

    }

    DbgLog((LOG_TIMING, 3,
        TEXT("CAMSchedule::Advise() Next time stamp: %lu ms, for advise %lu."),
        DWORD(rtNextTime / (UNITS / MILLISECONDS)), pAdvise->m_dwAdviseCookie ));

    return rtNextTime;
}

/* Private methods */

DWORD_PTR CAMSchedule::AddAdvisePacket( CAdvisePacket * pPacket ) {
    ASSERT(pPacket->m_rtEventTime >= 0 && pPacket->m_rtEventTime < MAX_TIME);
    ASSERT(CritCheckIn(&m_Serialize));

    CAdvisePacket * p_prev = &head;
    CAdvisePacket * p_n;

    const DWORD_PTR Result = pPacket->m_dwAdviseCookie = ++m_dwNextCookie;
    // This relies on the fact that z is a sentry with a maximal m_rtEventTime
    for(;;p_prev = p_n)
    {
        p_n = p_prev->m_next;
        if(p_n->m_rtEventTime >= pPacket->m_rtEventTime) 
            break;
    }
    p_prev->InsertAfter( pPacket );
    ++m_dwAdviseCount;

    DbgLog((LOG_TIMING, 2, TEXT("Added advise %lu, for thread 0x%02X, scheduled at %lu"),
        pPacket->m_dwAdviseCookie, GetCurrentThreadId(), (pPacket->m_rtEventTime / (UNITS / MILLISECONDS)) ));

    // If packet added at the head, then clock needs to re-evaluate wait time.
    if(p_prev == &head) SetEvent(m_ev);

    return Result;
}

void CAMSchedule::Delete( CAdvisePacket * pPacket ) {
    if(m_dwCacheCount >= dwCacheMax) delete pPacket;
    else {
        m_Serialize.Lock();
        pPacket->m_next = m_pAdviseCache;
        m_pAdviseCache = pPacket;
        ++m_dwCacheCount;
        m_Serialize.Unlock();
    }
}


// Takes the head of the list & repositions it
void CAMSchedule::ShuntHead() {
    CAdvisePacket * p_prev = &head;
    CAdvisePacket * p_n;

    m_Serialize.Lock();
    CAdvisePacket *const pPacket = head.m_next;

    // This will catch both an empty list,
    // and if somehow a MAX_TIME time gets into the list
    // (which would also break this method).
    ASSERT(pPacket->m_rtEventTime < MAX_TIME);

    // This relies on the fact that z is a sentry with a maximal m_rtEventTime
    for(;;p_prev = p_n)
    {
        p_n = p_prev->m_next;
        if(p_n->m_rtEventTime > pPacket->m_rtEventTime) 
            break;
    }
    
    // If p_prev == pPacket then we're already in the right place
    if(p_prev != pPacket)
    {
        head.m_next = pPacket->m_next;
        (p_prev->m_next = pPacket)->m_next = p_n;
        }
    #ifdef DEBUG
        DbgLog((LOG_TIMING, 2, TEXT("Periodic advise %lu, shunted to %lu"),
            pPacket->m_dwAdviseCookie, (pPacket->m_rtEventTime / (UNITS / MILLISECONDS)) ));
#endif
    m_Serialize.Unlock();
}


#ifdef DEBUG
void CAMSchedule::DumpLinkedList() {
    m_Serialize.Lock();
    int i=0;
    DbgLog((LOG_TIMING, 1, TEXT("CAMSchedule::DumpLinkedList() this = 0x%p"), this));
    for(CAdvisePacket * p = &head
        ; p
        ; p = p->m_next         , i++) {
        DbgLog((LOG_TIMING, 1, TEXT("Advise List # %lu, Cookie %d,  RefTime %lu"),
            i,
            p->m_dwAdviseCookie,
            p->m_rtEventTime / (UNITS / MILLISECONDS)
            ));
    }
    m_Serialize.Unlock();
}
#endif

