/*
 * dLeyna
 *
 * Copyright (C) 2012-2017 Intel Corporation. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms and conditions of the GNU Lesser General Public License,
 * version 2.1, as published by the Free Software Foundation.
 *
 * This program is distributed in the hope 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 program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Mark Ryan <mark.d.ryan@intel.com>
 *
 */

#include <libdleyna/core/error.h>
#include <libdleyna/core/log.h>

#include "async.h"
#include "path.h"

static void prv_delete(dls_task_t *task)
{
	if (!task->synchronous)
		dls_async_task_delete((dls_async_task_t *)task);

	switch (task->type) {
	case DLS_TASK_GET_CHILDREN:
		if (task->ut.get_children.filter)
			g_variant_unref(task->ut.get_children.filter);
		g_free(task->ut.get_children.sort_by);
		break;
	case DLS_TASK_MANAGER_GET_ALL_PROPS:
	case DLS_TASK_GET_ALL_PROPS:
		g_free(task->ut.get_props.interface_name);
		break;
	case DLS_TASK_MANAGER_GET_PROP:
	case DLS_TASK_GET_PROP:
		g_free(task->ut.get_prop.interface_name);
		g_free(task->ut.get_prop.prop_name);
		break;
	case DLS_TASK_MANAGER_SET_PROP:
		g_free(task->ut.set_prop.interface_name);
		g_free(task->ut.set_prop.prop_name);
		g_variant_unref(task->ut.set_prop.params);
		break;
	case DLS_TASK_SEARCH:
		g_free(task->ut.search.query);
		if (task->ut.search.filter)
			g_variant_unref(task->ut.search.filter);
		g_free(task->ut.search.sort_by);
		break;
	case DLS_TASK_BROWSE_OBJECTS:
		if (task->ut.browse_objects.objects)
			g_variant_unref(task->ut.browse_objects.objects);
		if (task->ut.browse_objects.filter)
			g_variant_unref(task->ut.browse_objects.filter);
		break;
	case DLS_TASK_GET_RESOURCE:
		if (task->ut.resource.filter)
			g_variant_unref(task->ut.resource.filter);
		g_free(task->ut.resource.protocol_info);
		break;
	case DLS_TASK_SET_PROTOCOL_INFO:
		if (task->ut.protocol_info.protocol_info)
			g_free(task->ut.protocol_info.protocol_info);
		break;
	case DLS_TASK_UPLOAD_TO_ANY:
	case DLS_TASK_UPLOAD:
		g_free(task->ut.upload.display_name);
		g_free(task->ut.upload.file_path);
		break;
	case DLS_TASK_CREATE_CONTAINER:
	case DLS_TASK_CREATE_CONTAINER_IN_ANY:
		g_free(task->ut.create_container.display_name);
		g_free(task->ut.create_container.type);
		if (task->ut.create_container.child_types)
			g_variant_unref(task->ut.create_container.child_types);
		break;
	case DLS_TASK_UPDATE_OBJECT:
		if (task->ut.update.to_add_update)
			g_variant_unref(task->ut.update.to_add_update);
		if (task->ut.update.to_delete)
			g_variant_unref(task->ut.update.to_delete);
		break;
	case DLS_TASK_CREATE_REFERENCE:
		g_free(task->ut.create_reference.item_path);
		break;
	case DLS_TASK_GET_ICON:
		g_free(task->ut.get_icon.resolution);
		g_free(task->ut.get_icon.mime_type);
		break;
	case DLS_TASK_WAKE:
		break;
	default:
		break;
	}

	g_free(task->target.path);
	g_free(task->target.root_path);
	g_free(task->target.id);

	if (task->result)
		g_variant_unref(task->result);

	g_free(task);
}

dls_task_t *dls_task_rescan_new(dleyna_connector_msg_id_t invocation)
{
	dls_task_t *task = g_new0(dls_task_t, 1);

	task->type = DLS_TASK_RESCAN;
	task->invocation = invocation;
	task->synchronous = TRUE;

	return task;
}

dls_task_t *dls_task_get_version_new(dleyna_connector_msg_id_t invocation)
{
	dls_task_t *task = g_new0(dls_task_t, 1);

	task->type = DLS_TASK_GET_VERSION;
	task->invocation = invocation;
	task->result_format = "(@s)";
	task->synchronous = TRUE;

	return task;
}

