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


#include <streams.h>
#include <strmctl.h>

CBaseStreamControl::CBaseStreamControl(__inout HRESULT *phr)
: m_StreamState(STREAM_FLOWING)
, m_StreamStateOnStop(STREAM_FLOWING) // means no pending stop
, m_tStartTime(MAX_TIME)
, m_tStopTime(MAX_TIME)
, m_StreamEvent(FALSE, phr)
, m_dwStartCookie(0)
, m_dwStopCookie(0)
, m_pRefClock(NULL)
, m_FilterState(State_Stopped)
, m_bIsFlushing(FALSE)
, m_bStopSendExtra(FALSE)
{}

CBaseStreamControl::~CBaseStreamControl()
{
    // Make sure we release the clock.
    SetSyncSource(NULL);
    return;
}


STDMETHODIMP CBaseStreamControl::StopAt(const REFERENCE_TIME * ptStop, BOOL bSendExtra, DWORD dwCookie)
{
    CAutoLock lck(&m_CritSec);
    m_bStopSendExtra = FALSE;    // reset
    m_bStopExtraSent = FALSE;
    if (ptStop)
    {
        if (*ptStop == MAX_TIME)
        {
            DbgLog((LOG_TRACE,2,TEXT("StopAt: Cancel stop")));
            CancelStop();
        // If there's now a command to start in the future, we assume
        // they want to be stopped when the graph is first run
        if (m_FilterState == State_Stopped && m_tStartTime < MAX_TIME) {
            m_StreamState = STREAM_DISCARDING;
                DbgLog((LOG_TRACE,2,TEXT("graph will begin by DISCARDING")));
        }
            return NOERROR;
        }
        DbgLog((LOG_TRACE,2,TEXT("StopAt: %dms extra=%d"),
                (int)(*ptStop/10000), bSendExtra));
    // if the first command is to stop in the future, then we assume they
        // want to be started when the graph is first run
    if (m_FilterState == State_Stopped && m_tStartTime > *ptStop) {
        m_StreamState = STREAM_FLOWING;
            DbgLog((LOG_TRACE,2,TEXT("graph will begin by FLOWING")));
    }
        m_bStopSendExtra = bSendExtra;
        m_tStopTime = *ptStop;
        m_dwStopCookie = dwCookie;
        m_StreamStateOnStop = STREAM_DISCARDING;
    }
    else
    {
        DbgLog((LOG_TRACE,2,TEXT("StopAt: now")));
    // sending an extra frame when told to stop now would mess people up
        m_bStopSendExtra = FALSE;
        m_tStopTime = MAX_TIME;
        m_dwStopCookie = 0;
        m_StreamState = STREAM_DISCARDING;
        m_StreamStateOnStop = STREAM_FLOWING;    // no pending stop
    }
    // we might change our mind what to do with a sample we're blocking
    m_StreamEvent.Set();
    return NOERROR;
}

STDMETHODIMP CBaseStreamControl::StartAt
( const REFERENCE_TIME *ptStart, DWORD dwCookie )
{
    CAutoLock lck(&m_CritSec);
    if (ptStart)
    {
        if (*ptStart == MAX_TIME)
        {
            DbgLog((LOG_TRACE,2,TEXT("StartAt: Cancel start")));
            CancelStart();
        // If there's now a command to stop in the future, we assume
        // they want to be started when the graph is first run
        if (m_FilterState == State_Stopped && m_tStopTime < MAX_TIME) {
                DbgLog((LOG_TRACE,2,TEXT("graph will begin by FLOWING")));
            m_StreamState = STREAM_FLOWING;
        }
            return NOERROR;
        }
        DbgLog((LOG_TRACE,2,TEXT("StartAt: %dms"), (int)(*ptStart/10000)));
    // if the first command is to start in the future, then we assume they
        // want to be stopped when the graph is first run
    if (m_FilterState == State_Stopped && m_tStopTime >= *ptStart) {
            DbgLog((LOG_TRACE,2,TEXT("graph will begin by DISCARDING")));
        m_StreamState = STREAM_DISCARDING;
    }
        m_tStartTime = *ptStart;
        m_dwStartCookie = dwCookie;
        // if (m_tStopTime == m_tStartTime) CancelStop();
    }
    else
    {
        DbgLog((LOG_TRACE,2,TEXT("StartAt: now")));
        m_tStartTime = MAX_TIME;
        m_dwStartCookie = 0;
        m_StreamState = STREAM_FLOWING;
    }
    // we might change our mind what to do with a sample we're blocking
    m_StreamEvent.Set();
    return NOERROR;
}

