/*
 *  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 "incoming_video_stream.h"

#include "critical_section_wrapper.h"
#include "event_wrapper.h"
#include "trace.h"
#include "thread_wrapper.h"
#include "video_render_frames.h"
#include "tick_util.h"
#include "map_wrapper.h"
#include "vplib.h"

#include <cassert>

// Platform specifics
#if defined(_WIN32)
#include <windows.h>
#elif defined(WEBRTC_LINUX)
#include <ctime>
#include <sys/time.h>
#else
#include <sys/time.h>
#endif

namespace webrtc {
IncomingVideoStream::IncomingVideoStream(const WebRtc_Word32 moduleId,
                                         const WebRtc_UWord32 streamId) :
    _moduleId(moduleId),
    _streamId(streamId),
    _streamCritsect(*CriticalSectionWrapper::CreateCriticalSection()),
    _threadCritsect(*CriticalSectionWrapper::CreateCriticalSection()),
    _bufferCritsect(*CriticalSectionWrapper::CreateCriticalSection()),
    _ptrIncomingRenderThread(),
    _deliverBufferEvent(*EventWrapper::Create()),
    _running(false),
    _ptrExternalCallback(NULL),
    _ptrRenderCallback(NULL),
    _renderBuffers(*(new VideoRenderFrames)),
    _callbackVideoType(kVideoI420),
    _callbackWidth(0),
    _callbackHeight(0),
    _incomingRate(0),
    _lastRateCalculationTimeMs(0),
    _numFramesSinceLastCalculation(0),
    _lastRenderedFrame(),
    _tempFrame(),
    _startImage(),
    _timeoutImage(),
    _timeoutTime(),
    _mirrorFramesEnabled(false),
    _mirroring(),
    _transformedVideoFrame()
{
    WEBRTC_TRACE(kTraceMemory, kTraceVideoRenderer, _moduleId,
                 "%s created for stream %d", __FUNCTION__, streamId);
}

IncomingVideoStream::~IncomingVideoStream()
{
    WEBRTC_TRACE(kTraceMemory, kTraceVideoRenderer, _moduleId,
                 "%s deleted for stream %d", __FUNCTION__, _streamId);

    Stop();

    // _ptrIncomingRenderThread - Delete in stop
    delete &_renderBuffers;
    delete &_streamCritsect;
    delete &_bufferCritsect;
    delete &_threadCritsect;
    delete &_deliverBufferEvent;

}

WebRtc_Word32 IncomingVideoStream::ChangeModuleId(const WebRtc_Word32 id)
{
    CriticalSectionScoped cs(_streamCritsect);

    _moduleId = id;
    return 0;
}

VideoRenderCallback*
IncomingVideoStream::ModuleCallback()
{
    CriticalSectionScoped cs(_streamCritsect);
    return this;
}

WebRtc_Word32 IncomingVideoStream::RenderFrame(const WebRtc_UWord32 streamId,
                                               VideoFrame& videoFrame)
{

    CriticalSectionScoped csS(_streamCritsect);
    WEBRTC_TRACE(kTraceStream, kTraceVideoRenderer, _moduleId,
                 "%s for stream %d, render time: %u", __FUNCTION__, _streamId,
                 videoFrame.RenderTimeMs());

    if (!_running)
    {
        WEBRTC_TRACE(kTraceStream, kTraceVideoRenderer, _moduleId,
                     "%s: Not running", __FUNCTION__);
        return -1;
    }

    if (true == _mirrorFramesEnabled)
    {
        _transformedVideoFrame.VerifyAndAllocate(videoFrame.Length());
        if (_mirroring.mirrorXAxis)
        {
            MirrorI420UpDown(videoFrame.Buffer(),
                                     _transformedVideoFrame.Buffer(),
                                     videoFrame.Width(), videoFrame.Height());
            _transformedVideoFrame.SetLength(videoFrame.Length());
            _transformedVideoFrame.SetWidth(videoFrame.Width());
            _transformedVideoFrame.SetHeight(videoFrame.Height());
            videoFrame.SwapFrame(_transformedVideoFrame);
        }
        if (_mirroring.mirrorYAxis)
        {
            MirrorI420LeftRight(videoFrame.Buffer(),
                                        _transformedVideoFrame.Buffer(),
                                        videoFrame.Width(), videoFrame.Height());
            _transformedVideoFrame.SetLength(videoFrame.Length());
            _transformedVideoFrame.SetWidth(videoFrame.Width());
            _transformedVideoFrame.SetHeight(videoFrame.Height());
            videoFrame.SwapFrame(_transformedVideoFrame);
        }
    }

    // Rate statistics
    _numFramesSinceLastCalculation++;
    WebRtc_Word64 nowMs = TickTime::MillisecondTimestamp();
    if (nowMs >= _lastRateCalculationTimeMs + KFrameRatePeriodMs)
    {
        _incomingRate = (WebRtc_UWord32) (1000 * _numFramesSinceLastCalculation
                / (nowMs - _lastRateCalculationTimeMs));
        _numFramesSinceLastCalculation = 0;
        _lastRateCalculationTimeMs = nowMs;
    }

    // Insert frame
    CriticalSectionScoped csB(_bufferCritsect);
    if (_renderBuffers.AddFrame(&videoFrame) == 1)
        _deliverBufferEvent.Set();

    return 0;
}

WebRtc_Word32 IncomingVideoStream::SetStartImage(const VideoFrame& videoFrame)
{
    CriticalSectionScoped csS(_threadCritsect);
    return _startImage.CopyFrame(videoFrame);
}

WebRtc_Word32 IncomingVideoStream::SetTimeoutImage(const VideoFrame& videoFrame,
                                                   const WebRtc_UWord32 timeout)
{
    CriticalSectionScoped csS(_threadCritsect);
    _timeoutTime = timeout;
    return _timeoutImage.CopyFrame(videoFrame);
}

WebRtc_Word32 IncomingVideoStream::SetRenderCallback(VideoRenderCallback* renderCallback)
{
    CriticalSectionScoped cs(_streamCritsect);

    WEBRTC_TRACE(kTraceInfo, kTraceVideoRenderer, _moduleId,
                 "%s(%x) for stream %d", __FUNCTION__, renderCallback,
                 _streamId);
    _ptrRenderCallback = renderCallback;
    return 0;
}

WebRtc_Word32 IncomingVideoStream::EnableMirroring(const bool enable,
                                                   const bool mirrorXAxis,
                                                   const bool mirrorYAxis)
{
    CriticalSectionScoped cs(_streamCritsect);
    _mirrorFramesEnabled = enable;
    _mirroring.mirrorXAxis = mirrorXAxis;
    _mirroring.mirrorYAxis = mirrorYAxis;

    return 0;
}

WebRtc_Word32 IncomingVideoStream::SetExternalCallback(VideoRenderCallback* externalCallback)
{
    CriticalSectionScoped cs(_streamCritsect);

    WEBRTC_TRACE(kTraceInfo, kTraceVideoRenderer, _moduleId,
                 "%s(%x) for stream %d", __FUNCTION__, externalCallback,
                 _streamId);
    _ptrExternalCallback = externalCallback;
    _callbackVideoType = kVideoI420;
    _callbackWidth = 0;
    _callbackHeight = 0;
    return 0;
}

WebRtc_Word32 IncomingVideoStream::Start()
{
    CriticalSectionScoped csS(_streamCritsect);
    WEBRTC_TRACE(kTraceInfo, kTraceVideoRenderer, _moduleId,
                 "%s for stream %d", __FUNCTION__, _streamId);
    if (_running)
    {
        WEBRTC_TRACE(kTraceWarning, kTraceVideoRenderer, _moduleId,
                     "%s: Already running", __FUNCTION__);
        return 0;
    }

    CriticalSectionScoped csT(_threadCritsect);
    assert(_ptrIncomingRenderThread == NULL);

    _ptrIncomingRenderThread
            = ThreadWrapper::CreateThread(IncomingVideoStreamThreadFun, this,
                                          kRealtimePriority,
                                          "IncomingVideoStreamThread");
    if (!_ptrIncomingRenderThread)
    {
        WEBRTC_TRACE(kTraceError, kTraceVideoRenderer, _moduleId,
                     "%s: No thread", __FUNCTION__);
        return -1;
    }

    unsigned int tId = 0;
    if (_ptrIncomingRenderThread->Start(tId))
    {
        WEBRTC_TRACE(kTraceInfo, kTraceVideoRenderer, _moduleId,
                     "%s: thread started: %u", __FUNCTION__, tId);
    }
    else
    {
        WEBRTC_TRACE(kTraceError, kTraceVideoRenderer, _moduleId,
                     "%s: Could not start send thread", __FUNCTION__);
        return -1;
    }
    _deliverBufferEvent.StartTimer(false, KEventStartupTimeMS);

    _running = true;
    return 0;
}

WebRtc_Word32 IncomingVideoStream::Stop()
{
    CriticalSectionScoped csStream(_streamCritsect);
    WEBRTC_TRACE(kTraceInfo, kTraceVideoRenderer, _moduleId,
                 "%s for stream %d", __FUNCTION__, _streamId);

    if (!_running)
    {
        WEBRTC_TRACE(kTraceWarning, kTraceVideoRenderer, _moduleId,
                     "%s: Not running", __FUNCTION__);
        return 0;
    }

    _threadCritsect.Enter();
    if (_ptrIncomingRenderThread)
    {
        ThreadWrapper* ptrThread = _ptrIncomingRenderThread;
        _ptrIncomingRenderThread = NULL;
        ptrThread->SetNotAlive();
#ifndef _WIN32
        _deliverBufferEvent.StopTimer();
#endif
        _threadCritsect.Leave();
        if (ptrThread->Stop())
        {
            delete ptrThread;
        }
        else
        {
            assert(false);
            WEBRTC_TRACE(kTraceWarning, kTraceVideoRenderer, _moduleId,
                         "%s: Not able to stop thread, leaking", __FUNCTION__);
        }
    }
    else
    {
        _threadCritsect.Leave();
    }
    _running = false;
    return 0;
}

WebRtc_Word32 IncomingVideoStream::Reset()
{
    CriticalSectionScoped csStream(_streamCritsect);
    CriticalSectionScoped csBuffer(_bufferCritsect);

    _renderBuffers.ReleaseAllFrames();
    return 0;
}

WebRtc_UWord32 IncomingVideoStream::StreamId() const
{
    CriticalSectionScoped csStream(_streamCritsect);
    return _streamId;
}

WebRtc_UWord32 IncomingVideoStream::IncomingRate() const
{
    CriticalSectionScoped cs(_streamCritsect);
    return _incomingRate;
}

bool IncomingVideoStream::IncomingVideoStreamThreadFun(void* obj)
{
    return static_cast<IncomingVideoStream*> (obj)->IncomingVideoStreamProcess();
}

bool IncomingVideoStream::IncomingVideoStreamProcess()
{
    if (kEventError != _deliverBufferEvent.Wait(KEventMaxWaitTimeMs))
    {
        if (_ptrIncomingRenderThread == NULL)
        {
            // Terminating
            return false;
        }

        _threadCritsect.Enter();

        VideoFrame* ptrFrameToRender = NULL;

        // Get a new frame to render and the time for the frame after this one.
        _bufferCritsect.Enter();
        ptrFrameToRender = _renderBuffers.FrameToRender();
        WebRtc_UWord32 waitTime = _renderBuffers.TimeToNextFrameRelease();
        _bufferCritsect.Leave();

        // Set timer for next frame to render
        if (waitTime > KEventMaxWaitTimeMs)
        {
            waitTime = KEventMaxWaitTimeMs;
        }
        _deliverBufferEvent.StartTimer(false, waitTime);

        if (!ptrFrameToRender)
        {
            if (_ptrRenderCallback)
            {
                if (_lastRenderedFrame.RenderTimeMs() == 0
                        && _startImage.Size()) // And we have not rendered anything and have a start image
                {
                    _tempFrame.CopyFrame(_startImage);// Copy the startimage if the renderer modifies the render buffer.
                    _ptrRenderCallback->RenderFrame(_streamId, _tempFrame);
                }
                else if (_timeoutImage.Size()
                        && _lastRenderedFrame.RenderTimeMs() + _timeoutTime
                                < TickTime::MillisecondTimestamp()) // We have rendered something a long time ago and have a timeout image
                {
                    _tempFrame.CopyFrame(_timeoutImage); // Copy the timeoutImage if the renderer modifies the render buffer.
                    _ptrRenderCallback->RenderFrame(_streamId, _tempFrame);
                }
            }

            // No frame
            _threadCritsect.Leave();
            return true;
        }

        // Send frame for rendering
        if (_ptrExternalCallback)
        {
            WEBRTC_TRACE(kTraceStream,
                         kTraceVideoRenderer,
                         _moduleId,
                         "%s: executing external renderer callback to deliver frame",
                         __FUNCTION__, ptrFrameToRender->RenderTimeMs());
            _ptrExternalCallback->RenderFrame(_streamId, *ptrFrameToRender);
        }
        else
        {
            if (_ptrRenderCallback)
            {
                WEBRTC_TRACE(kTraceStream, kTraceVideoRenderer, _moduleId,
                             "%s: Render frame, time: ", __FUNCTION__,
                             ptrFrameToRender->RenderTimeMs());
                _ptrRenderCallback->RenderFrame(_streamId, *ptrFrameToRender);
            }
        }

        // Release critsect before calling the module user
        _threadCritsect.Leave();

        // We're done with this frame, delete it.
        if (ptrFrameToRender)
        {
            CriticalSectionScoped cs(_bufferCritsect);
            _lastRenderedFrame.SwapFrame(*ptrFrameToRender);
            _renderBuffers.ReturnFrame(ptrFrameToRender);
        }
    }
    return true;
}
WebRtc_Word32 IncomingVideoStream::GetLastRenderedFrame(VideoFrame& videoFrame) const
{
    CriticalSectionScoped cs(_bufferCritsect);
    return videoFrame.CopyFrame(_lastRenderedFrame);
}

} //namespace webrtc

