/*MT*

    MediaTomb - http://www.mediatomb.cc/

    upnp_cds.cc - this file is part of MediaTomb.

    Copyright (C) 2005 Gena Batyan <bgeradz@mediatomb.cc>,
                       Sergey 'Jin' Bostandzhyan <jin@mediatomb.cc>

    Copyright (C) 2006-2010 Gena Batyan <bgeradz@mediatomb.cc>,
                            Sergey 'Jin' Bostandzhyan <jin@mediatomb.cc>,
                            Leonhard Wimmer <leo@mediatomb.cc>

    MediaTomb is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License version 2
    as published by the Free Software Foundation.

    MediaTomb 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 General Public License for more details.

    You should have received a copy of the GNU General Public License
    version 2 along with MediaTomb; if not, write to the Free Software
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.

    $Id$
*/

/// \file upnp_cds.cc

#include "upnp_cds.h"
#include "config_manager.h"
#include "server.h"
#include "storage.h"

using namespace zmm;
using namespace mxml;

ContentDirectoryService::ContentDirectoryService()
    : systemUpdateID(0)
    , stringLimit(ConfigManager::getInstance()->getIntOption(CFG_SERVER_UPNP_TITLE_AND_DESC_STRING_LIMIT))
{
}

ContentDirectoryService::~ContentDirectoryService()
{
}

void ContentDirectoryService::upnp_action_Browse(Ref<ActionRequest> request)
{
    log_debug("start\n");
    Ref<Storage> storage = Storage::getInstance();

    Ref<Element> req = request->getRequest();

    String objID = req->getChildText(_("ObjectID"));
    int objectID;
    String BrowseFlag = req->getChildText(_("BrowseFlag"));
    //String Filter; // not yet supported
    String StartingIndex = req->getChildText(_("StartingIndex"));
    String RequestedCount = req->getChildText(_("RequestedCount"));
    // String SortCriteria; // not yet supported

    //log_debug("Browse received parameters: ObjectID [%s] BrowseFlag [%s] StartingIndex [%s] RequestedCount [%s]\n",
    //            ObjectID.c_str(), BrowseFlag.c_str(), StartingIndex.c_str(), RequestedCount.c_str());

    if (objID == nullptr)
        throw UpnpException(UPNP_E_NO_SUCH_ID, _("empty object id"));
    else
        objectID = objID.toInt();

    unsigned int flag = BROWSE_ITEMS | BROWSE_CONTAINERS | BROWSE_EXACT_CHILDCOUNT;

    if (BrowseFlag == "BrowseDirectChildren")
        flag |= BROWSE_DIRECT_CHILDREN;
    else if (BrowseFlag != "BrowseMetadata")
        throw UpnpException(UPNP_SOAP_E_INVALID_ARGS,
            _("invalid browse flag: ") + BrowseFlag);

    Ref<CdsObject> parent = storage->loadObject(objectID);
    if ((parent->getClass() == UPNP_DEFAULT_CLASS_MUSIC_ALBUM) || (parent->getClass() == UPNP_DEFAULT_CLASS_PLAYLIST_CONTAINER))
        flag |= BROWSE_TRACK_SORT;

    if (ConfigManager::getInstance()->getBoolOption(CFG_SERVER_HIDE_PC_DIRECTORY))
        flag |= BROWSE_HIDE_FS_ROOT;

    Ref<BrowseParam> param(new BrowseParam(objectID, flag));

    param->setStartingIndex(StartingIndex.toInt());
    param->setRequestedCount(RequestedCount.toInt());

    Ref<Array<CdsObject>> arr;

    try {
        arr = storage->browse(param);
    } catch (const Exception& e) {
        throw UpnpException(UPNP_E_NO_SUCH_ID, _("no such object"));
    }

    Ref<Element> didl_lite(new Element(_("DIDL-Lite")));
    didl_lite->setAttribute(_(XML_NAMESPACE_ATTR),
        _(XML_DIDL_LITE_NAMESPACE));
    didl_lite->setAttribute(_(XML_DC_NAMESPACE_ATTR),
        _(XML_DC_NAMESPACE));
    didl_lite->setAttribute(_(XML_UPNP_NAMESPACE_ATTR),
        _(XML_UPNP_NAMESPACE));

    Ref<ConfigManager> cfg = ConfigManager::getInstance();

#ifdef EXTEND_PROTOCOLINFO
    if (cfg->getBoolOption(CFG_SERVER_EXTEND_PROTOCOLINFO_SM_HACK)) {
        didl_lite->setAttribute(_(XML_SEC_NAMESPACE_ATTR),
            _(XML_SEC_NAMESPACE));
    }
#endif

    for (int i = 0; i < arr->size(); i++) {
        Ref<CdsObject> obj = arr->get(i);
        if (cfg->getBoolOption(CFG_SERVER_EXTOPTS_MARK_PLAYED_ITEMS_ENABLED) && obj->getFlag(OBJECT_FLAG_PLAYED)) {
            String title = obj->getTitle();
            if (cfg->getBoolOption(CFG_SERVER_EXTOPTS_MARK_PLAYED_ITEMS_STRING_MODE_PREPEND))
                title = cfg->getOption(CFG_SERVER_EXTOPTS_MARK_PLAYED_ITEMS_STRING) + title;
            else
                title = title + cfg->getOption(CFG_SERVER_EXTOPTS_MARK_PLAYED_ITEMS_STRING);

            obj->setTitle(title);
        }

        Ref<Element> didl_object = UpnpXML_DIDLRenderObject(obj, false, stringLimit);

        didl_lite->appendElementChild(didl_object);
    }

    Ref<Element> response;
    response = UpnpXML_CreateResponse(request->getActionName(), _(DESC_CDS_SERVICE_TYPE));

    response->appendTextChild(_("Result"), didl_lite->print());
    response->appendTextChild(_("NumberReturned"), String::from(arr->size()));
    response->appendTextChild(_("TotalMatches"), String::from(param->getTotalMatches()));
    response->appendTextChild(_("UpdateID"), String::from(systemUpdateID));

    request->setResponse(response);
    log_debug("end\n");
}