dls_task_t *dls_task_get_servers_new(dleyna_connector_msg_id_t invocation)
{
	dls_task_t *task = g_new0(dls_task_t, 1);

	task->type = DLS_TASK_GET_SERVERS;
	task->invocation = invocation;
	task->result_format = "(@ao)";
	task->synchronous = TRUE;

	return task;
}

dls_task_t *dls_task_manager_get_prop_new(dleyna_connector_msg_id_t invocation,
					  const gchar *path,
					  GVariant *parameters,
					  GError **error)
{
	dls_task_t *task = (dls_task_t *)g_new0(dls_async_task_t, 1);

	g_variant_get(parameters, "(ss)", &task->ut.get_prop.interface_name,
		      &task->ut.get_prop.prop_name);
	g_strstrip(task->ut.get_prop.interface_name);
	g_strstrip(task->ut.get_prop.prop_name);

	task->target.path = g_strstrip(g_strdup(path));

	task->type = DLS_TASK_MANAGER_GET_PROP;
	task->invocation = invocation;
	task->result_format = "(v)";

	return task;
}

dls_task_t *dls_task_manager_get_props_new(dleyna_connector_msg_id_t invocation,
					   const gchar *path,
					   GVariant *parameters,
					   GError **error)
{
	dls_task_t *task = (dls_task_t *)g_new0(dls_async_task_t, 1);

	g_variant_get(parameters, "(s)", &task->ut.get_props.interface_name);
	g_strstrip(task->ut.get_props.interface_name);

	task->target.path = g_strstrip(g_strdup(path));

	task->type = DLS_TASK_MANAGER_GET_ALL_PROPS;
	task->invocation = invocation;
	task->result_format = "(@a{sv})";

	return task;
}

dls_task_t *dls_task_manager_set_prop_new(dleyna_connector_msg_id_t invocation,
					  const gchar *path,
					  GVariant *parameters,
					  GError **error)
{
	dls_task_t *task = (dls_task_t *)g_new0(dls_async_task_t, 1);

	g_variant_get(parameters, "(ssv)",
		      &task->ut.set_prop.interface_name,
		      &task->ut.set_prop.prop_name,
		      &task->ut.set_prop.params);

	g_strstrip(task->ut.set_prop.interface_name);
	g_strstrip(task->ut.set_prop.prop_name);

	task->target.path = g_strstrip(g_strdup(path));

	task->type = DLS_TASK_MANAGER_SET_PROP;
	task->invocation = invocation;

	return task;
}

static gboolean prv_set_task_target_info(dls_task_t *task, const gchar *path,
					 GError **error)
{
	task->target.path = g_strdup(path);
	g_strstrip(task->target.path);

	return dls_server_get_object_info(path, &task->target.root_path,
					       &task->target.id,
					       &task->target.device, error);
}

static gboolean prv_is_task_allowed(dls_task_t *task, GError **error)
{
	if (dls_server_is_device_sleeping(task->target.device)) {
		if (task->type != DLS_TASK_WAKE &&
		    task->type != DLS_TASK_GET_PROP)
			goto on_error;
	}

	return TRUE;

on_error:
	*error = g_error_new(DLEYNA_SERVER_ERROR,
			     DLEYNA_ERROR_OPERATION_FAILED,
			     "Target device is sleeping");

	return FALSE;
}

static dls_task_t *prv_m2spec_task_new(dls_task_type_t type,
				       dleyna_connector_msg_id_t invocation,
				       const gchar *path,
				       const gchar *result_format,
				       GError **error,
				       gboolean synchronous)
{
	dls_task_t *task;

	if (synchronous) {
		task = g_new0(dls_task_t, 1);
		task->synchronous = TRUE;
	} else {
		task = (dls_task_t *)g_new0(dls_async_task_t, 1);
	}

	task->type = type;

	if (!prv_set_task_target_info(task, path, error) ||
	    !prv_is_task_allowed(task, error)) {
		prv_delete(task);
		task = NULL;

		goto finished;
	}

	task->invocation = invocation;
	task->result_format = result_format;

finished:

	return task;
}

