/*
 *			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 "PltUPnP.h"
#include "GPACFileMediaServer.h"
#include "PltMediaItem.h"
#include "PltService.h"
#include "PltTaskManager.h"
#include "PltHttpServer.h"
#include "PltDidl.h"
#include "PltMetadataHandler.h"
#include "PltVersion.h"

#include <gpac/network.h>

NPT_SET_LOCAL_LOGGER("gpac.media.server.file")

GPAC_FileMediaServer::GPAC_FileMediaServer(const char*  friendly_name,
                        bool         show_ip,
                        const char*  uuid,
						NPT_UInt16   port) : PLT_FileMediaServer("", friendly_name, show_ip, uuid, port) 
{
}

void GPAC_FileMediaServer::AddSharedDirectory(const char *path, const char *alias, Bool is_hidden)
{
	u8 buf[10];
	if (!alias) {
			sprintf((char*)buf, "%08X", gf_crc_32((char*) path, strlen(path)));
			alias = (const char *)buf;
	}
	m_Directories.Add(GPAC_MediaDirectory(alias, path, is_hidden));
}



NPT_Result
GPAC_FileMediaServer::OnBrowseDirectChildren(PLT_ActionReference&          action, 
												const char*                   object_id, 
												const char*                   filter,
												NPT_UInt32                    start_index,
												NPT_UInt32                    req_count,
												const NPT_List<NPT_String>&   sort_criteria,
												const PLT_HttpRequestContext& context)
{
	/*not the root of our server*/
	if (strcmp(object_id, "/") && strcmp(object_id, "\\") && strcmp(object_id, "0")) {
		return PLT_FileMediaServer::OnBrowseDirectChildren(action, object_id, filter, start_index, req_count, sort_criteria, context);
	}

	unsigned long cur_index = 0;
    unsigned long num_returned = 0;
    unsigned long total_matches = 0;
    NPT_String didl = didl_header;

    PLT_MediaObjectReference item;
	for (u32 i=0; i<m_Directories.GetItemCount(); i++) {
		GPAC_MediaDirectory *alias;
		m_Directories.Get(i, alias);
		if (alias->m_Hide) continue;

        item = BuildFromFilePath(alias->m_Path, context, true, false);

        if (!item.IsNull()) {
            if ((cur_index >= start_index) && ((num_returned < req_count) || (req_count == 0))) {
                NPT_String tmp;
                NPT_CHECK_SEVERE(PLT_Didl::ToDidl(*item.AsPointer(), filter, tmp));

                didl += tmp;
                num_returned++;
            }
            cur_index++;
            total_matches++;        
        }
    };

    didl += didl_footer;

    NPT_CHECK_SEVERE(action->SetArgumentValue("Result", didl));
    NPT_CHECK_SEVERE(action->SetArgumentValue("NumberReturned", NPT_String::FromInteger(num_returned)));
    NPT_CHECK_SEVERE(action->SetArgumentValue("TotalMatches", NPT_String::FromInteger(total_matches))); // 0 means we don't know how many we have but most browsers don't like that!!
    NPT_CHECK_SEVERE(action->SetArgumentValue("UpdateId", "1"));

    return NPT_SUCCESS;
}

