/*
 *			GPAC - Multimedia Framework C SDK
 *
 *			Authors: Jean Le Feuvre 
 *			Copyright (c) Telecom ParisTech 2009-2012
 *			All rights reserved
 *
 *  This file is part of GPAC / Platinum UPnP module 
 *
 *  GPAC is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser General Public License as published by
 *  the Free Software Foundation; either version 2, or (at your option)
 *  any later version.
 *   
 *  GPAC is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Lesser General Public License for more details.
 *   
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; see the file COPYING.  If not, write to
 *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. 
 *
 *
 *	----------------------------------------------------------------------------------
 *		PLATINUM IS LICENSED UNDER GPL or commercial agreement - cf platinum license
 *	----------------------------------------------------------------------------------
 *
 */

#include "GPACPlatinum.h"

NPT_SET_LOCAL_LOGGER("gpac.media.renderer")

extern NPT_UInt8 RDR_ConnectionManagerSCPD[];
extern NPT_UInt8 RDR_AVTransportSCPD[];
extern NPT_UInt8 RDR_RenderingControlSCPD[];


void format_time_string(char *str, Double dur)
{
	u32 h, m, s;
	h = (u32) (dur / 3600);
	m = (u32) ( (dur - h*3600) / 60);
	s = (u32) ((dur - h*3600 - m*60));
	sprintf(str, "%02d:%02d:%02d", h, m, s);
}

GPAC_MediaRenderer::GPAC_MediaRenderer(GF_UPnP *upnp, const char*  friendly_name, 
                                     bool         show_ip, 
                                     const char*  uuid, 
                                     unsigned int port) :	
    PLT_DeviceHost("/", uuid, "urn:schemas-upnp-org:device:MediaRenderer:1", friendly_name, show_ip, port)
{
	m_mediaHistoryList = gf_list_new();
	m_pUPnP = upnp;
	m_connected = 0;
	m_Duration = m_Time = 0;
}

GPAC_MediaRenderer::~GPAC_MediaRenderer()
{
	if (m_mediaHistoryList) {
		/* empty mediaHistoryList */
		while (gf_list_count(m_mediaHistoryList) > 0) {
			char * last = (char*)gf_list_last(m_mediaHistoryList);
			gf_list_rem_last(m_mediaHistoryList);
			gf_free(last);
		}
		gf_list_del(m_mediaHistoryList);
	}
}