void ContentDirectoryService::upnp_action_GetSearchCapabilities(Ref<ActionRequest> request)
{
    log_debug("start\n");

    Ref<Element> response;
    response = UpnpXML_CreateResponse(request->getActionName(), _(DESC_CDS_SERVICE_TYPE));
    response->appendTextChild(_("SearchCaps"), _(""));

    request->setResponse(response);

    log_debug("end\n");
}

void ContentDirectoryService::upnp_action_GetSortCapabilities(Ref<ActionRequest> request)
{
    log_debug("start\n");

    Ref<Element> response;
    response = UpnpXML_CreateResponse(request->getActionName(), _(DESC_CDS_SERVICE_TYPE));
    response->appendTextChild(_("SortCaps"), _(""));

    request->setResponse(response);

    log_debug("end\n");
}

void ContentDirectoryService::upnp_action_GetSystemUpdateID(Ref<ActionRequest> request)
{
    log_debug("start\n");

    Ref<Element> response;
    response = UpnpXML_CreateResponse(request->getActionName(), _(DESC_CDS_SERVICE_TYPE));
    response->appendTextChild(_("Id"), String::from(systemUpdateID));

    request->setResponse(response);

    log_debug("end\n");
}

void ContentDirectoryService::process_action_request(Ref<ActionRequest> request)
{
    log_debug("start\n");

    if (request->getActionName() == "Browse") {
        upnp_action_Browse(request);
    } else if (request->getActionName() == "GetSearchCapabilities") {
        upnp_action_GetSearchCapabilities(request);
    } else if (request->getActionName() == "GetSortCapabilities") {
        upnp_action_GetSortCapabilities(request);
    } else if (request->getActionName() == "GetSystemUpdateID") {
        upnp_action_GetSystemUpdateID(request);
    } else {
        // invalid or unsupported action
        log_debug("unrecognized action %s\n",
            request->getActionName().c_str());
        request->setErrorCode(UPNP_E_INVALID_ACTION);
        //    throw UpnpException(UPNP_E_INVALID_ACTION, _("unrecognized action"));
    }

    log_debug("ContentDirectoryService::process_action_request: end\n");
}

void ContentDirectoryService::process_subscription_request(zmm::Ref<SubscriptionRequest> request)
{
    int err;
    IXML_Document* event = nullptr;

    Ref<Element> propset, property;

    log_debug("start\n");

    propset = UpnpXML_CreateEventPropertySet();
    property = propset->getFirstElementChild();
    property->appendTextChild(_("SystemUpdateID"), _("") + systemUpdateID);
    Ref<CdsObject> obj = Storage::getInstance()->loadObject(0);
    Ref<CdsContainer> cont = RefCast(obj, CdsContainer);
    property->appendTextChild(_("ContainerUpdateIDs"), _("0,") + cont->getUpdateID());
    String xml = propset->print();
    err = ixmlParseBufferEx(xml.c_str(), &event);
    if (err != IXML_SUCCESS) {
        throw UpnpException(UPNP_E_SUBSCRIPTION_FAILED, _("Could not convert property set to ixml"));
    }

    UpnpAcceptSubscriptionExt(Server::getInstance()->getDeviceHandle(),
        ConfigManager::getInstance()->getOption(CFG_SERVER_UDN).c_str(),
        DESC_CDS_SERVICE_ID, event, request->getSubscriptionID().c_str());

    ixmlDocument_free(event);
    log_debug("end\n");
}

void ContentDirectoryService::subscription_update(String containerUpdateIDs_CSV)
{
    int err;
    IXML_Document* event = nullptr;

    Ref<Element> propset, property;

    log_debug("start\n");

    systemUpdateID++;

    propset = UpnpXML_CreateEventPropertySet();
    property = propset->getFirstElementChild();
    property->appendTextChild(_("ContainerUpdateIDs"), containerUpdateIDs_CSV);
    property->appendTextChild(_("SystemUpdateID"), _("") + systemUpdateID);

    String xml = propset->print();

    err = ixmlParseBufferEx(xml.c_str(), &event);
    if (err != IXML_SUCCESS) {
        /// \todo add another error code
        throw UpnpException(UPNP_E_SUBSCRIPTION_FAILED, _("Could not convert property set to ixml"));
    }

    UpnpNotifyExt(Server::getInstance()->getDeviceHandle(),
        ConfigManager::getInstance()->getOption(CFG_SERVER_UDN).c_str(),
        DESC_CDS_SERVICE_ID, event);

    ixmlDocument_free(event);

    log_debug("end\n");
}