NPT_Result 
GPAC_FileMediaServer::ServeFile(NPT_HttpRequest&              request, 
                                 const NPT_HttpRequestContext& context,
                                 NPT_HttpResponse&             response,
                                 const NPT_String&             _file_path)
{
    NPT_COMPILER_UNUSED(context);

	NPT_String file_path = _file_path;
    NPT_String uri_path = NPT_Uri::PercentDecode(request.GetUrl().GetPath());
	NPT_String query = request.GetUrl().GetQuery();
	if (! query.IsEmpty()) {
		uri_path += "?";
		uri_path += query;
	}

    // prevent hackers from accessing files outside of our root
    if ((file_path.Find("/..") >= 0) || (file_path.Find("\\..") >= 0)) {
        return NPT_FAILURE;
    }

	NPT_String file_id = (const char *) file_path + ((file_path[0]=='0') ? 2:1);
	for (u32 i=0; i<m_Directories.GetItemCount(); i++) {
		GPAC_MediaDirectory *dir;
		m_Directories.Get(i, dir);
		if (file_id.StartsWith(dir->m_Alias)) {
			file_path = dir->m_Path + ((const char *) file_id + strlen(dir->m_Alias)+1);
			break;
		}
	}

	//Virtual File request
	for (u32 i=0; i<m_VirtualFiles.GetItemCount(); i++) {
		GPAC_VirtualFile vfile;
		m_VirtualFiles.Get(i, vfile);

		NPT_String path = uri_path;
		path.Replace('/', NPT_FilePath::Separator);

		if (path == vfile.m_URI) {
			NPT_Result res;
			NPT_Position start, end;
			PLT_HttpHelper::GetRange(request, start, end);
			res = ServeVirtualFile(response, &vfile, start, end, !request.GetMethod().Compare("HEAD"));
			if (vfile.m_temporary) {
				m_VirtualFiles.Remove(vfile);
			}
			return res;
		}
	}

    // File requested
    NPT_String path = m_FileBaseUri.GetPath();
    if (path.Compare(uri_path.Left(path.GetLength()), true) == 0) {
        NPT_Position start, end;
        PLT_HttpHelper::GetRange(request, start, end);
        
        return PLT_FileServer::ServeFile(response,
                                         file_path, 
                                         start, 
                                         end, 
                                         !request.GetMethod().Compare("HEAD"));
    } 

    // Album Art requested
    path = m_AlbumArtBaseUri.GetPath();
    if (path.Compare(uri_path.Left(path.GetLength()), true) == 0) {
        return OnAlbumArtRequest(response, file_path);
    } 
    
    return NPT_FAILURE;
}

NPT_Result
GPAC_FileMediaServer::GetFilePath(const char* object_id, 
                                 NPT_String& filepath) 
{
    if (!object_id) return NPT_ERROR_INVALID_PARAMETERS;

	NPT_String obj_id = object_id + (object_id[0]=='0' ? 2 : 1);
    filepath = "";
	for (u32 i=0; i<m_Directories.GetItemCount(); i++) {
		GPAC_MediaDirectory *dir;
		m_Directories.Get(i, dir);
		if (obj_id.StartsWith(dir->m_Alias)) {
			filepath = dir->m_Path;
			object_id += (object_id[0]=='0' ? 2 : 1);
			if (!strcmp(object_id, dir->m_Alias)) {
				object_id = "";
			} else {
				object_id += strlen(dir->m_Alias) + 1;
			}
			break;
		}
	}

    if (NPT_StringLength(object_id) > 2 || object_id[0]!='0') {
        filepath += (const char*)object_id + (object_id[0]=='0'?1:0);
    }

    return NPT_SUCCESS;
}

PLT_MediaObject*
GPAC_FileMediaServer::BuildFromFilePath(const NPT_String&        filepath, 
                                       const PLT_HttpRequestContext &context,
                                       bool                     with_count,
                                       bool                     keep_extension_in_title)
{
	return BuildFromFilePathAndHost(filepath, &context, with_count, keep_extension_in_title, NULL);

}