NPT_Result
GPAC_MediaRenderer::SetupServices(PLT_DeviceData& data)
{
    PLT_Service* service;

    {
        /* AVTransport */
        m_pAVService = new PLT_Service(
            &data,
            "urn:schemas-upnp-org:service:AVTransport:1", 
            "urn:upnp-org:serviceId:AVT_1-0",
            "urn:schemas-upnp-org:metadata-1-0/AVT/");
        NPT_CHECK_FATAL(m_pAVService->SetSCPDXML((const char*) RDR_AVTransportSCPD));
        NPT_CHECK_FATAL(m_pAVService->InitURLs("AVTransport", data.GetUUID()));
        NPT_CHECK_FATAL(data.AddService(m_pAVService));

        m_pAVService->SetStateVariableRate("LastChange", NPT_TimeInterval(0.2f));
        m_pAVService->SetStateVariable("A_ARG_TYPE_InstanceID", "0"); 

        // GetCurrentTransportActions
        m_pAVService->SetStateVariable("CurrentTransportActions", "Play,Pause,Stop,Seek,Next,Previous");

        // GetDeviceCapabilities
        m_pAVService->SetStateVariable("PossiblePlaybackStorageMedia", "NONE,NETWORK");
        m_pAVService->SetStateVariable("PossibleRecordStorageMedia", "NOT_IMPLEMENTED");
        m_pAVService->SetStateVariable("PossibleRecordQualityModes", "NOT_IMPLEMENTED");

        // GetMediaInfo
        m_pAVService->SetStateVariable("NumberOfTracks", "0");
        m_pAVService->SetStateVariable("CurrentMediaDuration", "00:00:00");;
        m_pAVService->SetStateVariable("AVTransportURI", "");
        m_pAVService->SetStateVariable("AVTransportURIMetadata", "");;
        m_pAVService->SetStateVariable("NextAVTransportURI", "NOT_IMPLEMENTED");
        m_pAVService->SetStateVariable("NextAVTransportURIMetadata", "NOT_IMPLEMENTED");
        m_pAVService->SetStateVariable("PlaybackStorageMedium", "NONE");
        m_pAVService->SetStateVariable("RecordStorageMedium", "NOT_IMPLEMENTED");
        m_pAVService->SetStateVariable("RecordMediumWriteStatus", "NOT_IMPLEMENTED");

        // GetPositionInfo
        m_pAVService->SetStateVariable("CurrentTrack", "0");
        m_pAVService->SetStateVariable("CurrentTrackDuration", "00:00:00");
        m_pAVService->SetStateVariable("CurrentTrackMetadata", "");
        m_pAVService->SetStateVariable("CurrentTrackURI", "");
        m_pAVService->SetStateVariable("RelativeTimePosition", "00:00:00"); 
        m_pAVService->SetStateVariable("AbsoluteTimePosition", "00:00:00");
        m_pAVService->SetStateVariable("RelativeCounterPosition", "2147483647"); // means NOT_IMPLEMENTED
        m_pAVService->SetStateVariable("AbsoluteCounterPosition", "2147483647"); // means NOT_IMPLEMENTED

        // disable indirect eventing for certain state variables
        //PLT_StateVariable* var;
        //var = 
			m_pAVService->FindStateVariable("RelativeTimePosition");
        //if (var) var->DisableIndirectEventing();
        //var = 
			m_pAVService->FindStateVariable("AbsoluteTimePosition");
        //if (var) var->DisableIndirectEventing();
        //var = 
			m_pAVService->FindStateVariable("RelativeCounterPosition");
        //if (var) var->DisableIndirectEventing();
        //var = 
			m_pAVService->FindStateVariable("AbsoluteCounterPosition");
        //if (var) var->DisableIndirectEventing();

        // GetTransportInfo
        m_pAVService->SetStateVariable("TransportState", "NO_MEDIA_PRESENT");
        m_pAVService->SetStateVariable("TransportStatus", "OK");
        m_pAVService->SetStateVariable("TransportPlaySpeed", "1");

        // GetTransportSettings
        m_pAVService->SetStateVariable("CurrentPlayMode", "NORMAL");
        m_pAVService->SetStateVariable("CurrentRecordQualityMode", "NOT_IMPLEMENTED");
    }

    {
        /* ConnectionManager */
        service = new PLT_Service(
            &data,
            "urn:schemas-upnp-org:service:ConnectionManager:1", 
            "urn:upnp-org:serviceId:CMGR_1-0");
        NPT_CHECK_FATAL(service->SetSCPDXML((const char*) RDR_ConnectionManagerSCPD));
        NPT_CHECK_FATAL(service->InitURLs("ConnectionManager", data.GetUUID()));
        NPT_CHECK_FATAL(data.AddService(service));

        service->SetStateVariable("CurrentConnectionIDs", "0");

        // put all supported mime types here instead
        service->SetStateVariable("SinkProtocolInfo", "http-get:*:*:*, rtsp-rtp-udp:*:*:*");
        service->SetStateVariable("SourceProtocolInfo", "");
    }

    {
        /* RenderingControl */
        service = new PLT_Service(
            &data,
            "urn:schemas-upnp-org:service:RenderingControl:1", 
            "urn:upnp-org:serviceId:RCS_1-0",
            "urn:schemas-upnp-org:metadata-1-0/RCS/");
        NPT_CHECK_FATAL(service->SetSCPDXML((const char*) RDR_RenderingControlSCPD));
        NPT_CHECK_FATAL(service->InitURLs("RenderingControl", data.GetUUID()));
        NPT_CHECK_FATAL(data.AddService(service));

        service->SetStateVariableRate("LastChange", NPT_TimeInterval(0.2f));

        service->SetStateVariable("Mute", "0");
        service->SetStateVariable("Volume", "100");
    }

    {
static NPT_UInt8 MIGRATION_SCPDXML[] = "<scpd xmlns=\"urn:schemas-upnp-org:service-1-0\">\
    <serviceStateTable>\
        <stateVariable>\
            <name>MigrationStatus</name>\
            <sendEventsAttribute>no</sendEventsAttribute>\
            <dataType>string</dataType>\
            <allowedValueList>\
                <allowedValue>OK</allowedValue>\
                <allowedValue>ERROR_OCCURRED</allowedValue>\
            </allowedValueList>\
        </stateVariable>\
        <stateVariable>\
            <name>MigrationMetaData</name>\
            <sendEventsAttribute>no</sendEventsAttribute>\
            <dataType>string</dataType>\
        </stateVariable>\
        <stateVariable>\
            <name>A_ARG_TYPE_InstanceID</name>\
            <sendEventsAttribute>no</sendEventsAttribute>\
            <dataType>ui4</dataType>\
        </stateVariable>\
    </serviceStateTable>\
    <actionList>\
        <action>\
            <name>StopForMigration</name>\
            <argumentList>\
                <argument>\
                    <name>InstanceID</name>\
                    <direction>in</direction>\
                    <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>\
                </argument>\
                <argument>\
                    <name>MigrationStatus</name>\
                    <direction>out</direction>\
                    <relatedStateVariable>MigrationStatus</relatedStateVariable>\
                </argument>\
                <argument>\
                    <name>MigrationMetaData</name>\
                    <direction>out</direction>\
                    <relatedStateVariable>MigrationMetaData</relatedStateVariable>\
                </argument>\
            </argumentList>\
        </action>\
    </actionList>\
</scpd>";

        /* MigrationService */
        m_pMigrationService = new PLT_Service(&data, "urn:intermedia:service:migration:1", "urn:intermedia:service:migration.001");
		
		NPT_CHECK_FATAL(m_pMigrationService->SetSCPDXML((const char*) MIGRATION_SCPDXML));
        NPT_CHECK_FATAL(m_pMigrationService->InitURLs("SessionMigration", data.GetUUID()));
        NPT_CHECK_FATAL(data.AddService(m_pMigrationService));

        m_pMigrationService->SetStateVariable("MigrationStatus", "OK");
        m_pMigrationService->SetStateVariable("MigrationMetaData", "");
    }

    return NPT_SUCCESS;
}