//  Retrieve information about current settings
STDMETHODIMP CBaseStreamControl::GetInfo(__out AM_STREAM_INFO *pInfo)
{
    if (pInfo == NULL)
    return E_POINTER;

    pInfo->tStart = m_tStartTime;
    pInfo->tStop  = m_tStopTime;
    pInfo->dwStartCookie = m_dwStartCookie;
    pInfo->dwStopCookie  = m_dwStopCookie;
    pInfo->dwFlags = m_bStopSendExtra ? AM_STREAM_INFO_STOP_SEND_EXTRA : 0;
    pInfo->dwFlags |= m_tStartTime == MAX_TIME ? 0 : AM_STREAM_INFO_START_DEFINED;
    pInfo->dwFlags |= m_tStopTime == MAX_TIME ? 0 : AM_STREAM_INFO_STOP_DEFINED;
    switch (m_StreamState) {
    default:
        DbgBreak("Invalid stream state");
    case STREAM_FLOWING:
        break;
    case STREAM_DISCARDING:
        pInfo->dwFlags |= AM_STREAM_INFO_DISCARDING;
        break;
    }
    return S_OK;
}


void CBaseStreamControl::ExecuteStop()
{
    ASSERT(CritCheckIn(&m_CritSec));
    m_StreamState = m_StreamStateOnStop;
    if (m_dwStopCookie && m_pSink) {
    DbgLog((LOG_TRACE,2,TEXT("*sending EC_STREAM_CONTROL_STOPPED (%d)"),
                            m_dwStopCookie));
        m_pSink->Notify(EC_STREAM_CONTROL_STOPPED, (LONG_PTR)this, m_dwStopCookie);
    }
    CancelStop(); // This will do the tidy up
}

void CBaseStreamControl::ExecuteStart()
{
    ASSERT(CritCheckIn(&m_CritSec));
    m_StreamState = STREAM_FLOWING;
    if (m_dwStartCookie) {
    DbgLog((LOG_TRACE,2,TEXT("*sending EC_STREAM_CONTROL_STARTED (%d)"),
                            m_dwStartCookie));
        m_pSink->Notify(EC_STREAM_CONTROL_STARTED, (LONG_PTR)this, m_dwStartCookie);
    }
    CancelStart(); // This will do the tidy up
}

void CBaseStreamControl::CancelStop()
{
    ASSERT(CritCheckIn(&m_CritSec));
    m_tStopTime = MAX_TIME;
    m_dwStopCookie = 0;
    m_StreamStateOnStop = STREAM_FLOWING;
}

void CBaseStreamControl::CancelStart()
{
    ASSERT(CritCheckIn(&m_CritSec));
    m_tStartTime = MAX_TIME;
    m_dwStartCookie = 0;
}


// This guy will return one of the three StreamControlState's.  Here's what the caller
// should do for each one:
//
// STREAM_FLOWING:      Proceed as usual (render or pass the sample on)
// STREAM_DISCARDING:   Calculate the time 'til *pSampleStart and wait that long
//                      for the event handle (GetStreamEventHandle()).  If the
//                      wait expires, throw the sample away.  If the event
//            fires, call me back, I've changed my mind.
//            I use pSampleStart (not Stop) so that live sources don't
//             block for the duration of their samples, since the clock
//            will always read approximately pSampleStart when called


// All through this code, you'll notice the following rules:
// - When start and stop time are the same, it's as if start was first
// - An event is considered inside the sample when it's >= sample start time
//   but < sample stop time
// - if any part of the sample is supposed to be sent, we'll send the whole
//   thing since we don't break it into smaller pieces
// - If we skip over a start or stop without doing it, we still signal the event
//   and reset ourselves in case somebody's waiting for the event, and to make
//   sure we notice that the event is past and should be forgotten
// Here are the 19 cases that have to be handled (x=start o=stop <-->=sample):
//
// 1.    xo<-->        start then stop
// 2.    ox<-->        stop then start
// 3.     x<o->        start
// 4.     o<x->        stop then start
// 5.     x<-->o        start
// 6.     o<-->x        stop
// 7.      <x->o        start
// 8.      <o->x        no change
// 9.      <xo>        start
// 10.      <ox>        stop then start
// 11.      <-->xo    no change
// 12.      <-->ox    no change
// 13.     x<-->        start
// 14.    <x->        start
// 15.    <-->x        no change
// 16.   o<-->        stop
// 17.      <o->        no change
// 18.      <-->o        no change
// 19.    <-->        no change