PLT_MediaObject*
GPAC_FileMediaServer::BuildFromFilePathAndHost(const NPT_String&        __filepath, 
                                       const PLT_HttpRequestContext *context,
                                       bool                     with_count /* = true */,
                                       bool                     keep_extension_in_title /* = false */,
									   const char *host)
{
    PLT_MediaItemResource resource;
    PLT_MediaObject*      object = NULL;
	NPT_String filepath = __filepath;
	unsigned int len;

    /* retrieve the entry type (directory or file) */
    NPT_FileInfo info; 
    NPT_CHECK_LABEL_FATAL(NPT_File::GetInfo(__filepath, &info), failure);

	len = 0;
	for (u32 i=0; i<m_Directories.GetItemCount(); i++) {
		GPAC_MediaDirectory *dir;
		m_Directories.Get(i, dir);
		if (__filepath.StartsWith(dir->m_Path) && (dir->m_Path.GetLength() > len) ) {
			char *fp = __filepath;
			filepath = NPT_FilePath::Separator + dir->m_Alias + NPT_String( fp + strlen(dir->m_Path) - 1);
			len = dir->m_Path.GetLength();
		}
	}


    if (info.m_Type == NPT_FileInfo::FILE_TYPE_REGULAR) {
		NPT_IpAddress ip;

        object = new PLT_MediaItem();

        /* Set the title using the filename for now */
        object->m_Title = NPT_FilePath::BaseName(filepath, keep_extension_in_title);
        if (object->m_Title.GetLength() == 0) goto failure;

        /* Set the protocol Info from the extension */
        resource.m_ProtocolInfo = PLT_ProtocolInfo(PLT_MediaItem::GetProtocolInfo(filepath, true, context));
        if (!resource.m_ProtocolInfo.IsValid())  goto failure;

        /* Set the resource file size */
        resource.m_Size = info.m_Size;
 
        /* format the resource URI */
        NPT_String url = "0" + filepath;


		if (host) {
			ip.Parse(host);

			NPT_List<NPT_NetworkInterface*> if_list;
			NPT_List<NPT_NetworkInterface*>::Iterator net_if;
			NPT_List<NPT_NetworkInterfaceAddress>::Iterator net_if_addr;

			NPT_CHECK_LABEL_SEVERE(PLT_UPnPMessageHelper::GetNetworkInterfaces(if_list, true), failure);

			for (net_if = if_list.GetFirstItem(); net_if; net_if++) {
				if ( (*net_if)->IsAddressInNetwork(ip) ) {
					ip = (*net_if)->GetAddresses().GetFirstItem()->GetPrimaryAddress();
					break;
				}
			}

		} else if (context) {
			ip = context->GetLocalAddress().GetIpAddress();
		} else {
			// get list of ip addresses
			NPT_List<NPT_IpAddress> ips;
			NPT_CHECK_LABEL_SEVERE(PLT_UPnPMessageHelper::GetIPAddresses(ips), failure);

			// iterate through list and build list of resources
			NPT_List<NPT_IpAddress>::Iterator ipi = ips.GetFirstItem();

			ip = *ipi;
		}

        /* Look to see if a metadatahandler exists for this extension */
        PLT_MetadataHandler* handler = NULL;
        NPT_Result res = NPT_ContainerFind(
            m_MetadataHandlers, 
            PLT_MetadataHandlerFinder(NPT_FilePath::FileExtension(filepath)), 
            handler);
        if (NPT_SUCCEEDED(res) && handler) {
            /* if it failed loading data, reset the metadatahandler so we don't use it */
            if (NPT_SUCCEEDED(handler->Load(filepath))) {
                /* replace the title with the one from the Metadata */
                NPT_String newTitle;
                if (handler->GetTitle(newTitle) != NULL) {
                    object->m_Title = newTitle;
                }

                /* assign description */
                handler->GetDescription(object->m_Description.long_description);

                /* assign album art uri if we haven't yet */
                /* prepend the album art base URI and url encode it */ 
                if (object->m_ExtraInfo.album_art_uri.GetLength() == 0) {
                    object->m_ExtraInfo.album_art_uri = 
						NPT_Uri::PercentEncode(BuildResourceUri(m_AlbumArtBaseUri, ip.ToString(), url), 
                                               NPT_Uri::UnsafeCharsToEncode);
                }

                /* duration */
                handler->GetDuration(resource.m_Duration);

                /* protection */
                handler->GetProtection(resource.m_Protection);
            }
		}

		object->m_ObjectClass.type = PLT_MediaItem::GetUPnPClass(filepath, context);

		resource.m_Uri = BuildResourceUri(m_FileBaseUri, ip.ToString(), url);
		object->m_Resources.Add(resource);

	} else {
        object = new PLT_MediaContainer;

        /* Assign a title for this container */
        if ((filepath.Compare("/", true) == 0) || (filepath.Compare("\\", true) == 0)) {
            object->m_Title = "Root";
        } else {
			filepath.TrimRight("/\\");
            object->m_Title = NPT_FilePath::BaseName(filepath, keep_extension_in_title);
            if (object->m_Title.GetLength() == 0) goto failure;
        }

#ifndef _WIN32_WCE
		/* Get the number of children for this container */
        NPT_Cardinal count = 0;
        if (with_count && NPT_SUCCEEDED(NPT_File::GetCount(__filepath, count))) {
            ((PLT_MediaContainer*)object)->m_ChildrenCount = count;
        }
#endif	//_WIN32_WCE

        object->m_ObjectClass.type = "object.container";
    }

    /* is it the root? */
    if ((filepath.Compare("/", true) == 0) || (filepath.Compare("\\", true) == 0)) {
        object->m_ParentID = "-1";
        object->m_ObjectID = "0";
    } else {
        NPT_String directory = NPT_FilePath::DirName(filepath);
        /* is the parent path the root? */
		if ((directory.Compare("/", true) == 0) || (directory.Compare("\\", true) == 0)) {
            object->m_ParentID = "0";
        } else {
            object->m_ParentID = "0" + directory;
        }
        object->m_ObjectID = "0" + filepath;
    }
    return object;

failure:
    delete object;
    return NULL;
}