NPT_Result
GPAC_MediaRenderer::OnAction(PLT_ActionReference&          action, 
                            const PLT_HttpRequestContext& context)
{
    NPT_COMPILER_UNUSED(context);

    /* parse the action name */
    NPT_String name = action->GetActionDesc().GetName();

	m_ip_src = context.GetRemoteAddress().GetIpAddress().ToString();

	/* Is it a ConnectionManager Service Action ? */
    if (name.Compare("GetCurrentConnectionIDs", true) == 0) {
        if (NPT_FAILED(action->SetArgumentsOutFromStateVariable())) {
            return NPT_FAILURE;
        }
        return NPT_SUCCESS;
    }
    if (name.Compare("GetProtocolInfo", true) == 0) {
        if (NPT_FAILED(action->SetArgumentsOutFromStateVariable())) {
            return NPT_FAILURE;
        }
        return NPT_SUCCESS;
    }    
    if (name.Compare("GetCurrentConnectionInfo", true) == 0) {
        return OnGetCurrentConnectionInfo(action);
    }  
    if (name.Compare("StopForMigration", true) == 0) {
		NPT_String res = m_pUPnP->OnMigrate();
        m_pMigrationService->SetStateVariable("MigrationStatus", "OK");
        m_pMigrationService->SetStateVariable("MigrationMetaData", res);

		if (NPT_FAILED(action->SetArgumentsOutFromStateVariable())) {
            return NPT_FAILURE;
        }
        return NPT_SUCCESS;
    }  

    /* Is it a AVTransport Service Action ? */

    // since all actions take an instance ID and we only support 1 instance
    // verify that the Instance ID is 0 and return an error here now if not
    NPT_String serviceType = action->GetActionDesc().GetService()->GetServiceType();
    if (serviceType.Compare("urn:schemas-upnp-org:service:AVTransport:1", true) == 0) {
        if (NPT_FAILED(action->VerifyArgumentValue("InstanceID", "0"))) {
            action->SetError(802,"Not valid InstanceID.");
            return NPT_FAILURE;
        }
    }

    if (name.Compare("GetCurrentTransportActions", true) == 0) {
        if (NPT_FAILED(action->SetArgumentsOutFromStateVariable())) {
            return NPT_FAILURE;
        }
        return NPT_SUCCESS;
    }
    if (name.Compare("GetDeviceCapabilities", true) == 0) {
        if (NPT_FAILED(action->SetArgumentsOutFromStateVariable())) {
            return NPT_FAILURE;
        }
        return NPT_SUCCESS;
    }
    if (name.Compare("GetMediaInfo", true) == 0) {
        if (NPT_FAILED(action->SetArgumentsOutFromStateVariable())) {
            return NPT_FAILURE;
        }
        return NPT_SUCCESS;
    }
    if (name.Compare("GetPositionInfo", true) == 0) {
		if (m_pUPnP->m_pTerm->root_scene) {
			char szVal[100];

			m_pAVService->SetStateVariable("CurrentTrack", "0");
			format_time_string(szVal, m_Duration);
			m_pAVService->SetStateVariable("CurrentTrackDuration", szVal);

			m_pAVService->SetStateVariable("CurrentTrackMetadata", "");
			m_pAVService->SetStateVariable("CurrentTrackURI", "");
			format_time_string(szVal, m_Time);
			m_pAVService->SetStateVariable("RelativeTimePosition", szVal); 
			m_pAVService->SetStateVariable("AbsoluteTimePosition", szVal);
			m_pAVService->SetStateVariable("RelativeCounterPosition", "2147483647"); // means NOT_IMPLEMENTED
			m_pAVService->SetStateVariable("AbsoluteCounterPosition", "2147483647"); // means NOT_IMPLEMENTED
		} else {
			if (NPT_FAILED(action->SetArgumentsOutFromStateVariable())) {
				return NPT_FAILURE;
			}
		}
        return NPT_SUCCESS;
    }
    if (name.Compare("GetTransportInfo", true) == 0) {
        if (NPT_FAILED(action->SetArgumentsOutFromStateVariable())) {
            return NPT_FAILURE;
        }
        return NPT_SUCCESS;
    }
    if (name.Compare("GetTransportSettings", true) == 0) {
        if (NPT_FAILED(action->SetArgumentsOutFromStateVariable())) {
            return NPT_FAILURE;
        }
        return NPT_SUCCESS;
    }
    if (name.Compare("Next", true) == 0) {
        return OnNext(action);
    }
    if (name.Compare("Pause", true) == 0) {
        return OnPause(action);
    }
    if (name.Compare("Play", true) == 0) {
        return OnPlay(action);
    }
    if (name.Compare("Previous", true) == 0) {
        return OnPrevious(action);
    }
    if (name.Compare("Seek", true) == 0) {
        return OnSeek(action);
    }
    if (name.Compare("Stop", true) == 0) {
        return OnStop(action);
    }
    if (name.Compare("SetAVTransportURI", true) == 0) {
        return OnSetAVTransportURI(action);
    }
    if (name.Compare("SetPlayMode", true) == 0) {
        return OnSetPlayMode(action);
    }

    /* Is it a RendererControl Service Action ? */
    if (serviceType.Compare("urn:schemas-upnp-org:service:RenderingControl:1", true) == 0) {
        /* we only support master channel */
        if (NPT_FAILED(action->VerifyArgumentValue("Channel", "Master"))) {
            action->SetError(402,"Invalid Args.");
            return NPT_FAILURE;
        }
    }

    if (name.Compare("GetVolume", true) == 0) {
        NPT_CHECK_SEVERE(action->SetArgumentsOutFromStateVariable());
        return NPT_SUCCESS;
    }

    if (name.Compare("GetMute", true) == 0) {
        NPT_CHECK_SEVERE(action->SetArgumentsOutFromStateVariable());
        return NPT_SUCCESS;
    }

    if (name.Compare("SetVolume", true) == 0) {
          return OnSetVolume(action);
    }

    if (name.Compare("SetMute", true) == 0) {
          return OnSetMute(action);
    }

    action->SetError(401,"No Such Action.");
    return NPT_FAILURE;
}

