/*
 *  Copyright (c) 2011 The WebRTC project authors. All Rights Reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree. An additional intellectual property rights grant can be found
 *  in the file PATENTS.  All contributing project authors may
 *  be found in the AUTHORS file in the root of the source tree.
 */

#include "vie_frame_provider_base.h"
#include "critical_section_wrapper.h"
#include "tick_util.h"
#include "trace.h"
#include "vie_defines.h"

namespace webrtc {

ViEFrameProviderBase::ViEFrameProviderBase(int Id, int engineId):
_id(Id),
_engineId(engineId),
_frameCallbackMap(),
_providerCritSect(*CriticalSectionWrapper::CreateCriticalSection()),
_ptrExtraFrame(NULL),
_frameDelay(0)
{
}

ViEFrameProviderBase::~ViEFrameProviderBase()
{
    if(_frameCallbackMap.Size()>0)
    {
        WEBRTC_TRACE(webrtc::kTraceWarning,
                     webrtc::kTraceVideo,
                     ViEId(_engineId,_id),
                     "FrameCallbacks still exist when Provider deleted %d",
                     _frameCallbackMap.Size());
    }
    for(MapItem* item=_frameCallbackMap.First();item!=NULL;item=_frameCallbackMap.Next(item))
    {
        static_cast<ViEFrameCallback*>(item->GetItem())->ProviderDestroyed(_id);
    }

    while(_frameCallbackMap.Erase(_frameCallbackMap.First()) == 0)
        ;

    delete &_providerCritSect;
    delete _ptrExtraFrame;
}

int ViEFrameProviderBase::Id()
{
    return _id;
}

void ViEFrameProviderBase::DeliverFrame(webrtc::VideoFrame& videoFrame,int numCSRCs,
                                const WebRtc_UWord32 CSRC[kRtpCsrcSize])
{
#ifdef _DEBUG
    const TickTime startProcessTime=TickTime::Now();
#endif
    CriticalSectionScoped cs(_providerCritSect);

    // Deliver the frame to all registered callbacks
    if (_frameCallbackMap.Size() > 0)
    {
        if(_frameCallbackMap.Size()==1)
        {
            ViEFrameCallback* frameObserver = static_cast<ViEFrameCallback*>(_frameCallbackMap.First()->GetItem());
            frameObserver->DeliverFrame(_id,videoFrame,numCSRCs,CSRC);
        }
        else
        {
            // Make a copy of the frame for all callbacks
            for (MapItem* mapItem = _frameCallbackMap.First();
                mapItem != NULL;
                mapItem = _frameCallbackMap.Next(mapItem))
            {
                if (_ptrExtraFrame == NULL)
                {
                    _ptrExtraFrame = new webrtc::VideoFrame();
                }
                if (mapItem != NULL)
                {
                    ViEFrameCallback* frameObserver = static_cast<ViEFrameCallback*>(mapItem->GetItem());
                    if (frameObserver != NULL)
                    {
                        // We must copy the frame each time since the previous receiver might swap it...
                        _ptrExtraFrame->CopyFrame(videoFrame);
                        frameObserver->DeliverFrame(_id, *_ptrExtraFrame,numCSRCs,CSRC);
                    }
                }
            }
        }
    }

#ifdef _DEBUG
    const int processTime=(int) (TickTime::Now()-startProcessTime).Milliseconds();
    if(processTime>25) // Warn If the delivery time is too long.
    {
        WEBRTC_TRACE(webrtc::kTraceWarning, webrtc::kTraceVideo, ViEId(_engineId,_id), "%s Too long time: %ums",__FUNCTION__,processTime);
    }
#endif
}

void ViEFrameProviderBase::SetFrameDelay(int frameDelay)
{

    CriticalSectionScoped cs(_providerCritSect);
    _frameDelay=frameDelay;

    for (MapItem* mapItem = _frameCallbackMap.First();
                mapItem != NULL;
                mapItem = _frameCallbackMap.Next(mapItem))
    {
        ViEFrameCallback* frameObserver = static_cast<ViEFrameCallback*>(mapItem->GetItem());
        assert(frameObserver);
        frameObserver->DelayChanged(_id,frameDelay);
    }

}

int ViEFrameProviderBase::FrameDelay()
{
    return _frameDelay;
}

int ViEFrameProviderBase::GetBestFormat(int& bestWidth,
                                        int& bestHeight,
                                        int& bestFrameRate)
{

    int largestWidth = 0;
    int largestHeight = 0;
    int highestFrameRate = 0;

    CriticalSectionScoped cs(_providerCritSect);

    // Check if this one already exists...
    for (MapItem* mapItem = _frameCallbackMap.First();
         mapItem != NULL;
         mapItem = _frameCallbackMap.Next(mapItem))
    {


        int preferedWidth=0;
        int preferedHeight=0;
        int preferedFrameRate=0;

        ViEFrameCallback* callbackObject = static_cast<ViEFrameCallback*>(mapItem->GetItem());
        assert(callbackObject);
        if(callbackObject->GetPreferedFrameSettings(preferedWidth,preferedHeight,preferedFrameRate)==0)
        {
            if (preferedWidth > largestWidth)
            {
                largestWidth = preferedWidth;
            }
            if (preferedHeight > largestHeight)
            {
                largestHeight = preferedHeight;
            }
            if (preferedFrameRate > highestFrameRate)
            {
                highestFrameRate = preferedFrameRate;
            }
        }
    }

    bestWidth = largestWidth;
    bestHeight = largestHeight;
    bestFrameRate = highestFrameRate;

    return 0;
}

int ViEFrameProviderBase::RegisterFrameCallback(int observerId,ViEFrameCallback* callbackObject)
{
    if (callbackObject == NULL)
    {
        WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId, _id),
            "%s: No argument", __FUNCTION__);
        return -1;
    }
    WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _id),
        "%s(0x%p)", __FUNCTION__, callbackObject);

    {
        CriticalSectionScoped cs(_providerCritSect);

        // Check if this one already exists...
        for (MapItem* mapItem = _frameCallbackMap.First();
             mapItem != NULL;
             mapItem = _frameCallbackMap.Next(mapItem))
        {
            const ViEFrameCallback* observer=static_cast<ViEFrameCallback*> (mapItem->GetItem());
            if (observer == callbackObject)
            {
                // This callback is already registered
                WEBRTC_TRACE(webrtc::kTraceWarning, webrtc::kTraceVideo, ViEId(_engineId, _id),
                    "%s 0x%p already registered", __FUNCTION__, callbackObject);

                assert("!frameObserver already registered");
                return -1;
            }
        }

        if (_frameCallbackMap.Insert(observerId,callbackObject) != 0)
        {
            WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId, _id),
                "%s: Could not add 0x%p to list", __FUNCTION__, callbackObject);
            return -1;
        }
    }
    // Report current capture delay
    callbackObject->DelayChanged(_id,_frameDelay);

    FrameCallbackChanged(); // Notify implementer of this class that the callback list have changed
    return 0;


}