NPT_String GPAC_FileMediaServer::GetResourceURI(const char *url, const char *for_host)
{
	char *abs_url;
	NPT_String res = url, path;

	abs_url = NULL;
	for (u32 i=0; i<m_Directories.GetItemCount(); i++) {
		GPAC_MediaDirectory *dir;
		m_Directories.Get(i, dir);
		abs_url = gf_url_concatenate(dir->m_Path, url);
		FILE *f = gf_f64_open(abs_url, "rt");
		if (f) {
			fclose(f);
			break;
		}
		gf_free((void*)abs_url);
		abs_url = NULL;
	}
	if (!abs_url ) return res;

	/*url was absolute, add its root directory*/
	if (!strcmp(abs_url, url)) {
		Bool found = 0;
		NPT_String newdir;
		/*if the path is /my/example/path/test.ext, we want to share the parent directory
			/my/example/, otherwise we will loose the ability to browse for resource in the parent dir
		*/
		u32 nb_sep=0;
		u32 len = strlen(abs_url);
		u32 i=0;
		while (i<=len) {
			if ((abs_url[len-i]=='\\') || (abs_url[len-i]=='/') ) {
				nb_sep++;
				if (nb_sep==2) break;
			}
			i++;
		}
		/*if no parent directory, we don't allow sharing of the resource*/
		if (nb_sep!=2) return "";
		
		char sep = abs_url[len-i];
		abs_url[len-i] = 0;
		newdir = abs_url;
		abs_url[len-i] = sep;

		newdir.Replace('/', NPT_FilePath::Separator);
		newdir += NPT_FilePath::Separator;

		for (u32 i=0; i<m_Directories.GetItemCount(); i++) {
			GPAC_MediaDirectory *dir;
			m_Directories.Get(i, dir);
			if (!strcmp(newdir, dir->m_Path)) {
				found = 1;
			}
		}
		if (!found)
			AddSharedDirectory(newdir, NULL, 1);
	}

	path = abs_url;
	/*replace all '/' with neptune file path separator otherwise file functions get screwed up ...*/
	path.Replace('/', NPT_FilePath::Separator);

	gf_free((void*)abs_url);

	PLT_MediaObject*mobj = BuildFromFilePathAndHost(path, NULL, true, false, for_host);
	if (mobj) {
		res = mobj->m_Resources[0].m_Uri;
		delete mobj;
	}
	return res;
}

