//------------------------------------------------------------------------------
// 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();
}