dls_task_t *dls_task_get_children_new(dleyna_connector_msg_id_t invocation,
				      const gchar *path, GVariant *parameters,
				      gboolean items, gboolean containers,
				      GError **error)
{
	dls_task_t *task;

	task = prv_m2spec_task_new(DLS_TASK_GET_CHILDREN, invocation, path,
				   "(@aa{sv})", error, FALSE);
	if (!task)
		goto finished;

	task->ut.get_children.containers = containers;
	task->ut.get_children.items = items;

	g_variant_get(parameters, "(uu@as)",
		      &task->ut.get_children.start,
		      &task->ut.get_children.count,
		      &task->ut.get_children.filter);

	task->ut.get_children.sort_by = g_strdup("");

finished:

	return task;
}

dls_task_t *dls_task_get_children_ex_new(dleyna_connector_msg_id_t invocation,
					 const gchar *path,
					 GVariant *parameters, gboolean items,
					 gboolean containers,
					 GError **error)
{
	dls_task_t *task;

	task = prv_m2spec_task_new(DLS_TASK_GET_CHILDREN, invocation, path,
				   "(@aa{sv})", error, FALSE);
	if (!task)
		goto finished;

	task->ut.get_children.containers = containers;
	task->ut.get_children.items = items;

	g_variant_get(parameters, "(uu@ass)",
		      &task->ut.get_children.start,
		      &task->ut.get_children.count,
		      &task->ut.get_children.filter,
		      &task->ut.get_children.sort_by);

finished:

	return task;
}

dls_task_t *dls_task_get_prop_new(dleyna_connector_msg_id_t invocation,
				  const gchar *path, GVariant *parameters,
				  GError **error)
{
	dls_task_t *task;

	task = prv_m2spec_task_new(DLS_TASK_GET_PROP, invocation, path, "(v)",
				   error, FALSE);
	if (!task)
		goto finished;

	g_variant_get(parameters, "(ss)", &task->ut.get_prop.interface_name,
		      &task->ut.get_prop.prop_name);

	g_strstrip(task->ut.get_prop.interface_name);
	g_strstrip(task->ut.get_prop.prop_name);

finished:

	return task;
}

dls_task_t *dls_task_get_props_new(dleyna_connector_msg_id_t invocation,
				   const gchar *path, GVariant *parameters,
				   GError **error)
{
	dls_task_t *task;

	task = prv_m2spec_task_new(DLS_TASK_GET_ALL_PROPS, invocation, path,
				   "(@a{sv})", error, FALSE);
	if (!task)
		goto finished;

	g_variant_get(parameters, "(s)", &task->ut.get_props.interface_name);
	g_strstrip(task->ut.get_props.interface_name);

finished:

	return task;
}

dls_task_t *dls_task_search_new(dleyna_connector_msg_id_t invocation,
				const gchar *path, GVariant *parameters,
				GError **error)
{
	dls_task_t *task;

	task = prv_m2spec_task_new(DLS_TASK_SEARCH, invocation, path,
				   "(@aa{sv})", error, FALSE);
	if (!task)
		goto finished;

	g_variant_get(parameters, "(suu@as)", &task->ut.search.query,
		      &task->ut.search.start, &task->ut.search.count,
		      &task->ut.search.filter);

	task->ut.search.sort_by = g_strdup("");

finished:
	return task;
}

dls_task_t *dls_task_search_ex_new(dleyna_connector_msg_id_t invocation,
				   const gchar *path, GVariant *parameters,
				   GError **error)
{
	dls_task_t *task;

	task = prv_m2spec_task_new(DLS_TASK_SEARCH, invocation, path,
				   "(@aa{sv}u)", error, FALSE);
	if (!task)
		goto finished;

	g_variant_get(parameters, "(suu@ass)", &task->ut.search.query,
		      &task->ut.search.start, &task->ut.search.count,
		      &task->ut.search.filter, &task->ut.search.sort_by);

	task->multiple_retvals = TRUE;

finished:

	return task;
}

dls_task_t *dls_task_browse_objects_new(dleyna_connector_msg_id_t invocation,
					const gchar *path, GVariant *parameters,
					GError **error)
{
	dls_task_t *task;

	task = prv_m2spec_task_new(DLS_TASK_BROWSE_OBJECTS, invocation, path,
				   "(@aa{sv})", error, FALSE);
	if (!task)
		goto finished;

	g_variant_get(parameters, "(@ao@as)",
		      &task->ut.browse_objects.objects,
		      &task->ut.browse_objects.filter);

finished:

	return task;
}