void GPAC_FileMediaServer::ShareVirtualResource(const char *res_uri, const char *res_val, const char *res_mime, Bool temporary)
{
	NPT_String the_uri;
	char *uri = (char *)res_uri;
	char *sep = (char *) strstr(res_uri, "://");
	if (sep) {
		sep = strchr(sep+3, '/');
		if (sep) uri = sep;
		else uri = (char*)"/";
	}
	the_uri = "";
	while (1) {
		sep = strchr(uri, '%');
		if (!sep) break;

		if (!strnicmp(sep, "%3f", 3) || !strnicmp(sep, "%5c", 3) || !strnicmp(sep, "%2f", 3)) {
			sep[0]=0;
			the_uri += uri;
			sep[0]='%';

			if (!strnicmp(sep, "%3f", 3))
				the_uri += "?";
			else if (!strnicmp(sep, "%5c", 3))
				the_uri += "/";
			else 
				the_uri += " ";
			uri= sep+3;
			continue;
		} else {
			the_uri += "%";
			uri= sep+1;
		}
	}
	the_uri += uri;
	the_uri.Replace('/', NPT_FilePath::Separator);

	GPAC_VirtualFile vres(the_uri, res_val, res_mime, temporary);

	m_VirtualFiles.Add(vres);
	GF_LOG(GF_LOG_DEBUG, GF_LOG_NETWORK, ("[UPnP] sharing virtual file %s as %s\n", res_uri, (const char *)the_uri));
}

NPT_Result 
GPAC_FileMediaServer::ServeVirtualFile(NPT_HttpResponse& response,
                          GPAC_VirtualFile  *vfile, 
                          NPT_Position      start,
                          NPT_Position      end,
                          bool              request_is_head) 
{
    NPT_LargeSize            total_len;
    NPT_Result               result;

	NPT_MemoryStream* memory_stream = new NPT_MemoryStream(vfile->m_Content.GetChars(), vfile->m_Content.GetLength() );
    NPT_InputStreamReference stream(memory_stream);

	if (NPT_FAILED(result = stream->GetSize(total_len)) ) {
        response.SetStatus(404, "File Not Found");
        return NPT_SUCCESS;
	}

	GF_LOG(GF_LOG_DEBUG, GF_LOG_NETWORK, ("[UPnP] Serving virtual file %s\n", vfile->m_Content.GetChars()));

	NPT_HttpEntity* entity = new NPT_HttpEntity();
	entity->SetContentLength(total_len);
	response.SetEntity(entity);
	entity->SetContentType(vfile->m_MIME);

    // request is HEAD, returns without setting a body
    if (request_is_head) return NPT_SUCCESS;

    // see if it was a byte range request
    if (start != (NPT_Position)-1 || end != (NPT_Position)-1) {
        // we can only support a range from an offset to the end of the resource for now
        // due to the fact we can't limit how much to read from a stream yet
        NPT_Position start_offset = (NPT_Position)-1, end_offset = total_len - 1, len;
        if (start == (NPT_Position)-1 && end != (NPT_Position)-1) {
            // we are asked for the last N=end bytes
            // adjust according to total length
            if (end >= total_len) {
                start_offset = 0;
            } else {
                start_offset = total_len-end;
            }
        } else if (start != (NPT_Position)-1) {
            start_offset = start;
            // if the end is specified but incorrect
            // set the end_offset in order to generate a bad response
            if (end != (NPT_Position)-1 && end < start) {
                end_offset = (NPT_Position)-1;
            }
        }

        // in case the range request was invalid or we can't seek then respond appropriately
        if (start_offset == (NPT_Position)-1 || end_offset == (NPT_Position)-1 || 
            start_offset > end_offset || NPT_FAILED(stream->Seek(start_offset))) {
            response.SetStatus(416, "Requested range not satisfiable");
        } else {
            len = end_offset - start_offset + 1;
            response.SetStatus(206, "Partial Content");
            PLT_HttpHelper::SetContentRange(response, start_offset, end_offset, total_len);

            entity->SetInputStream(stream);
            entity->SetContentLength(len);
        }
    } else {
        entity->SetInputStream(stream);
    }
    return NPT_SUCCESS;
}

