#include "LiveStreamPushModule.h"

struct CLiveStreamHandler
{
    CImageIO *pImageIO;
    LiveStreamParam sParam;

    ServerMediaSession* pSMS;
};

CLiveStreamPushModule::CLiveStreamPushModule()
{
    m_pEnv = nullptr;
    m_pRTSPServer = nullptr;

    m_szQuit = 0;
    m_bInService = false;
}

CLiveStreamPushModule::~CLiveStreamPushModule()
{
    while(!m_vLiveStreamHandlers.empty())
    {
        CLiveStreamHandler *pHandler = m_vLiveStreamHandlers[0];
        StopLiveStream(pHandler);
    }
}

void CLiveStreamPushModule::StartLiveStreamService(int nPort)
{
    if(CheckInService())
        return;

    m_pEnv = nullptr;
    m_pRTSPServer = nullptr;
    m_szQuit = 0;
    m_bInService = false;
    m_nPort = nPort;
    if(m_nPort < 0)
        m_nPort = 8554;

    std::thread iWorkThread(&CLiveStreamPushModule::WorkThread,this);
    iWorkThread.detach();

    while(!CheckInService())
    {
        std::this_thread::sleep_for(std::chrono::milliseconds(10));
    }
}

void CLiveStreamPushModule::StopLiveStreamService()
{
    m_szQuit = 1;

    while(CheckInService())
    {
        std::this_thread::sleep_for(std::chrono::milliseconds(10));
    }
}

bool CLiveStreamPushModule::IsInService()
{
    return CheckInService();
}

CLiveStreamHandler *CLiveStreamPushModule::AddOneLiveStream(LiveStreamParam sParam)
{
    if(!CheckInService())
    {
        return nullptr;
    }

    if(sParam.strStreamName.empty())
    {
        return nullptr;
    }

    CLiveStreamHandler *pAlreadyHandler = AlreadyHasStreamName(sParam.strStreamName);
    if(pAlreadyHandler)
    {
        return pAlreadyHandler;
    }

    CImageIO *piImgIO = new CImageIO(sParam.nFrameRate,sParam.nWidth,sParam.nHeight);

    UsageEnvironment* pEnv = m_pEnv;
    RTSPServer* pRtspServer = m_pRTSPServer;

#if 1
    ServerMediaSession* pLiveStreamSMS
            = ServerMediaSession::createNew(*pEnv, sParam.strStreamName.c_str() , sParam.strStreamInfo.c_str(),
                                            "Session streamed by EwayRobot Company.",
                                            false /*SSM*/);

    OutPacketBuffer::maxSize = 10000000;
    CLiveStreamMediaSubSession *pLiveStreamSubSession = CLiveStreamMediaSubSession::createNew(*pEnv,piImgIO);

    pLiveStreamSMS->addSubsession(pLiveStreamSubSession);
    pRtspServer->addServerMediaSession(pLiveStreamSMS);

    char* url = pRtspServer->rtspURL(pLiveStreamSMS);
    *pEnv << "Play this stream using the URL \"" << url << "\"\n";
    delete[] url;
#else

    struct in_addr destinationAddress;
    destinationAddress.s_addr = chooseRandomIPv4SSMAddress(*pEnv);

    const unsigned short rtpPortNum = 18888;
    const unsigned short rtcpPortNum = rtpPortNum+1;
    const unsigned char ttl = 255;

    const Port rtpPort(rtpPortNum);
    const Port rtcpPort(rtcpPortNum);

    Groupsock rtpGroupsock(*pEnv, destinationAddress, rtpPort, ttl);
    rtpGroupsock.multicastSendOnly(); // we're a SSM source
    Groupsock rtcpGroupsock(*pEnv, destinationAddress, rtcpPort, ttl);
    rtcpGroupsock.multicastSendOnly(); // we're a SSM source

    // Create a 'H265 Video RTP' sink from the RTP 'groupsock':
    OutPacketBuffer::maxSize = 10000000;
    RTPSink* videoSink = H264VideoRTPSink::createNew(*pEnv, &rtpGroupsock, 96);

    // Create (and start) a 'RTCP instance' for this RTP sink:
    const unsigned estimatedSessionBandwidth = 5000; // in kbps; for RTCP b/w share
    const unsigned maxCNAMElen = 100;
    unsigned char CNAME[maxCNAMElen+1];
    gethostname((char*)CNAME, maxCNAMElen);
    CNAME[maxCNAMElen] = '\0'; // just in case
    RTCPInstance* rtcp
            = RTCPInstance::createNew(*pEnv, &rtcpGroupsock,
                                      estimatedSessionBandwidth, CNAME,
                                      videoSink, NULL /* we're a server */,
                                      True /* we're a SSM source */);

    // Note: This starts RTCP running automatically

    ServerMediaSession* pLiveStreamSMS
            = ServerMediaSession::createNew(*pEnv, sParam.strStreamName.c_str() , sParam.strStreamInfo.c_str(),
                                            "Session streamed by EwayRobot Company.",
                                            true /*SSM*/);

    pLiveStreamSMS->addSubsession(PassiveServerMediaSubsession::createNew(*videoSink, rtcp));
    pRtspServer->addServerMediaSession(pLiveStreamSMS);

    char* url = pRtspServer->rtspURL(pLiveStreamSMS);
    *pEnv << "Play this stream using the URL \"" << url << "\"\n";
    delete[] url;

    CFramedLiveSource* fileSource = CFramedLiveSource::createNew(*pEnv,piImgIO);
    if (fileSource == NULL) {
        *pEnv << "Unable to open file \" as a byte-stream file source\n";
        exit(1);
    }

    H264VideoStreamFramer *pVideoSource = H264VideoStreamFramer::createNew(*pEnv, fileSource);

    videoSink->startPlaying(*pVideoSource, NULL, NULL);
#endif


    CLiveStreamHandler *pHandler = new CLiveStreamHandler;
    pHandler->sParam = sParam;
    pHandler->pImageIO = piImgIO;
    pHandler->pSMS = pLiveStreamSMS;
    m_vLiveStreamHandlers.push_back(pHandler);

    return pHandler;
}