dls_task_t *dls_task_get_resource_new(dleyna_connector_msg_id_t invocation,
				      const gchar *path, GVariant *parameters,
				      GError **error)
{
	dls_task_t *task;

	task = prv_m2spec_task_new(DLS_TASK_GET_RESOURCE, invocation, path,
				   "(@a{sv})", error, FALSE);
	if (!task)
		goto finished;

	g_variant_get(parameters, "(s@as)",
		      &task->ut.resource.protocol_info,
		      &task->ut.resource.filter);

finished:

	return task;
}

dls_task_t *dls_task_set_protocol_info_new(dleyna_connector_msg_id_t invocation,
					   GVariant *parameters)
{
	dls_task_t *task = g_new0(dls_task_t, 1);

	task->type = DLS_TASK_SET_PROTOCOL_INFO;
	task->invocation = invocation;
	task->synchronous = TRUE;
	g_variant_get(parameters, "(s)", &task->ut.protocol_info.protocol_info);

	return task;
}

static dls_task_t *prv_upload_new_generic(dls_task_type_t type,
					  dleyna_connector_msg_id_t invocation,
					  const gchar *path,
					  GVariant *parameters,
					  GError **error)
{
	dls_task_t *task;

	task = prv_m2spec_task_new(type, invocation, path,
				   "(uo)", error, FALSE);
	if (!task)
		goto finished;

	g_variant_get(parameters, "(ss)", &task->ut.upload.display_name,
		      &task->ut.upload.file_path);
	g_strstrip(task->ut.upload.file_path);
	task->multiple_retvals = TRUE;

finished:

	return task;
}

dls_task_t *dls_task_prefer_local_addresses_new(
					dleyna_connector_msg_id_t invocation,
					GVariant *parameters)
{
	dls_task_t *task = g_new0(dls_task_t, 1);

	task->type = DLS_TASK_SET_PREFER_LOCAL_ADDRESSES;
	task->invocation = invocation;
	task->synchronous = TRUE;
	g_variant_get(parameters, "(b)",
		      &task->ut.prefer_local_addresses.prefer);

	return task;
}

dls_task_t *dls_task_upload_to_any_new(dleyna_connector_msg_id_t invocation,
				       const gchar *path, GVariant *parameters,
				       GError **error)
{
	return prv_upload_new_generic(DLS_TASK_UPLOAD_TO_ANY, invocation,
				      path, parameters, error);
}

dls_task_t *dls_task_upload_new(dleyna_connector_msg_id_t invocation,
				const gchar *path, GVariant *parameters,
				GError **error)
{
	return prv_upload_new_generic(DLS_TASK_UPLOAD, invocation,
				      path, parameters, error);
}

dls_task_t *dls_task_get_upload_status_new(dleyna_connector_msg_id_t invocation,
					   const gchar *path,
					   GVariant *parameters,
					   GError **error)
{
	dls_task_t *task;

	task = prv_m2spec_task_new(DLS_TASK_GET_UPLOAD_STATUS, invocation, path,
				   "(stt)", error, TRUE);
	if (!task)
		goto finished;

	g_variant_get(parameters, "(u)",
		      &task->ut.upload_action.upload_id);
	task->multiple_retvals = TRUE;

finished:

	return task;
}

dls_task_t *dls_task_get_upload_ids_new(dleyna_connector_msg_id_t invocation,
					const gchar *path,
					GError **error)
{
	dls_task_t *task;

	task = prv_m2spec_task_new(DLS_TASK_GET_UPLOAD_IDS, invocation, path,
				   "(@au)", error, TRUE);

	return task;
}

dls_task_t *dls_task_cancel_upload_new(dleyna_connector_msg_id_t invocation,
				       const gchar *path,
				       GVariant *parameters,
				       GError **error)
{
	dls_task_t *task;

	task = prv_m2spec_task_new(DLS_TASK_CANCEL_UPLOAD, invocation, path,
				   NULL, error, TRUE);
	if (!task)
		goto finished;

	g_variant_get(parameters, "(u)",
		      &task->ut.upload_action.upload_id);

finished:

	return task;
}

dls_task_t *dls_task_delete_new(dleyna_connector_msg_id_t invocation,
				const gchar *path,
				GError **error)
{
	dls_task_t *task;

	task = prv_m2spec_task_new(DLS_TASK_DELETE_OBJECT, invocation,
				   path, NULL, error, FALSE);
	return task;
}