NPT_Result
GPAC_MediaRenderer::OnGetCurrentConnectionInfo(PLT_ActionReference& action)
{
    if (NPT_FAILED(action->VerifyArgumentValue("ConnectionID", "0"))) {
        action->SetError(706,"No Such Connection.");
        return NPT_FAILURE;
    }

    if (NPT_FAILED(action->SetArgumentValue("RcsID", "0"))){
        return NPT_FAILURE;
    }
    if (NPT_FAILED(action->SetArgumentValue("AVTransportID", "0"))) {
        return NPT_FAILURE;
    }
    if (NPT_FAILED(action->SetArgumentValue("ProtocolInfo", "http-get:*:*:*"))) {
        return NPT_FAILURE;
    }
    if (NPT_FAILED(action->SetArgumentValue("PeerConnectionManager", "/"))) {
        return NPT_FAILURE;
    }
    if (NPT_FAILED(action->SetArgumentValue("PeerConnectionID", "-1"))) {
        return NPT_FAILURE;
    }
    if (NPT_FAILED(action->SetArgumentValue("Direction", "Input"))) {
        return NPT_FAILURE;
    }
    if (NPT_FAILED(action->SetArgumentValue("Status", "Unknown"))) {
        return NPT_FAILURE;
    }

    return NPT_SUCCESS;
}