// ----------------------------------------------------------------------------
// DeregisterFrameCallback
// ----------------------------------------------------------------------------

int ViEFrameProviderBase::DeregisterFrameCallback(const ViEFrameCallback* callbackObject)
{
    if (callbackObject == NULL)
    {
        WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId, _id),
            "%s: No argument", __FUNCTION__);
        return -1;
    }
    WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _id),
        "%s(0x%p)", __FUNCTION__, callbackObject);


    {
        CriticalSectionScoped cs(_providerCritSect);
        bool itemFound=false;


        // Try to find the callback in our list
        for (MapItem* mapItem = _frameCallbackMap.First();
             mapItem != NULL;
             mapItem = _frameCallbackMap.Next(mapItem))
        {
            const ViEFrameCallback* observer=static_cast<ViEFrameCallback*> (mapItem->GetItem());
            if (observer == callbackObject)
            {
                // We found it, remove it!
                _frameCallbackMap.Erase(mapItem);
                WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _id),
                    "%s 0x%p deregistered", __FUNCTION__, callbackObject);
                itemFound=true;
                break;
            }
        }
        if(!itemFound)
        {
            WEBRTC_TRACE(webrtc::kTraceWarning, webrtc::kTraceVideo, ViEId(_engineId, _id),
                    "%s 0x%p not found", __FUNCTION__, callbackObject);
            return -1;
        }
    }

    FrameCallbackChanged(); // Notify implementer of this class that the callback list have changed
    return 0;
}

// ----------------------------------------------------------------------------
// IsFrameCallbackRegistered
// ----------------------------------------------------------------------------

bool ViEFrameProviderBase::IsFrameCallbackRegistered(const ViEFrameCallback* callbackObject)
{
    if (callbackObject == NULL)
    {
        WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideo, ViEId(_engineId, _id),
            "%s: No argument", __FUNCTION__);
        return false;
    }
    WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _id),
        "%s(0x%p)", __FUNCTION__, callbackObject);

    for (MapItem* mapItem = _frameCallbackMap.First();
         mapItem != NULL;
         mapItem = _frameCallbackMap.Next(mapItem))
    {
        if (callbackObject == mapItem->GetItem())
        {
            // We found the callback
            WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _id),
                "%s 0x%p is registered", __FUNCTION__, callbackObject);
            return true;
        }
    }
    WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideo, ViEId(_engineId, _id),
        "%s 0x%p not registered", __FUNCTION__, callbackObject);
    return false;
}

// ----------------------------------------------------------------------------
// NumberOfRegistersFrameCallbacks
// ----------------------------------------------------------------------------

int ViEFrameProviderBase::NumberOfRegistersFrameCallbacks()
{
    CriticalSectionScoped cs(_providerCritSect);
    return _frameCallbackMap.Size();
}
} // namespac webrtc