bool CLiveStreamPushModule::PushImage(CLiveStreamHandler *pHandler, const cv::Mat &iMat)
{
    if(!CheckInService())
        return false;

    if(pHandler == nullptr  || !ContainsStreamHandler(pHandler) || iMat.empty())
        return false;

    if(pHandler->sParam.nHeight != iMat.rows ||
            pHandler->sParam.nWidth != iMat.cols)
        return false;

    return pHandler->pImageIO->WriteCVMat(iMat);
}

bool CLiveStreamPushModule::StartSaveToFile(CLiveStreamHandler *pHandler, std::string strFileName)
{
    if(!CheckInService())
        return false;

    if(pHandler == nullptr || !ContainsStreamHandler(pHandler))
        return false;

    return pHandler->pImageIO->StartRecording(strFileName);
}

bool CLiveStreamPushModule::StopSaveToFile(CLiveStreamHandler *pHandler)
{
    if(!CheckInService())
        return false;

    if(pHandler == nullptr || !ContainsStreamHandler(pHandler))
        return false;

    return pHandler->pImageIO->SetFinishRecordFlag();
}

bool CLiveStreamPushModule::StopLiveStream(CLiveStreamHandler *pHandler)
{
    if(!CheckInService())
        return false;

    if(pHandler == nullptr || !ContainsStreamHandler(pHandler))
        return false;

    UsageEnvironment* pEnv = m_pEnv;
    RTSPServer* pRtspServer = m_pRTSPServer;

    StopSaveToFile(pHandler);

    pRtspServer->deleteServerMediaSession(pHandler->pSMS);
    delete pHandler->pImageIO;
    RemoveStreamHandler(pHandler);
}


void CLiveStreamPushModule::WorkThread()
{
    HandleUnicast();
}

bool CLiveStreamPushModule::CheckInService()
{
    if(m_bInService == false || nullptr == m_pEnv || nullptr == m_pRTSPServer)
        return false;

    return true;
}

CLiveStreamHandler * CLiveStreamPushModule::AlreadyHasStreamName(const std::string &strStreamName)
{
    int nSize = m_vLiveStreamHandlers.size();
    for(int i = 0;i < nSize;i++)
    {
        CLiveStreamHandler *pHandler = m_vLiveStreamHandlers[i];
        if(pHandler->sParam.strStreamName == strStreamName)
            return pHandler;
    }

    return nullptr;
}

bool CLiveStreamPushModule::ContainsStreamHandler(CLiveStreamHandler *pHandler)
{
    if(pHandler == nullptr)
        return false;

    auto it = std::find(m_vLiveStreamHandlers.begin(), m_vLiveStreamHandlers.end(), pHandler);
    if (it != m_vLiveStreamHandlers.end())
    {
        return true;
    }

    return false;
}

bool CLiveStreamPushModule::RemoveStreamHandler(CLiveStreamHandler *pHandler)
{
    auto it = std::find(m_vLiveStreamHandlers.begin(), m_vLiveStreamHandlers.end(), pHandler);
    while (it != m_vLiveStreamHandlers.end())
    {
        it = m_vLiveStreamHandlers.erase(it);
        it = std::find(it, m_vLiveStreamHandlers.end(), pHandler);
    }
}

int CLiveStreamPushModule::HandleUnicast()
{
    TaskScheduler* pScheduler = BasicTaskScheduler::createNew();
    UsageEnvironment* pEnv = BasicUsageEnvironment::createNew(*pScheduler);
    m_pEnv = pEnv;


    RTSPServer* pRtspServer = RTSPServer::createNew(*pEnv, m_nPort);
    if (pRtspServer == NULL) {
        *pEnv << "Create RTSP Service Failed : " << pEnv->getResultMsg();
        return 0;
    }
    m_pRTSPServer = pRtspServer;


    m_bInService = true;
    pEnv->taskScheduler().doEventLoop(&m_szQuit); // does not return

    m_bInService = false;
    m_pEnv = nullptr;
    m_pRTSPServer = nullptr;

    return 0;
}
