/* ---------------------------------------------------------------------------
** This software is in the public domain, furnished "as is", without technical
** support, and with no warranty, express or implied, as to its usefulness for
** any purpose.
**
** V4l2RTSPServer.h
** 
** V4L2 RTSP server
**
** -------------------------------------------------------------------------*/

#pragma once

#include <list>

// live555
#include <BasicUsageEnvironment.hh>
#include <GroupsockHelper.hh>

#include "UnicastServerMediaSubsession.h"
#include "MulticastServerMediaSubsession.h"
#include "TSServerMediaSubsession.h"
#include "HTTPServer.h"

class V4l2RTSPServer {
    public:
        V4l2RTSPServer(unsigned short rtspPort, unsigned short rtspOverHTTPPort = 0, int timeout = 10, unsigned int hlsSegment = 0, const std::list<std::string> & userPasswordList = std::list<std::string>(), const char* realm = NULL, const std::string & webroot = "", const char *sslkeycert = NULL)
            : m_stop(0)
            , m_env(BasicUsageEnvironment::createNew(*BasicTaskScheduler::createNew()))
            , m_rtspPort(rtspPort)
        {     
            UserAuthenticationDatabase* auth = createUserAuthenticationDatabase(userPasswordList, realm);
            m_rtspServer = HTTPServer::createNew(*m_env, rtspPort, auth, timeout, hlsSegment, webroot, sslkeycert);
           	if (m_rtspServer != NULL)
            {
                if (rtspOverHTTPPort)
                {
                    m_rtspServer->setUpTunnelingOverHTTP(rtspOverHTTPPort);
                }             
            }
            
        }

        virtual ~V4l2RTSPServer() {
            Medium::close(m_rtspServer);
            TaskScheduler* scheduler = &(m_env->taskScheduler());
            m_env->reclaim();
            delete scheduler;	
        }

        bool available() {
            return ((m_env != NULL) && (m_rtspServer != NULL));
        }
        std::string getResultMsg() {
            std::string result("UsageEnvironment not exists");
            if (m_env) {
                result = m_env->getResultMsg();
            }
            return result;
        }

        void eventLoop(char * stop) {
            m_env->taskScheduler().doEventLoop(stop); 
        }

        void eventLoop() {
            m_env->taskScheduler().doEventLoop(&m_stop); 
        }

        void stopLoop() {
            m_stop = 1;
        }

        UsageEnvironment* env() {
            return m_env; 
        }

        // -----------------------------------------
        //    create video capture & replicator
        // -----------------------------------------
        StreamReplicator* CreateVideoReplicator( 
					const V4L2DeviceParameters& inParam,
					int queueSize, V4L2DeviceSource::CaptureMode captureMode, int repeatConfig,
					const std::string& outputFile, V4l2IoType ioTypeOut, V4l2Output*& out);

#ifdef HAVE_ALSA
        StreamReplicator* CreateAudioReplicator(
			const std::string& audioDev, const std::list<snd_pcm_format_t>& audioFmtList, int audioFreq, int audioNbChannels, int verbose,
			int queueSize, V4L2DeviceSource::CaptureMode captureMode);
        
        static std::string       getV4l2Alsa(const std::string& v4l2device);
        static snd_pcm_format_t  decodeAudioFormat(const std::string& fmt);
        static std::string       getAudioFormatName(const snd_pcm_format_t fmt) {
            return snd_pcm_format_name(fmt);
        }
#endif

        // -----------------------------------------
        //    Add unicast Session
        // -----------------------------------------
        ServerMediaSession* AddUnicastSession(const std::string& url, StreamReplicator* videoReplicator, StreamReplicator* audioReplicator) {
			// Create Unicast Session					
			std::list<ServerMediaSubsession*> subSession;
			if (videoReplicator)
			{
				subSession.push_back(UnicastServerMediaSubsession::createNew(*this->env(), videoReplicator));				
			}
			if (audioReplicator)
			{
				subSession.push_back(UnicastServerMediaSubsession::createNew(*this->env(), audioReplicator));				
			}
			return this->addSession(url, subSession);	    
        }

        // -----------------------------------------
        //    Add HLS & MPEG# Session
        // -----------------------------------------
        ServerMediaSession* AddHlsSession(const std::string& url, int hlsSegment, StreamReplicator* videoReplicator, StreamReplicator* audioReplicator) {
            std::list<ServerMediaSubsession*> subSession;
            if (videoReplicator)
            {
                subSession.push_back(TSServerMediaSubsession::createNew(*this->env(), videoReplicator, audioReplicator, hlsSegment));				
            }
            ServerMediaSession* sms = this->addSession(url, subSession);
            
            struct in_addr ip;
#if LIVEMEDIA_LIBRARY_VERSION_INT	<	1611878400				
            ip.s_addr = ourIPAddress(*this->env());
#else
            ip.s_addr = ourIPv4Address(*this->env());
#endif
            LOG(NOTICE) << "HLS       http://" << inet_ntoa(ip) << ":" << m_rtspPort << "/" << url << ".m3u8";
            LOG(NOTICE) << "MPEG-DASH http://" << inet_ntoa(ip) << ":" << m_rtspPort << "/" << url << ".mpd";	
			
			return sms;	    
        }        