enum CBaseStreamControl::StreamControlState CBaseStreamControl::CheckSampleTimes
( __in const REFERENCE_TIME * pSampleStart, __in const REFERENCE_TIME * pSampleStop )
{
    CAutoLock lck(&m_CritSec);

    ASSERT(!m_bIsFlushing);
    ASSERT(pSampleStart && pSampleStop);

    // Don't ask me how I came up with the code below to handle all 19 cases
    // - DannyMi

    if (m_tStopTime >= *pSampleStart)
    {
        if (m_tStartTime >= *pSampleStop)
        return m_StreamState;        // cases  8 11 12 15 17 18 19
    if (m_tStopTime < m_tStartTime)
        ExecuteStop();            // case 10
    ExecuteStart();                         // cases 3 5 7 9 13 14
    return m_StreamState;
    }

    if (m_tStartTime >= *pSampleStop)
    {
        ExecuteStop();                          // cases 6 16
        return m_StreamState;
    }

    if (m_tStartTime <= m_tStopTime)
    {
    ExecuteStart();
    ExecuteStop();
        return m_StreamState;        // case 1
    }
    else
    {
    ExecuteStop();
    ExecuteStart();
        return m_StreamState;        // cases 2 4
    }
}


enum CBaseStreamControl::StreamControlState CBaseStreamControl::CheckStreamState( IMediaSample * pSample )
{

    REFERENCE_TIME rtBufferStart, rtBufferStop;
    const BOOL bNoBufferTimes =
              pSample == NULL ||
              FAILED(pSample->GetTime(&rtBufferStart, &rtBufferStop));

    StreamControlState state;
    LONG lWait;

    do
        {
         // something has to break out of the blocking
            if (m_bIsFlushing || m_FilterState == State_Stopped)
        return STREAM_DISCARDING;

            if (bNoBufferTimes) {
                //  Can't do anything until we get a time stamp
                state = m_StreamState;
                break;
            } else {
                state = CheckSampleTimes( &rtBufferStart, &rtBufferStop );
                if (state == STREAM_FLOWING)
            break;

        // we aren't supposed to send this, but we've been
        // told to send one more than we were supposed to
        // (and the stop isn't still pending and we're streaming)
        if (m_bStopSendExtra && !m_bStopExtraSent &&
                    m_tStopTime == MAX_TIME &&
                    m_FilterState != State_Stopped) {
            m_bStopExtraSent = TRUE;
            DbgLog((LOG_TRACE,2,TEXT("%d sending an EXTRA frame"),
                                m_dwStopCookie));
            state = STREAM_FLOWING;
            break;
        }
            }

            // We're in discarding mode

            // If we've no clock, discard as fast as we can
            if (!m_pRefClock) {
        break;

        // If we're paused, we can't discard in a timely manner because
        // there's no such thing as stream times.  We must block until
        // we run or stop, or we'll end up throwing the whole stream away
        // as quickly as possible
        } else if (m_FilterState == State_Paused) {
        lWait = INFINITE;

        } else {
            // wait until it's time for the sample until we say "discard"
            // ("discard in a timely fashion")
            REFERENCE_TIME rtNow;
                EXECUTE_ASSERT(SUCCEEDED(m_pRefClock->GetTime(&rtNow)));
                rtNow -= m_tRunStart;   // Into relative ref-time
                lWait = LONG((rtBufferStart - rtNow)/10000); // 100ns -> ms
                if (lWait < 10) break; // Not worth waiting - discard early
        }

    } while(WaitForSingleObject(GetStreamEventHandle(), lWait) != WAIT_TIMEOUT);

    return state;
}


void CBaseStreamControl::NotifyFilterState( FILTER_STATE new_state, REFERENCE_TIME tStart )
{
    CAutoLock lck(&m_CritSec);

    // or we will get confused
    if (m_FilterState == new_state)
    return;

    switch (new_state)
    {
        case State_Stopped:

            DbgLog((LOG_TRACE,2,TEXT("Filter is STOPPED")));

        // execute any pending starts and stops in the right order,
        // to make sure all notifications get sent, and we end up
        // in the right state to begin next time (??? why not?)

        if (m_tStartTime != MAX_TIME && m_tStopTime == MAX_TIME) {
        ExecuteStart();
        } else if (m_tStopTime != MAX_TIME && m_tStartTime == MAX_TIME) {
        ExecuteStop();
        } else if (m_tStopTime != MAX_TIME && m_tStartTime != MAX_TIME) {
        if (m_tStartTime <= m_tStopTime) {
            ExecuteStart();
            ExecuteStop();
        } else {
            ExecuteStop();
            ExecuteStart();
        }
        }
        // always start off flowing when the graph starts streaming
        // unless told otherwise
        m_StreamState = STREAM_FLOWING;
            m_FilterState = new_state;
            break;

        case State_Running:

            DbgLog((LOG_TRACE,2,TEXT("Filter is RUNNING")));

            m_tRunStart = tStart;
            // fall-through

        default: // case State_Paused:
            m_FilterState = new_state;
    }
    // unblock!
    m_StreamEvent.Set();
}


void CBaseStreamControl::Flushing(BOOL bInProgress)
{
    CAutoLock lck(&m_CritSec);
    m_bIsFlushing = bInProgress;
    m_StreamEvent.Set();
}
