/*
#  $Id$
# -----------------------------------------------------------------------------
#  The part of 'VideoNEXT MediaClient SDK'
# -----------------------------------------------------------------------------
#  Author: Petrov Maxim, 03/04/2009
#  Edited by: Podlesniy Gleb, 02/12/2020
#  QA by:
#  Copyright: videoNEXT LLC
# -----------------------------------------------------------------------------
*/

#include "ace/OS_NS_time.h"
#include "MediaClientImpl.h"
#include "AudioVideoPlaybackController.h"
#include "BasicUsageEnvironment.hh"
#include "FileMediaStreamProducerImpl.h"

namespace videonext { namespace media {

#define MSG_ENDOFSTREAM "End of stream"
#define MSG_SEEKERROR   "Failed to start playing session: seek() failed"

static Mutex envMutex;

StreamError::StreamError(int code, const char* message)
	: std::runtime_error(message), code_(code)
{}

FileMediaStreamProducerImpl::FileMediaStreamProducerImpl(MediaStreamHandler* mediaStreamHandler, const std::string& url, unsigned cacheSize,
                                                         unsigned streamId, float speed)

	: MediaStreamProducerImpl(mediaStreamHandler, url, cacheSize, streamId, false, 0, speed)
	, stopFlag_(0), stepMode_(false), frameSinkBufferSize_(1000000), playbackController_(NULL)
	, sink_(NULL), formatCtx_(0), codecId_(AV_CODEC_ID_NONE), playDirection_(1), playbackTask_(0)
	, presentTask_(0), clipStartTime_(0), skipNextFrame_(false), lastFramePTS_(0), extradata_(NULL)
	, extradataSize_(0), timeBase_((AVRational){0, 0})
	, frameTv_((struct timeval){0, 0}), lastTv_((struct timeval){-1, -1}), firstFrame_(false)
	, firstChunk_(true), jumpToTimestamp_(0)
{
	MutexGuard g(envMutex);

	av_init_packet(&pkt_);
	pkt_.data = NULL;
	pkt_.size = 0;

	// Begin by setting up our usage environment:
	scheduler_ = BasicTaskScheduler::createNew();
	env_ = AdaptiveUsageEnvironment::createNew(*scheduler_, mediaStreamHandler_);

	playbackController_ = new AudioVideoPlaybackController(*env_, this, mediaStreamHandler, cacheSize, 0);

	serverVersion_ = 0xFFFFFFFF; // To disable stream is going from unknown server VideoFrameConsumer warning
}

/*virtual*/ FileMediaStreamProducerImpl::~FileMediaStreamProducerImpl()
{
    ACE_Guard<ACE_Thread_Mutex> mg(playMutex_);

    close();

    Medium::close(playbackController_);
    playbackController_ = NULL;

    delete scheduler_;
    env_->reclaim2();
}

void FileMediaStreamProducerImpl::close()
{
    closeSink();

    if (pkt_.data)
        av_packet_unref(&pkt_);

    if (formatCtx_)
    {
        avformat_close_input(&formatCtx_);
        formatCtx_ = NULL;
    }

    delete[] extradata_;
    extradata_ = NULL;
}

void FileMediaStreamProducerImpl::closeSink()
{
	if (sink_)
	{
		if (playbackController_)
		{
			playbackController_->removeSink(sink_);
		}
		Medium::close(sink_);
		sink_ = NULL;
	}
}

/*virtual*/ void FileMediaStreamProducerImpl::teardown()
{
	stop();
}

/*virtual*/ void FileMediaStreamProducerImpl::open()
{
    changeState(OPENING);

    std::vector<std::string> t;
    std::map<std::string, std::string> requestParams;
    try
    {
        if (base_dir_.empty())
        {
            tokenize(url_, "?", &t);
            if (t.size() == 1)
                fileName_ = url_.substr(strlen("file://"));
            else if (t.size() == 2)
            {
                base_dir_ = t[0].substr(strlen("file://"));
                std::string query = t[1];
                t.clear();
                tokenize(query, "&", &t);
                for (std::vector<std::string>::iterator it = t.begin(); it != t.end(); ++it)
                {
                    std::vector<std::string> kv;
                    tokenize(*it, "=", &kv);
                    if (kv.empty())
                        continue;
                    printf("Inserting '%s' => '%s'\n", kv[0].c_str(), kv[1].c_str());
                    requestParams.insert(std::make_pair(kv[0], kv.size() == 1 ? "" : kv[1]));
                }

                objid_  = requestParams["objid"];
                s_time_ = (time_t)atoll(requestParams["s_time"].c_str());
                e_time_ = (time_t)atoll(requestParams["e_time"].c_str());
                std::string direction(requestParams["direction"]);

                if (objid_.empty()
                        /*|| requestParams_["streamnum"].empty()*/)
                {
                    throw StreamError(EINTERNALERROR, "Missing 'objid' argument in the URL");
                }

                if(!s_time_) {
                    throw StreamError(EINTERNALERROR, "Missing 's_time' argument in the URL");
                }
#if 0
                if(!e_time_) {
                    throw StreamError(EINTERNALERROR, "Missing 'e_time' argument in the URL");
                }
#endif
                // TODO: add s_time & e_time check depending on direction

                if(!direction.empty() && direction == "0") {
                    playDirection_ = -1;
                    playbackController_->setDirection(-1);

                    jumpToTimestamp_ = s_time_;
                } else if (s_time_ % 30) {
                    jumpToTimestamp_ = s_time_;
                    s_time_ -= s_time_ % 30;
                }
            }
        }

        if (!base_dir_.empty())
        {
            /* Fill ptm struct */
            struct tm ptm;
            char tmp_buff[80];
            if (playDirection_ <= 0) {
                time_t chunk_ts = s_time_ % 30 ? s_time_ - s_time_ % 30 : s_time_ - 30;
                ACE_OS::gmtime_r(&chunk_ts, &ptm);
            } else
                ACE_OS::gmtime_r(&s_time_, &ptm);

            // YYMMDD dir
            strftime(tmp_buff, sizeof(tmp_buff), "%y%m%d", &ptm);
            std::string dayDir(base_dir_ + "/" + objid_ + "/" + tmp_buff);

            char chunkSuffix_str[16];
            if (snprintf(chunkSuffix_str, sizeof chunkSuffix_str, "-%02d", 1/*hardcoded*/) == -1)
            {
                throw StreamError(EINTERNALERROR, "Failed to add chunk suffix");
            }

            // HH dir
            strftime(tmp_buff, sizeof(tmp_buff), "%H", &ptm);
            std::string hourDir(dayDir + "/" + tmp_buff + chunkSuffix_str);

            /* Creating full path to chunk */
            strftime(tmp_buff, sizeof(tmp_buff), "%y%m%d%H%M%S.mov", &ptm);
            fileName_ = hourDir + "/" + tmp_buff;
        }

		if (avformat_open_input(&formatCtx_, fileName_.c_str(), NULL, NULL) != 0)
		{
            const std::string err_str("Requested video fragment is not available in videoarchive (time gap): " + fileName_);
            if (firstChunk_)
                throw StreamError(EINTERNALERROR, err_str.c_str());
            else
                throw StreamError(EENDOFARCHIVE, err_str.c_str());
		}
        firstChunk_ = false;

		StreamInfo* streamInfo = NULL;

		// Create and start Frame Consumers for each stream.
		for (unsigned i = 0; i < formatCtx_->nb_streams; i++)
		{
			AVStream* stream = formatCtx_->streams[i];

			if (stream->codecpar->codec_id == AV_CODEC_ID_NONE)
			{
				playbackController_->mediaStreamHandler()->log("Stream codec not found\n");
				continue;
			}

			// Skiping data stream, because we will mux data in the video frames
			if (stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
			{
				if (!stream->codecpar)
				{
					throw StreamError(EINTERNALERROR, "Stream codec params not found");
				}

				streamInfo = new StreamInfo;

				codecId_ = stream->codecpar->codec_id;

				if (codecId_ == AV_CODEC_ID_MPEG4)
					streamInfo->RTPCodecStr = "MP4V-ES";
				else if (codecId_ == AV_CODEC_ID_MJPEG)
					streamInfo->RTPCodecStr = "JPEG";
				else if (codecId_ == AV_CODEC_ID_H264)
					streamInfo->RTPCodecStr = "H264";
				else if (codecId_ == AV_CODEC_ID_HEVC)
					streamInfo->RTPCodecStr = "HEVC";
				else if (codecId_ == AV_CODEC_ID_MPEG2VIDEO)
					streamInfo->RTPCodecStr = "MPV";
				else
				{
					delete streamInfo;
					throw StreamError(EINTERNALERROR, "Unknown codec");
				}

				timeBase_ = stream->time_base;

				int64_t duration = getClipDuration(stream);

				parseMetadata();

				streamInfo->streamId = 0;
				streamInfo->configData = 0;
				streamInfo->configDataSize = 0;
				// Duration in milliseconds
				streamInfo->duration = static_cast<unsigned>(duration / 1000);
				streamInfo->type = VIDEO;
				streamInfo->metadata = metadata_;

				sink_ = VideoFrameConsumer::createNew(*env_, playbackController_, frameSinkBufferSize_, decoderType_, preferedPixelFormat_, streamInfo, false);
				playbackController_->addSink(sink_, AudioVideoPlaybackController::VIDEO);
				if (!sink_->init())
				{
					closeSink();
					streamInfo = NULL;
					throw StreamError(EINTERNALERROR, "Could not initialize video sink");
				}

				delete[] extradata_;
				extradata_ = NULL;
				extradataSize_ = 0;

				uint8_t* extradata = stream->codecpar->extradata;
				int extradata_size = stream->codecpar->extradata_size;
				if (extradata && extradata_size)
				{
					if (codecId_ == AV_CODEC_ID_H264)
					{
						int dataSize = 0;
						uint8_t* data = getH264ExtraData(extradata, extradata_size, dataSize);
						if (data)
						{
							sink_->addExtraData(data, dataSize);

							struct timeval tv = {0, 0};
							uint8_t* pps = findH264StartCode(data, dataSize);
							if (pps)
							{
								sink_->addData(data, pps - data, tv); // Passing SPS
								sink_->addData(pps + 4, dataSize - (pps - data + 4), tv); // Passing PPS
							}
							else
							{
								sink_->addData(data, dataSize, tv); // PPS not found, fallback to send all extra data
							}
							delete[] data;
						}
					}
                    if (codecId_ == AV_CODEC_ID_HEVC)
                    {
                        fprintf(stderr, "Codecpar extradata size: %d\n", extradata_size);
                        for(int i=0; i<(extradata_size > 180 ? 180 : extradata_size); i++)
                            fprintf(stderr, "0x%02x ", extradata[i]);
                        fprintf(stderr, "\n");

                        int dataSize = 0;
                        uint8_t* data = getH265ExtraData(extradata, extradata_size, dataSize);
                        if (data)
                        {
                            fprintf(stderr, "Parsed extradata size: %d\n", dataSize);
                            for(int i=0; i<(dataSize > 180 ? 180 : dataSize); i++)
                                fprintf(stderr, "0x%02x ", data[i]);
                            fprintf(stderr, "\n");

                            sink_->addExtraData(data, dataSize);

                            bool found(false), added(false);
                            struct timeval tv = {0, 0};
                            uint8_t* ptr = data;
                            int remain = dataSize, vpsSize;
                            while (uint8_t* some_ps = findH264StartCode(ptr, remain)) {
                                if(!found) {
                                    // Found SPS startcode
                                    remain -= some_ps - data + 4;
                                    //printf("remain=%d\n", remain);
                                    vpsSize = some_ps - data;
                                    ptr = some_ps + 4;
                                    found = true;
                                } else {
                                    // Found PPS startcode
                                    // Let's process all VPS / SPS / PPS
                                    remain -= some_ps - ptr + 4;
                                    //printf("remain=%d\n", remain);

                                    sink_->addData(data, vpsSize, tv);
                                    sink_->addData(ptr, some_ps - ptr, tv);
                                    sink_->addData(some_ps + 4, remain, tv);
                                    added = true;
                                    break;
                                }
                            }

                            if (!added) {
                                // fallback to send all extra data
                                sink_->addData(data, dataSize, tv);
                            }
                            delete[] data;
                        }
                    }
					else if (codecId_ == AV_CODEC_ID_MPEG4)
					{
						uint8_t* data = findMpeg4ExtraData(extradata, extradata_size, extradataSize_);
						if (data)
						{
							extradata_ = new uint8_t[extradataSize_];
							memcpy(extradata_, extradata, extradataSize_);
						}
					}
				}

				char msg[128];
				snprintf(msg, sizeof(msg), "Video stream found in file, extra data size %d\n", extradata_size);
				playbackController_->mediaStreamHandler()->log(msg);

				break;
			}
		}

		if (!playbackController_->hasSinks())
		{
			throw StreamError(EINTERNALERROR, "No sinks created");
		}

		if (streamInfo)
		{
			char msg[1024];
			snprintf(msg, sizeof(msg), "Opened file %s, codec %s, duration %d ms\n", fileName_.c_str(), streamInfo->RTPCodecStr.c_str(), streamInfo->duration);
			playbackController_->mediaStreamHandler()->log(msg);
		}

		clipStartTime_ = getClipStartTime();
		if (clipStartTime_)
		{
			struct tm tm;
			ACE_OS::gmtime_r(&clipStartTime_, &tm);
			char timeFormated[64];
			strftime(timeFormated, sizeof(timeFormated), "%Y/%m/%d %H:%M:%S", &tm);
			char msg[128];
			snprintf(msg, sizeof(msg), "Clip start time %s\n", timeFormated);
			playbackController_->mediaStreamHandler()->log(msg);
		}
		else
		{
			playbackController_->mediaStreamHandler()->log("Clip start time is not found\n");
		}

		lastTv_ = (struct timeval){-1, -1};

        printf("jumpToTimestamp_ = %ld\n", jumpToTimestamp_);
        if(jumpToTimestamp_ > 0) {
            jumpToTimestamp(jumpToTimestamp_);
            jumpToTimestamp_ = 0;
        }

        setSpeed(speed_);

        changeState(OPENED);

		env_->taskScheduler().doEventLoop(&stopFlag_); // does not return
	}
	catch (const StreamError& e)
	{
		env_->setResultErrMsg((std::string(e.what()) + "\n").c_str());
		env_->setResultErr(e.code());
        changeState(STOPPED, e.code());
	}
}

/*virtual*/ void FileMediaStreamProducerImpl::play()
{
	ACE_Guard<ACE_Thread_Mutex> mg(playMutex_);

	if (stopFlag_ == 1)
		return;

	if (playbackController_->hasSinks() && playbackController_->isPaused())
	{
		resume();
		return;
	}

	if (playbackController_->getState() != OPENED)
		return;

	// Schedule playing file
	playbackTask_ = env_->taskScheduler().scheduleDelayedTask(0, (TaskFunc*)playbackTask, this);
}

/*static*/ void FileMediaStreamProducerImpl::playbackTask(void* clientData)
{
	FileMediaStreamProducerImpl* p = (FileMediaStreamProducerImpl*)clientData;
	p->playFrame();
}

void FileMediaStreamProducerImpl::playFrame()
{
	ACE_Guard<ACE_Thread_Mutex> mg(playMutex_);

	if (stopFlag_ == 1)
		return;

	if (playbackController_->isPaused())
	{
		playbackTask_ = env_->taskScheduler().scheduleDelayedTask(50000, (TaskFunc*)playbackTask, this);
		return;
	}

	AVPacket pkt2;
	pkt2.data = NULL;

	bool present = false;
	try
	{
		do
		{
			if (pkt_.data)
				av_packet_unref(&pkt_);

			if (av_read_frame(formatCtx_, &pkt_) != 0)
			{
				throw StreamError(EENDOFSTREAM, MSG_ENDOFSTREAM);
			}
		} while (pkt_.stream_index != 0 && (!skipNextFrame_ || (pkt_.flags & AV_PKT_FLAG_KEY) != 0));

		struct timeval tv;
		getFrameTimestamp(pkt_, tv);

		lastFramePTS_ = pkt_.pts;

		DelayInterval duration = lastTv_.tv_sec >= 0 && lastTv_.tv_usec >= 0 ?
			Timeval(tv) - Timeval(lastTv_) :
			DelayInterval(0, 0);

		frameTv_ = tv;
		lastTv_ = tv;

		present = true;
        presentTask_ = env_->taskScheduler().scheduleDelayedTask((duration.seconds() * 1000000 + duration.useconds()) / playbackController_->getSpeed(),
																 (TaskFunc*)presentTask,
																 this);

		// Schedule next playback. For that we need to get next frame and calculate duration
		if (playDirection_ < 0)
		{
			av_init_packet(&pkt2);
			pkt2.data = NULL;
			pkt2.size = 0;

			// 1) find previous key frame;
			// 2) schedule playback task
			int64_t pts = pkt_.pts;
            while ((pts -= 100) >= 0)
			{
				if (av_seek_frame(formatCtx_, 0, pts, AVSEEK_FLAG_ANY | AVSEEK_FLAG_BACKWARD) != 0)
				{
					throw StreamError(EENDOFSTREAM, MSG_ENDOFSTREAM);
				}

				if (av_read_frame(formatCtx_, &pkt2) != 0)
				{
					throw StreamError(EENDOFSTREAM, MSG_ENDOFSTREAM);
				}

				if (pkt2.pts >= pkt_.pts)
				{
					throw StreamError(EENDOFSTREAM, MSG_ENDOFSTREAM);
				}

				if ((pkt2.flags & AV_PKT_FLAG_KEY) != 0)
				{
					struct timeval tv2;
					getFrameTimestamp(pkt2, tv2);

					DelayInterval duration = Timeval(tv) - Timeval(tv2);

					// Seek to this frame again
					if (av_seek_frame(formatCtx_, 0, pts, AVSEEK_FLAG_ANY | AVSEEK_FLAG_BACKWARD) != 0)
					{
						throw StreamError(EENDOFSTREAM, MSG_ENDOFSTREAM);
					}

					lastTv_ = (struct timeval){-1, -1};
					playbackTask_ = env_->taskScheduler().scheduleDelayedTask((duration.seconds() * 1000000 + duration.useconds()) / playbackController_->getSpeed(),
																			  (TaskFunc*)playbackTask,
																			  this);
					break;
				}

				// Wipe the packet that was allocated by av_read_frame
				av_packet_unref(&pkt2);
			}

			if (pkt2.data)
				// Wipe the packet that was allocated by av_read_frame
				av_packet_unref(&pkt2);

			if (pts < 0)
			{
				throw StreamError(EENDOFSTREAM, MSG_ENDOFSTREAM);
			}
		}
	}
	catch (const StreamError& e)
	{
		// Let's wipe the packets that were allocated by av_read_frame
		if (pkt_.data)
		    av_packet_unref(&pkt_);
		if (pkt2.data)
		    av_packet_unref(&pkt2);

		if (e.code() == EENDOFSTREAM)
		{
            if (playDirection_ < 0 && present)
			{
				firstFrame_ = true; // Set end of stream status in presentFrame
			}
			else
			{
                close();
                mg.release();

                if (!e_time_ || s_time_ + 30 < e_time_) {
                    // let's play next chunk
                    s_time_ += 30;
                    open();
                } else {
                    *env_ << (std::string(e.what()) + "\n").c_str();
                    env_->setResultErr(s_time_ + 30 >= e_time_ ? e.code() : EENDOFARCHIVE);
                    changeState(IDLE, e.code());
                }
			}
		}
		else
		{
			env_->setResultErrMsg((std::string(e.what()) + "\n").c_str());
			env_->setResultErr(e.code());
			close();
			mg.release();
			changeState(STOPPED, e.code());
		}
	}
}

/*static*/ void FileMediaStreamProducerImpl::presentTask(void* clientData)
{
	FileMediaStreamProducerImpl* p = (FileMediaStreamProducerImpl*)clientData;
	p->presentFrame();
}

void FileMediaStreamProducerImpl::presentFrame()
{
	ACE_Guard<ACE_Thread_Mutex> mg(playMutex_);

	if (stopFlag_ == 1)
		return;

	if (!skipNextFrame_)
	{
		if (pkt_.data && pkt_.size)
		{
			uint8_t* data = pkt_.data;
			int dataSize = pkt_.size;

			if (codecId_ == AV_CODEC_ID_H264)
			{
				data = findH264Frame(pkt_.data, pkt_.size);
				dataSize = data ? pkt_.size - (data - pkt_.data) : 0;
				if (!data)
				{
					playbackController_->mediaStreamHandler()->log("H264 frame not found, skiping frame\n");
				}
			}
			else if (codecId_ == AV_CODEC_ID_HEVC)
			{
				data = findH265Frame(pkt_.data, pkt_.size);
				dataSize = data ? pkt_.size - (data - pkt_.data) : 0;
				if (!data)
				{
					playbackController_->mediaStreamHandler()->log("H265 frame not found, skiping frame\n");
				}
			}
			else if (extradata_ && extradataSize_)
			{
				int ret = av_grow_packet(&pkt_, extradataSize_);
				if (ret != 0)
				    fprintf(stderr, "Failed to grow packet (code=%d)\n", ret);
				else
				{
				    dataSize = pkt_.size + extradataSize_;
				    memmove(data + extradataSize_, data, extradataSize_);
				    memcpy(data, extradata_, extradataSize_);
				    //memcpy(data + extradataSize_, pkt_.data, pkt_.size);
				    delete[] extradata_;
				    extradata_ = NULL;
				    extradataSize_ = 0;
				}
			}

			if (data && dataSize)
			{
				struct timeval tv;
				tv.tv_sec = clipStartTime_ + frameTv_.tv_sec;
				tv.tv_usec = frameTv_.tv_usec;

				playbackController_->videoSink()->addData(data, dataSize, tv);
			}

			if (playDirection_ >= 0)
			{
				playbackTask_ = env_->taskScheduler().scheduleDelayedTask(0, (TaskFunc*)playbackTask, this);
			}
		}
	}
	else
	{
		skipNextFrame_ = false;
	}

	if (pkt_.data)
	    // Wipe the packet that was allocated by av_read_frame
	    av_packet_unref(&pkt_);

	if (firstFrame_)
	{
        firstFrame_ = false;
        close();
        mg.release();

        s_time_ -= s_time_ % 30 ? s_time_ % 30 : 30;
        if (!e_time_ || s_time_ > e_time_) {
            // let's play next chunk
            jumpToTimestamp_ = s_time_;
            open();
        } else {
            *env_ << "End of stream\n";
            env_->setResultErr(EENDOFSTREAM);
            changeState(IDLE, EENDOFSTREAM);
        }
	}
}

void FileMediaStreamProducerImpl::stop()
{
	ACE_Guard<ACE_Thread_Mutex> mg(playMutex_);

	if (stopFlag_ == 1)
		return;

	env_->taskScheduler().unscheduleDelayedTask(playbackTask_);
	env_->taskScheduler().unscheduleDelayedTask(presentTask_);

	if (playbackController_ && playbackController_->hasSinks())
	{
		playbackController_->setPlayFramesFromServer(false);
		mg.release();
		playbackController_->setPaused(true);
		mg.acquire();
	}

	stopFlag_ = 1;
}

void FileMediaStreamProducerImpl::pause()
{
	ACE_Guard<ACE_Thread_Mutex> mg(playMutex_);

	if (!playbackController_->hasSinks())
		return;

	playbackController_->setPlayFramesFromServer(false);

	if (!stepMode_)
	{
		playbackController_->setServerPaused(true);
	}

	mg.release();
	playbackController_->setPaused(true);
}

/*virtual*/ void FileMediaStreamProducerImpl::setPlayDirection(int value)
{
	ACE_Guard<ACE_Thread_Mutex> mg(playMutex_);

	if (value != playDirection_)
	{
		skipNextFrame_ = true;

		// Seeking to last played key frame
		av_seek_frame(formatCtx_, 0, value >= 0 ? lastFramePTS_ + 1 : lastFramePTS_, AVSEEK_FLAG_ANY);
	}

	playDirection_ = value;
}

/*virtual*/ void FileMediaStreamProducerImpl::changeState(STREAM_STATE state, int errorCode)
{
	playbackController_->setState(state, errorCode);
}

void FileMediaStreamProducerImpl::setStepMode(bool value)
{
	ACE_Guard<ACE_Thread_Mutex> mg(playMutex_);

	if (!playbackController_->hasSinks())
		return;

	// if step mode was disabled, sending RTSP "PAUSE"
	if (stepMode_ && value == 0)
		pause();

	stepMode_ = value;
}

/*virtual*/ void FileMediaStreamProducerImpl::setSpeed(float speed)
{
	ACE_Guard<ACE_Thread_Mutex> mg(playMutex_);

	if (!playbackController_->hasSinks())
		return;

	playbackController_->setSpeed(speed);
}

void FileMediaStreamProducerImpl::resume()
{
	ACE_Guard<ACE_Thread_Mutex> mg(playMutex_);

	if (!playbackController_->hasSinks())
		return;

	playbackController_->setPaused(false);
	playbackController_->setPlayFramesFromServer(true);
	playbackController_->setServerPaused(false);
}

void FileMediaStreamProducerImpl::jumpToTimestamp(int timestamp, bool mark)
{
	ACE_Guard<ACE_Thread_Mutex> mg(playMutex_);

	if (!playbackController_->hasSinks())
		return;

	AVPacket pkt;
	av_init_packet(&pkt);
	pkt.data = NULL;
	pkt.size = 0;

	try
	{
		int64_t time = (timestamp - clipStartTime_) * 1000000;
		int64_t pts = av_rescale_q(time, (AVRational){1, 1000000}, timeBase_);

        //printf("time=%" PRId64 ", pts=%" PRId64 ", ts=%ld, startTime_=%ld\n", time, pts, timestamp, clipStartTime_);

		int pktFlags = 0;
        while ((pts -= 100) >= 0 && (pktFlags & AV_PKT_FLAG_KEY) == 0)
		{
			if(pkt.data)
				// Wipe the packet that was allocated by av_read_frame
			av_packet_unref(&pkt);

            if (av_seek_frame(formatCtx_, 0, pts, AVSEEK_FLAG_ANY | AVSEEK_FLAG_BACKWARD) != 0)
			{
				throw StreamError(0, MSG_SEEKERROR);
			}

			if (av_read_frame(formatCtx_, &pkt) != 0)
			{
				throw StreamError(0, MSG_SEEKERROR);
			}

			if (pkt.stream_index != 0)
				continue;

			if (pkt.pts > pts)
			{
				throw StreamError(0, MSG_SEEKERROR);
			}

			pktFlags = pkt.flags;
		}

		if (av_seek_frame(formatCtx_, 0, pkt.pts, AVSEEK_FLAG_ANY | AVSEEK_FLAG_BACKWARD) != 0)
		{
			throw StreamError(0, MSG_SEEKERROR);
		}
	}
	catch (const StreamError& e)
	{
		*env_ << (std::string(e.what()) + "\n").c_str();
	}

    if(pkt.data)
        // Wipe the packet that was allocated by av_read_frame
        av_packet_unref(&pkt);
}

bool FileMediaStreamProducerImpl::startRecording(const char* file_name, const std::map<std::string, std::string>& metadata)
{
	return playbackController_->startRecording(file_name, metadata);
}

bool FileMediaStreamProducerImpl::endRecording(bool stop)
{
	return playbackController_->endRecording(stop);
}

void FileMediaStreamProducerImpl::getFrameTimestamp(const AVPacket& pkt, struct timeval& t)
{
	int64_t time = timeBase_.num != 0 || timeBase_.den != 0 ?
		av_rescale_q(pkt.pts, timeBase_, (AVRational){1, 1000000}) :
		pkt.pts;
	t.tv_sec = time / 1000000;
	t.tv_usec = time % 1000000;
}

uint64_t FileMediaStreamProducerImpl::getClipDuration(AVStream* stream)
{
	if (!stream)
		return 0;

	AVPacket pkt;
	av_init_packet(&pkt);
	pkt.data = NULL;
	pkt.size = 0;

	uint64_t duration = av_rescale_q(stream->duration, stream->time_base, (AVRational){1, 1000000});

	if (av_seek_frame(formatCtx_, stream->index, 0, AVSEEK_FLAG_ANY) != 0
            || av_read_frame(formatCtx_, &pkt) != 0)
		return duration;

	av_seek_frame(formatCtx_, stream->index, 0, AVSEEK_FLAG_ANY);

	int64_t time = av_rescale_q(pkt.pts, stream->time_base, (AVRational){1, 1000000});

	// Wipe the packet that was allocated by av_read_frame
	av_packet_unref(&pkt);
	return duration - time;
}

void FileMediaStreamProducerImpl::parseMetadata(const std::string& metaText, const std::map<std::string, std::string>& map)
{
	std::map<std::string, std::string>::const_iterator it;
	for (it = map.begin(); it != map.end(); ++it)
	{
		size_t pos1 = metaText.find(it->first);
		if (pos1 == std::string::npos)
		{
			continue;
		}
		pos1 += it->first.size();
		size_t pos2 = metaText.find("\n", pos1);
		size_t len = pos2 != std::string::npos ? pos2 - pos1 : metaText.size() - pos1;
		metadata_[it->second] = metaText.substr(pos1, len);
	}
}

void FileMediaStreamProducerImpl::parseMetaObj(const std::string& metaText, const std::string& prefix, const std::string& idKey, const std::string& nameKey)
{
	size_t pos1 = metaText.find(prefix + "[");
	if (pos1 == std::string::npos)
	{
		return;
	}
	pos1 += prefix.size() + 1;
	size_t pos2 = metaText.find("] ", pos1);
	if (pos2 == std::string::npos)
	{
		return;
	}
	metadata_[idKey] = metaText.substr(pos1, pos2 - pos1);
	pos2 += 2;
	size_t pos3 = metaText.find("\n", pos2);
	size_t len = pos3 != std::string::npos ? pos3 - pos2 : metaText.size() - pos2;
	metadata_[nameKey] = metaText.substr(pos2, len);
}

void FileMediaStreamProducerImpl::parseMetadata()
{
	std::map<std::string, std::string> map1;
	map1["Start time (UTC): "] = "StartTime";
	map1["Start time (local): "] = "StartTime";
	map1["End time (local): "] = "EndTime";

	std::map<std::string, std::string> map2;
	map2["Time Zone: "] = "TimeZone";
	map2["PTZ: "] = "PTZ";
	map2["Motion Detection: "] = "MD";
	map2["Analytics: "] = "Analytics";
	map2["GEO Latitude: "] = "GeoLat";
	map2["GEO Longitude: "] = "GeoLong";
	map2["GEO Altitude: "] = "GeoAlt";
	map2["Source: "] = "Source";
	map2["System message: "] = "SystemMessage";
	map2["Operator message: "] = "OperatorMessage";

	std::map<std::string, std::string> map3;
	map3["Name: "] = "SystemName";
	map3["Location: "] = "SystemLocation";
	map3["Contact: "] = "ContactInfo";

	std::map<std::string, std::string> map4;
	map4["Location: "] = "Location";
	map4["Additional: "] = "Note";

	metadata_.clear();

	AVDictionaryEntry* titleEntry = av_dict_get(formatCtx_->metadata, "title", NULL, 0);
	if (titleEntry)
	{
		parseMetaObj(titleEntry->value, "Camera: ", "Id", "Name");
		parseMetaObj(titleEntry->value, "Sound: ", "AudioId", "AudioName");
		parseMetadata(titleEntry->value, map1);
	}

	AVDictionaryEntry* commentEntry = av_dict_get(formatCtx_->metadata, "comment", NULL, 0);
	if (commentEntry)
	{
		parseMetadata(commentEntry->value, map2);
	}

	AVDictionaryEntry* copyrightEntry = av_dict_get(formatCtx_->metadata, "copyright", NULL, 0);
	if (copyrightEntry)
	{
		parseMetadata(copyrightEntry->value, map3);
	}

	AVDictionaryEntry* authorEntry = av_dict_get(formatCtx_->metadata, "author", NULL, 0);
	if (authorEntry)
	{
		parseMetadata(authorEntry->value, map4);
	}
}

time_t FileMediaStreamProducerImpl::getClipStartTime()
{
	struct tm tm = {};
	ACE_OS::strptime(metadata_["StartTime"].c_str(), "%Y/%m/%d %H:%M:%S", &tm);
	time_t time = mktime(&tm);
	if (time > 0)
	{
		time_t current;
		ACE_OS::time(&current);
		struct tm ltm;
		ACE_OS::localtime_r(&current, &ltm);
		struct tm gtm;
		ACE_OS::gmtime_r(&current, &gtm);
		time_t lt = mktime(&ltm);
		time_t gt = mktime(&gtm);
		return time + (lt - gt);
	}
	return 0;
}

/*static*/ uint8_t* FileMediaStreamProducerImpl::getH264ExtraData(uint8_t* extraData, int extraDataSize, int& outSize)
{
	outSize = 0;
	if (!extraData || !extraDataSize || extraDataSize < 8)
	{
		return NULL;
	}

	const int maxSize = 1024;
	uint8_t* result = new uint8_t[maxSize];
	int index = 0;

	uint8_t* ptr = extraData + 6;

	uint16_t spsSize = static_cast<uint16_t>(ptr[0]) << 8 | ptr[1];
	ptr += 2;
	for (int i = 0; i < spsSize && ptr < extraData + extraDataSize && index < maxSize; i++)
	{
		result[index++] = *(ptr++);
	}

	if (ptr + 1 >= extraData + extraDataSize || index >= maxSize)
	{
		outSize = index;
		return result;
	}

	uint8_t ppsCount = *(ptr++);
	for (int i = 0; i < ppsCount && ptr < extraData + extraDataSize && index < maxSize; i++)
	{
		if (index + 4 >= maxSize || ptr + 2 >= extraData + extraDataSize)
		{
			break;
		}
		result[index++] = 0;
		result[index++] = 0;
		result[index++] = 0;
		result[index++] = 1;
		uint16_t ppsSize = static_cast<uint16_t>(ptr[0]) << 8 | ptr[1];
		ptr += 2;
		for (int j = 0; j < ppsSize && ptr < extraData + extraDataSize && index < maxSize; j++)
		{
			result[index++] = *(ptr++);
		}
	}

	outSize = index;
	return result;
}

/*static*/ uint8_t* FileMediaStreamProducerImpl::getH265ExtraData(uint8_t* extraData, int extraDataSize, int& outSize)
{
    outSize = 0;
    if (!extraData || !extraDataSize || extraDataSize < 4)
        return NULL;

    const int maxSize = 1024;
    uint8_t* result = new uint8_t[maxSize];
    int index = 0;

    uint8_t* ptr = extraData;

    if(ptr[0] || ptr[1] || ptr[2] > 1)
    {
        /* It seems the extradata is encoded as hvcC format.
         * Temporarily, we support configurationVersion==0 until 14496-15 3rd
         * is finalized. When finalized, configurationVersion will be 1 and we
         * can recognize hvcC by checking if avctx->extradata[0]==1 or not. */

        if (extraDataSize < 23)
            return NULL;

        ptr += 22;
        int num_arrays = *ptr++;
        //printf("num_arrays=%d\n", num_arrays);

        /* Decode nal units from hvcC. */
        for (int i = 0; i < num_arrays; i++)
        {
            if (ptr + 5 >= extraData + extraDataSize)
                break;

            int type = *ptr++ & 0x3f;
            uint16_t cnt = static_cast<uint16_t>(ptr[0] << 8 | ptr[1]);
            //printf("type=%d, cnt_%d=%d\n", type, i, cnt);
            ptr += 2;

            for (int j = 0; j < cnt; j++)
            {
                if (i && index + 4 >= maxSize)
                    break;

                if(i) {
                    result[index++] = 0;
                    result[index++] = 0;
                    result[index++] = 0;
                    result[index++] = 1;
                }

                int nalsize = static_cast<uint16_t>(ptr[0]) << 8 | ptr[1];
                //printf("nalsize = %d\n", nalsize);
                ptr += 2;
                for (int k = 0; k< nalsize && ptr < extraData + extraDataSize && index < maxSize; k++)
                    result[index++] = *ptr++;

                if (ptr + 1 >= extraData + extraDataSize || index >= maxSize)
                {
                    outSize = index;
                    return result;
                }
            }
        }

        outSize = index;
        return result;
    }
    // TODO:
    // else {...}

    return NULL;
}

/*static*/ uint8_t* FileMediaStreamProducerImpl::findH264StartCode(uint8_t* data, int dataSize)
{
	if (!data || dataSize < 4)
	{
		return NULL;
	}

	uint8_t* ptr = data;
	uint8_t* pStart = NULL;
	while (ptr < data + dataSize - 4)
	{
		if (ptr[0] == 0 && ptr[1] == 0 && ptr[2] == 0 && ptr[3] == 1)
		{
			pStart = ptr;
			break;
		}
		ptr++;
	}
	return pStart;
}

/*static*/ uint8_t* FileMediaStreamProducerImpl::findH264Frame(uint8_t* data, int dataSize)
{
	if (!data || dataSize < 4)
	{
		return NULL;
	}

	uint8_t* ptr = data;
	unsigned length = static_cast<unsigned>(ptr[0]) << 24 | static_cast<unsigned>(ptr[1]) << 16 |
		static_cast<unsigned>(ptr[2]) << 8 | ptr[3];
	ptr += 4;
	while (ptr < data + dataSize && ((ptr[0] & 0x1F) == 8 || (ptr[0] & 0x1F) == 7 || (ptr[0] & 0x1F) == 6))
	{
		ptr += length;
		if (ptr + 4 < data + dataSize)
		{
			length = static_cast<unsigned>(ptr[0]) << 24 | static_cast<unsigned>(ptr[1]) << 16 |
				static_cast<unsigned>(ptr[2]) << 8 | ptr[3];
		}
		ptr += 4;
	}
	return ptr < data + dataSize ? ptr : NULL;
}	

/*static*/ uint8_t* FileMediaStreamProducerImpl::findH265Frame(uint8_t* data, int dataSize)
{
    if (!data || dataSize < 4)
    {
        return NULL;
    }

    uint8_t* ptr = data;
    unsigned length = static_cast<unsigned>(ptr[0]) << 24 | static_cast<unsigned>(ptr[1]) << 16 |
        static_cast<unsigned>(ptr[2]) << 8 | ptr[3];
#if 0
    fprintf(stderr, "[h265] frame length=%d, dataSize=%d: 0x%02x 0x%02x 0x%02x 0x%02x\n", length, dataSize, ptr[0], ptr[1], ptr[2], ptr[3]);
    for (unsigned i= 0; i < (dataSize>180?180:dataSize); i++)
        fprintf(stderr, "%02X ", (uint8_t)((unsigned char*)data)[i]);
    fprintf(stderr, "\n");
#endif

    ptr += 4;
    while (ptr < data + dataSize)
    {
        uint8_t type = ptr[0] >> 1 & 0x3F;
        if (!(type == 32 || type == 33 || type == 34 || type == 39))
            break;

#if 0
        fprintf(stderr, "LENGTH: %d, frame type: %d\n", length, type);
        if(type >= 32 && type <= 34) {
            fprintf(stderr, "PARAM_SET (%d). length: %d\n", type, length);
            for(int i=0; i<length; i++) {
                if(i%8 == 0)
                    fprintf(stderr, "\n");
                fprintf(stderr, "0x%02x ", ptr[i]);
            }
            fprintf(stderr, "\n");
        }
#endif

        ptr += length;
        if (ptr + 4 < data + dataSize)
            length = static_cast<unsigned>(ptr[0]) << 24 | static_cast<unsigned>(ptr[1]) << 16 |
                static_cast<unsigned>(ptr[2]) << 8 | ptr[3];
        ptr += 4;
    }
    return ptr < data + dataSize ? ptr : NULL;
}

/*static*/ uint8_t* FileMediaStreamProducerImpl::findMpeg4ExtraData(uint8_t* extraData, int extraDataSize, int& outSize)
{
	outSize = extraDataSize;
	if (!extraData)
	{
		return NULL;
	}

	/* Get data until 0x000001B2 */
	uint8_t* ptr = extraData;
	while (ptr < extraData + (extraDataSize - 4))
	{
		if (ptr[0] == 0 && ptr[1] == 0 && ptr[2] == 1 && ptr[3] == 0xB2)
		{
			outSize = ptr - extraData + 4;
			break;
		}
		ptr++;
	}
	return extraData;
}

} // namespace media
} // namespace videonext