dls_task_t *dls_task_create_container_new_generic(
					dleyna_connector_msg_id_t invocation,
					dls_task_type_t type,
					const gchar *path,
					GVariant *parameters,
					GError **error)
{
	dls_task_t *task;

	task = prv_m2spec_task_new(type, invocation, path,
				   "(@o)", error, FALSE);
	if (!task)
		goto finished;

	g_variant_get(parameters, "(ss@as)",
		      &task->ut.create_container.display_name,
		      &task->ut.create_container.type,
		      &task->ut.create_container.child_types);

finished:

	return task;
}

dls_task_t *dls_task_create_reference_new(dleyna_connector_msg_id_t invocation,
					  dls_task_type_t type,
					  const gchar *path,
					  GVariant *parameters,
					  GError **error)
{
	dls_task_t *task;

	task = prv_m2spec_task_new(type, invocation, path,
				   "(@o)", error, FALSE);
	if (!task)
		goto finished;

	g_variant_get(parameters, "(o)", &task->ut.create_reference.item_path);
	(void) g_strstrip(task->ut.create_reference.item_path);

finished:

	return task;
}

dls_task_t *dls_task_update_new(dleyna_connector_msg_id_t invocation,
				const gchar *path, GVariant *parameters,
				GError **error)
{
	dls_task_t *task;

	task = prv_m2spec_task_new(DLS_TASK_UPDATE_OBJECT, invocation, path,
				   NULL, error, FALSE);
	if (!task)
		goto finished;

	g_variant_get(parameters, "(@a{sv}@as)",
		      &task->ut.update.to_add_update,
		      &task->ut.update.to_delete);

finished:

	return task;
}

dls_task_t *dls_task_get_metadata_new(dleyna_connector_msg_id_t invocation,
				const gchar *path, GError **error)
{
	dls_task_t *task;

	task = prv_m2spec_task_new(DLS_TASK_GET_OBJECT_METADATA, invocation,
				   path, "(@s)", error, FALSE);

	return task;
}

dls_task_t *dls_task_get_icon_new(dleyna_connector_msg_id_t invocation,
				  const gchar *path, GVariant *parameters,
				  GError **error)
{
	dls_task_t *task;

	task = prv_m2spec_task_new(DLS_TASK_GET_ICON, invocation, path,
				   "(@ays)", error, FALSE);
	if (!task)
		goto finished;

	task->multiple_retvals = TRUE;

	g_variant_get(parameters, "(ss)", &task->ut.get_icon.mime_type,
		      &task->ut.get_icon.resolution);

finished:

	return task;
}

dls_task_t *dls_task_wake_new(dleyna_connector_msg_id_t invocation,
			      const gchar *path, GError **error)
{
	dls_task_t *task;

	task = prv_m2spec_task_new(DLS_TASK_WAKE, invocation, path,
				   NULL, error, FALSE);

	return task;
}

void dls_task_complete(dls_task_t *task)
{
	GVariant *variant = NULL;

	if (!task)
		goto finished;

	if (task->invocation) {
		if (task->result_format) {
			if (task->multiple_retvals)
				variant = g_variant_ref(task->result);
			else
				variant = g_variant_ref_sink(
					g_variant_new(task->result_format,
						      task->result));
		}

		dls_server_get_connector()->return_response(task->invocation,
							    variant);
		if (variant)
			g_variant_unref(variant);

		task->invocation = NULL;
	}

finished:

	return;
}

void dls_task_fail(dls_task_t *task, GError *error)
{
	if (!task)
		goto finished;

	if (task->invocation) {
		dls_server_get_connector()->return_error(task->invocation,
							 error);
		task->invocation = NULL;
	}

finished:

	return;
}

void dls_task_cancel(dls_task_t *task)
{
	GError *error;

	if (!task)
		goto finished;

	if (task->invocation) {
		error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_CANCELLED,
				    "Operation cancelled.");
		dls_server_get_connector()->return_error(task->invocation,
							 error);
		task->invocation = NULL;
		g_error_free(error);
	}

	if (!task->synchronous)
		dls_async_task_cancel((dls_async_task_t *)task);

finished:

	return;
}

void dls_task_delete(dls_task_t *task)
{
	GError *error;

	if (!task)
		goto finished;

	if (task->invocation) {
		error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_DIED,
				    "Unable to complete command.");
		dls_server_get_connector()->return_error(task->invocation,
							 error);
		g_error_free(error);
	}

	prv_delete(task);

finished:

	return;
}