NPT_Result GPAC_MediaRenderer::OnSetAVTransportURI(PLT_ActionReference& action)
{
	char the_url[4096], szVal[100];
    NPT_String url_id;
	const char *MediaUri;
    if (NPT_FAILED(action->GetArgumentValue("CurrentURI", url_id))) {
        return NPT_FAILURE;
	}
	MediaUri = url_id;
	if (!MediaUri) return NPT_FAILURE;

	GF_LOG(GF_LOG_INFO, GF_LOG_NETWORK, ("[UPnP] Request: change media\n"));

	if (m_connected) {
		m_connected = 0;
		m_pUPnP->OnStop(m_ip_src);
	}
	const char *ext = strrchr(MediaUri, '.');
	if (ext && !stricmp(ext, ".m3u")) {
		GF_LOG(GF_LOG_ERROR, GF_LOG_NETWORK, ("[UPnP] M3U playlists not supported yet\n"));
		return NPT_SUCCESS;
	}

	/* Load and add to mediaHistoryList */
	strcpy(the_url, MediaUri);
	GF_LOG(GF_LOG_INFO, GF_LOG_NETWORK, ("[UPnP] Adding media to the list : %s\n", MediaUri));
	gf_list_add(m_mediaHistoryList, gf_strdup(MediaUri));
	GF_LOG(GF_LOG_INFO, GF_LOG_NETWORK, ("[UPnP] Opening URL %s\n", the_url));
	m_track_pos = gf_list_count(m_mediaHistoryList);

	m_connected = 1;
	m_pUPnP->OnConnect(the_url, m_ip_src);
	/* Set UPnP datas */
    m_pAVService->SetStateVariable("TransportState", "PLAYING");
    m_pAVService->SetStateVariable("AVTransportURI", the_url);

	sprintf(szVal, "%d", gf_list_count(m_mediaHistoryList));
    m_pAVService->SetStateVariable("NumberOfTracks", szVal);
	sprintf(szVal, "%d", m_track_pos);
    m_pAVService->SetStateVariable("CurrentTrack", szVal);
	return NPT_SUCCESS;
}

NPT_Result GPAC_MediaRenderer::OnPause(PLT_ActionReference& action)
{
	GF_LOG(GF_LOG_INFO, GF_LOG_NETWORK, ("[UPnP] Request: change state : PAUSE\n"));
    m_pAVService->SetStateVariable("TransportState", "PAUSED_PLAYBACK");
	m_pUPnP->OnPause(0, m_ip_src);
    return NPT_SUCCESS;
}