        // -----------------------------------------
        //    Add multicats Session
        // -----------------------------------------
        ServerMediaSession* AddMulticastSession(const std::string& url, in_addr destinationAddress, unsigned short & rtpPortNum, unsigned short & rtcpPortNum, StreamReplicator* videoReplicator, StreamReplicator* audioReplicator) {

            LOG(NOTICE) << "RTP  address " << inet_ntoa(destinationAddress) << ":" << rtpPortNum;
            LOG(NOTICE) << "RTCP address " << inet_ntoa(destinationAddress) << ":" << rtcpPortNum;
        	unsigned char ttl = 5;
            std::list<ServerMediaSubsession*> subSession;						
            if (videoReplicator)
            {
                subSession.push_back(MulticastServerMediaSubsession::createNew(*this->env(), destinationAddress, Port(rtpPortNum), Port(rtcpPortNum), ttl, videoReplicator));					
                // increment ports for next sessions
                rtpPortNum+=2;
                rtcpPortNum+=2;
            }
            
            if (audioReplicator)
            {
                subSession.push_back(MulticastServerMediaSubsession::createNew(*this->env(), destinationAddress, Port(rtpPortNum), Port(rtcpPortNum), ttl, audioReplicator));				
                
                // increment ports for next sessions
                rtpPortNum+=2;
                rtcpPortNum+=2;
            }
            return this->addSession(url, subSession);
        }	

        std::string decodeMulticastUrl(const std::string & maddr, in_addr & destinationAddress, unsigned short & rtpPortNum, unsigned short & rtcpPortNum)
        {
            std::istringstream is(maddr);
            std::string ip;
            getline(is, ip, ':');						
            if (!ip.empty())
            {
                destinationAddress.s_addr = inet_addr(ip.c_str());
            } else {
                destinationAddress.s_addr = chooseRandomIPv4SSMAddress(*this->env());
            }	
            
            rtpPortNum = 20000;
            std::string port;
            getline(is, port, ':');						
            if (!port.empty())
            {
                rtpPortNum = atoi(port.c_str());
            }	
            rtcpPortNum = rtpPortNum+1;
            getline(is, port, ':');						
            if (!port.empty())
            {
                rtcpPortNum = atoi(port.c_str());
            }
            return inet_ntoa(destinationAddress) + std::string(":") + std::to_string(rtpPortNum) + std::string(":") + std::to_string(rtcpPortNum);
        }

        ServerMediaSession* AddMulticastSession(const std::string& url, const std::string& inmulticasturi, std::string& outmulticasturi, StreamReplicator* videoReplicator, StreamReplicator* audioReplicator) {
			struct in_addr destinationAddress;
			unsigned short rtpPortNum;
			unsigned short rtcpPortNum;
            outmulticasturi = this->decodeMulticastUrl(inmulticasturi, destinationAddress, rtpPortNum, rtcpPortNum);
            return this->AddMulticastSession(url, destinationAddress, rtpPortNum, rtcpPortNum, videoReplicator, audioReplicator);
        }

        // -----------------------------------------
        //    get rtsp url
        // -----------------------------------------
        std::string getRtspUrl(ServerMediaSession* sms) {
            std::string url;
            char* rtspurl = m_rtspServer->rtspURL(sms);
            if (rtspurl != NULL)
            {
                url = rtspurl;
                delete[] rtspurl;			
            }
            return url;
        }
        int numClientSessions() {
            return m_rtspServer->numClientSessions();        
        }

        void RemoveSession(ServerMediaSession* sms) {
            m_rtspServer->deleteServerMediaSession(sms);
        }

    protected:
        UserAuthenticationDatabase* createUserAuthenticationDatabase(const std::list<std::string> & userPasswordList, const char* realm)
        {
            UserAuthenticationDatabase* auth = NULL;
            if (userPasswordList.size() > 0)
            {
                auth = new UserAuthenticationDatabase(realm, (realm != NULL) );
                
                std::list<std::string>::const_iterator it;
                for (it = userPasswordList.begin(); it != userPasswordList.end(); ++it)
                {
                    std::istringstream is(*it);
                    std::string user;
                    getline(is, user, ':');	
                    std::string password;
                    getline(is, password);	
                    auth->addUserRecord(user.c_str(), password.c_str());
                }
            }
            
            return auth;
        }

        ServerMediaSession* addSession(const std::string & sessionName, ServerMediaSubsession* subSession)
        {
            std::list<ServerMediaSubsession*> subSessionList;
            if (subSession) {
                subSessionList.push_back(subSession);
            }
            return this->addSession(sessionName, subSessionList);
        }

        ServerMediaSession* addSession(const std::string & sessionName, const std::list<ServerMediaSubsession*> & subSession)
        {
            ServerMediaSession* sms = NULL;
            if (subSession.empty() == false)
            {
                sms = ServerMediaSession::createNew(*m_env, sessionName.c_str());
                if (sms != NULL)
                {
                    std::list<ServerMediaSubsession*>::const_iterator subIt;
                    for (subIt = subSession.begin(); subIt != subSession.end(); ++subIt)
                    {
                        sms->addSubsession(*subIt);
                    }
                    
                    m_rtspServer->addServerMediaSession(sms);

                    char* url = m_rtspServer->rtspURL(sms);
                    if (url != NULL)
                    {
                        LOG(NOTICE) << "Play this stream using the URL \"" << url << "\"";
                        delete[] url;			
                    }
                }
            }
            return sms;
        }

    protected:
        char              m_stop;
        UsageEnvironment* m_env;	
        RTSPServer*       m_rtspServer;
        int               m_rtspPort;
};