NPT_Result GPAC_MediaRenderer::OnPlay(PLT_ActionReference& action)
{
	GF_LOG(GF_LOG_INFO, GF_LOG_NETWORK, ("[UPnP] Request: change state : PLAY\n"));

	/* if nothing playing, connect to first media of mediaHistoryList */
	if (m_connected) {
	    m_pAVService->SetStateVariable("TransportState", "PLAYING");
		m_pUPnP->OnPause(1, m_ip_src);
	} else if (gf_list_count(m_mediaHistoryList) >= 1) {
		char *track = (char *) gf_list_get(m_mediaHistoryList, 0);
		m_track_pos = 1;

		GF_LOG(GF_LOG_INFO, GF_LOG_NETWORK, ("[UPnP] Reading first media : %s\n", track));
	    m_pAVService->SetStateVariable("TransportState", "PLAYING");
		m_connected = 1;
		m_pUPnP->OnConnect(track, m_ip_src);
		//MRSetTrack(track, upnph->TrackPosition);
	}
    return NPT_SUCCESS;
}


NPT_Result GPAC_MediaRenderer::OnStop(PLT_ActionReference& action)
{
	GF_LOG(GF_LOG_INFO, GF_LOG_NETWORK, ("[UPnP] Request: change state : STOP\n"));
	if (m_pUPnP->m_pTerm->root_scene) {
	    m_pAVService->SetStateVariable("TransportState", "STOPPED");
		m_pUPnP->OnStop(m_ip_src);
	}
	return NPT_SUCCESS;
}

NPT_Result GPAC_MediaRenderer::OnPrevious(PLT_ActionReference& action)
{
    return NPT_SUCCESS;
}

NPT_Result GPAC_MediaRenderer::OnNext(PLT_ActionReference& action)
{
   return NPT_SUCCESS;
}

NPT_Result GPAC_MediaRenderer::OnSeek(PLT_ActionReference& action)
{
	u32 h, m, s;
	Double time;
    NPT_String unit, target;
    if (NPT_FAILED(action->GetArgumentValue("Unit", unit))) {
        return NPT_FAILURE;
	}
    if (NPT_FAILED(action->GetArgumentValue("Target", target))) {
        return NPT_FAILURE;
	}
	if ((unit!="ABS_TIME") && (unit!="REL_TIME")) {
		action->SetError(710,"Seek mode not supported");
        return NPT_FAILURE;
	}
	sscanf(target, "%d:%d:%d", &h, &m, &s);
	time = h*3600.0 + m*60.0 + s;
	m_pUPnP->OnSeek(time);
    return NPT_SUCCESS;
}


NPT_Result GPAC_MediaRenderer::OnSetPlayMode(PLT_ActionReference& action)
{
    return NPT_SUCCESS;
}


NPT_Result GPAC_MediaRenderer::OnSetVolume(PLT_ActionReference& action)
{
    return NPT_SUCCESS;
}

NPT_Result GPAC_MediaRenderer::OnSetMute(PLT_ActionReference& action)
{
    return NPT_SUCCESS;
}

void GPAC_MediaRenderer::SetDuration(Double duration, Bool can_seek)
{
	char szVal[100];
	format_time_string(szVal, duration);
	m_Duration = duration;
    m_pAVService->SetStateVariable("CurrentTrackDuration", szVal);
}

void GPAC_MediaRenderer::SetTime(Double time)
{
	char szVal[100];
	format_time_string(szVal, time);
	m_Time = time;
    m_pAVService->SetStateVariable("RelativeTimePosition", szVal);
    m_pAVService->SetStateVariable("AbsoluteTimePosition", szVal);
}

void GPAC_MediaRenderer::SetConnected(const char *url)
{
    m_pAVService->SetStateVariable("AVTransportURI", url);
    m_pAVService->SetStateVariable("CurrentTrackURI", url);
    m_pAVService->SetStateVariable("TransportState", "PLAYING");
	m_connected = url ? 1 : 0;
}
