/*
 * Copyright (C) 2019 "IoT.bzh"
 * Author Jonathan Aillet <jonathan.aillet@iot.bzh>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#define _GNU_SOURCE

#include <stdio.h>
#include <json-c/json.h>

#include <afb/afb-binding.h>

#include <ctl-config.h>

#include "4a-hal-utilities-data.h"
#include "4a-hal-utilities-hal-streams-handler.h"

/*******************************************************************************
 *		Actions to be call when a stream verb is called		       *
 ******************************************************************************/

json_object *HalUtlCallSingleStream(afb_req_t request,
				    afb_api_t apiHandle,
				    CtlConfigT *ctrlConfig,
				    struct HalData *currentHalData,
				    char *apiToCall,
				    struct InternalHalMixerData *currentMixerData,
				    json_object *requestJson)
{
	char *returnedError = NULL, *returnedInfo = NULL;

	json_object *responseJ = NULL;

	if(afb_api_call_sync(apiHandle,
			     apiToCall,
			     currentMixerData->verbToCall,
			     json_object_get(requestJson),
			     &responseJ,
			     &returnedError,
			     &returnedInfo)) {
		if(responseJ)
			json_object_put(responseJ);
		AFB_API_ERROR(apiHandle,
			      "Something went wrong during call to verb '%s' of api '%s' "
			      "with error '%s' and info '%s' (stream '%s')",
			      currentMixerData->verbToCall,
			      apiToCall,
			      returnedError ? returnedError : "not returned",
			      returnedInfo ? returnedInfo : "not returned",
			      currentMixerData->verb);
		free(returnedError);
		free(returnedInfo);
		return NULL;
	}

	if(! responseJ) {
		AFB_API_ERROR(apiHandle,
			      "Seems that %s call to api %s succeed but no response was returned (stream '%s')",
			      currentMixerData->verbToCall,
			      apiToCall,
			      currentMixerData->verb);
		free(returnedError);
		free(returnedInfo);
		return NULL;
	}

	// TBD JAI : When mixer events will be available, use them instead of generating events at calls
	if(! currentMixerData->event ||
	   (afb_event_push(currentMixerData->event, json_object_get(responseJ)) < 0)) {
		AFB_API_ERROR(apiHandle, "Could not generate an event for stream %s", currentMixerData->verb);
	}

	return responseJ;
}

json_object *HalUtlCallAllStream(afb_req_t request,
				 afb_api_t apiHandle,
				 CtlConfigT *ctrlConfig,
				 struct HalData *currentHalData,
				 char *apiToCall,
				 struct cds_list_head *streamsDataListHead,
				 json_object *requestJson)
{
	struct InternalHalMixerData *currentMixerData;

	json_object *responseJ = NULL, *toReturnJ;

	toReturnJ = json_object_new_object();
	if(! toReturnJ) {
		AFB_API_ERROR(apiHandle, "Didn't succeed to allocate response json object");
		return NULL;
	}

	cds_list_for_each_entry(currentMixerData, streamsDataListHead, node) {
		responseJ = HalUtlCallSingleStream(request,
						   apiHandle,
						   ctrlConfig,
						   currentHalData,
						   apiToCall,
						   currentMixerData,
						   requestJson);
		if(! responseJ) {
			AFB_API_ERROR(apiHandle,
				      "Seems that an error happened during %s call to api %s (stream '%s')",
				      currentMixerData->verbToCall,
				      apiToCall,
				      currentMixerData->verb);
			json_object_put(toReturnJ);
			return NULL;
		}

		json_object_object_add(toReturnJ, currentMixerData->verb, responseJ);
	}

	return toReturnJ;
}

void HalUtlActionOnMixer(afb_req_t request, enum ActionOnMixerType actionType)
{
	char *apiToCall;

	struct cds_list_head *streamsDataListHead = NULL;

	afb_api_t apiHandle;
	CtlConfigT *ctrlConfig;

	struct HalData *currentHalData;

	struct InternalHalMixerData *currentMixerData = NULL;

	json_object *requestJson, *verboseBooleanJ, *toReturnJ;

	apiHandle = afb_req_get_api(request);
	if(! apiHandle) {
		afb_req_fail(request, "api_handle", "Can't get current hal api handle");
		return;
	}

	ctrlConfig = (CtlConfigT *) afb_api_get_userdata(apiHandle);
	if(! ctrlConfig) {
		afb_req_fail(request, "hal_controller_config", "Can't get current internal hal controller config");
		return;
	}

	currentHalData = (struct HalData *) getExternalData(ctrlConfig);
	if(! currentHalData) {
		afb_req_fail(request, "hal_controller_data", "Can't get current internal hal controller data");
		return;
	}

	requestJson = afb_req_json(request);
	if(! requestJson) {
		afb_req_fail(request, "request_json", "Can't get request json");
		return;
	}

	if(json_object_is_type(requestJson, json_type_object) && json_object_get_object(requestJson)->count > 0) {
		verboseBooleanJ = json_object_new_boolean(1);
		if(! verboseBooleanJ) {
			afb_req_fail(request,
				     "verbose_boolean_allocation",
				     "Didn't succeed to allocate verbose value json boolean");
			return;
		}

		json_object_object_add(requestJson, "verbose", verboseBooleanJ);
	}

	apiToCall = currentHalData->internalHalData->mixerApiName;
	if(! apiToCall) {
		afb_req_fail(request, "mixer_api", "Can't get mixer api");
		return;
	}

	if(currentHalData->status != HAL_STATUS_READY) {
		afb_req_fail(request, "hal_not_ready", "Seems that hal is not ready");
		return;
	}

	switch(actionType) {
		case ACTION_ON_MIXER_STREAM:
			currentMixerData = (struct InternalHalMixerData *) afb_req_get_vcbdata(request);
			if(! currentMixerData) {
				afb_req_fail(request, "hal_call_data", "Can't get stream to call data");
				return;
			}

			toReturnJ = HalUtlCallSingleStream(request,
							   apiHandle,
							   ctrlConfig,
							   currentHalData,
							   apiToCall,
							   currentMixerData,
							   requestJson);
			if(! toReturnJ) {
				afb_req_fail_f(request,
					       "mixer_call",
					       "Seems that an error happened during %s call to api %s (stream '%s')",
					       currentMixerData->verbToCall,
					       apiToCall,
					       currentMixerData->verb);
				return;
			}

			afb_req_success_f(request,
					  toReturnJ,
					  "Action %s correctly transferred to %s without any error raised",
					  currentMixerData->verbToCall,
					  apiToCall);
			break;

		case ACTION_ON_MIXER_ALL_STREAM:
			streamsDataListHead = (struct cds_list_head *) afb_req_get_vcbdata(request);
			if(! streamsDataListHead) {
				afb_req_fail(request, "hal_call_data", "Can't get stream list data");
				return;
			}

			toReturnJ = HalUtlCallAllStream(request,
							apiHandle,
							ctrlConfig,
							currentHalData,
							apiToCall,
							streamsDataListHead,
							requestJson);
			if(! toReturnJ) {
				afb_req_fail_f(request,
					       "mixer_call",
					       "Seems that an error happened during call to all streams using api %s",
					       apiToCall);
				return;
			}

			afb_req_success(request,
					toReturnJ,
					"Actions correctly transferred to all streams without any error raised");
			break;

		default:
			afb_req_fail(request, "mixer_action_type", "Action type is unknown");
			return;
	}
}

void HalUtlActionOnStream(afb_req_t request)
{
	HalUtlActionOnMixer(request, ACTION_ON_MIXER_STREAM);
}

void HalUtlActionOnAllStream(afb_req_t request)
{
	HalUtlActionOnMixer(request, ACTION_ON_MIXER_ALL_STREAM);
}

/*******************************************************************************
 *		Add stream data and verb function			       *
 ******************************************************************************/

struct InternalHalMixerData *HalUtlAddStreamDataAndCreateStreamVerb(afb_api_t apiHandle,
								    char *verb,
								    char *verbToCall,
								    char *streamCardId)
{
	int wrapRet;

	json_object *streamAddedEventJ;

	CtlConfigT *ctrlConfig;

	struct HalData *currentHalData;
	struct InternalHalMixerData *createdStreamData;

	if(! apiHandle || ! verb || ! verbToCall || ! streamCardId)
		return NULL;

	ctrlConfig = (CtlConfigT *) afb_api_get_userdata(apiHandle);
	if(! ctrlConfig)
		return NULL;

	currentHalData = (struct HalData *) getExternalData(ctrlConfig);
	if(! currentHalData ||
	   ! currentHalData->internalHalData)
		return NULL;

	createdStreamData = HalUtlAddMixerDataToMixerDataList(&currentHalData->internalHalData->streamsDataListHead);
	if(! createdStreamData)
		return NULL;

	createdStreamData->verb = strdup(verb);
	createdStreamData->verbToCall = strdup(verbToCall);
	createdStreamData->streamCardId = strdup(streamCardId);

	if(! createdStreamData->verb ||
	   ! createdStreamData->verbToCall ||
	   ! createdStreamData->streamCardId) {
		HalUtlRemoveSelectedMixerData(&currentHalData->internalHalData->streamsDataListHead, createdStreamData);
		return NULL;
	}

	createdStreamData->event = afb_api_make_event(apiHandle, createdStreamData->verb);
	if(! createdStreamData->event) {
		HalUtlRemoveSelectedMixerData(&currentHalData->internalHalData->streamsDataListHead, createdStreamData);
		return NULL;
	}

	if(afb_api_add_verb(apiHandle,
			    createdStreamData->verb,
			    "Stream action transferred to mixer",
			    HalUtlActionOnStream,
			    (void *) createdStreamData,
			    NULL,
			    0,
			    0)) {
		AFB_API_ERROR(apiHandle,"Error while creating verb for stream : '%s'", createdStreamData->verb);
		HalUtlRemoveSelectedMixerData(&currentHalData->internalHalData->streamsDataListHead, createdStreamData);
		return NULL;
	}

	wrapRet = wrap_json_pack(&streamAddedEventJ,
				 "{s:s, s:s, s:s}",
				 "action", "added",
				 "name", createdStreamData->verb,
				 "cardId", createdStreamData->streamCardId);
	if(wrapRet) {
		AFB_API_ERROR(apiHandle,"Didn't succeed to allocate added stream event json object");
		HalUtlRemoveSelectedMixerData(&currentHalData->internalHalData->streamsDataListHead, createdStreamData);
		return NULL;
	}

	afb_event_push(currentHalData->internalHalData->streamUpdates, streamAddedEventJ);

	return createdStreamData;
}

int HalUtlRemoveStreamDataAndDeleteStreamVerbUsingMixerData(afb_api_t apiHandle,
							    struct InternalHalMixerData *toRemoveStreamData)
{
	int returnedErr, wrapRet;

	json_object *streamRemovedEventJ;

	CtlConfigT *ctrlConfig;

	struct HalData *currentHalData;

	if(! apiHandle || ! toRemoveStreamData)
		return -1;

	ctrlConfig = (CtlConfigT *) afb_api_get_userdata(apiHandle);
	if(! ctrlConfig)
		return -2;

	currentHalData = (struct HalData *) getExternalData(ctrlConfig);
	if((! currentHalData) ||
	   (! currentHalData->internalHalData) ||
	   (! currentHalData->internalHalData->streamUpdates))
		return -3;

	if(afb_api_del_verb(apiHandle, toRemoveStreamData->verb, NULL)) {
		AFB_API_ERROR(apiHandle,"Error while deleting verb for stream : '%s'", toRemoveStreamData->verb);
		return -4;
	}

	wrapRet = wrap_json_pack(&streamRemovedEventJ,
				 "{s:s, s:s, s:s}",
				 "action", "removed",
				 "name", toRemoveStreamData->verb,
				 "cardId", toRemoveStreamData->streamCardId);
	if(wrapRet) {
		AFB_API_ERROR(apiHandle,"Didn't succeed to allocate removed stream event json object");
		return -5;
	}

	returnedErr = HalUtlRemoveSelectedMixerData(&currentHalData->internalHalData->streamsDataListHead, toRemoveStreamData);
	if(returnedErr) {
		AFB_API_ERROR(apiHandle,
			      "Error %i while removing data for stream",
			      returnedErr);
		json_object_put(streamRemovedEventJ);
		return -6;
	}

	afb_event_push(currentHalData->internalHalData->streamUpdates, streamRemovedEventJ);

	return 0;
}

int HalUtlRemoveStreamDataAndDeleteStreamVerb(afb_api_t apiHandle,
					      char *verb,
					      char *verbToCall,
					      char *streamCardId)
{
	int returnedErr;

	CtlConfigT *ctrlConfig;

	struct HalData *currentHalData;
	struct InternalHalMixerData *toRemoveStreamData;

	if(! apiHandle || ! verb || ! verbToCall || ! streamCardId)
		return -1;

	ctrlConfig = (CtlConfigT *) afb_api_get_userdata(apiHandle);
	if(! ctrlConfig)
		return -2;

	currentHalData = (struct HalData *) getExternalData(ctrlConfig);
	if(! currentHalData ||
	   ! currentHalData->internalHalData)
		return -3;

	toRemoveStreamData = HalUtlSearchMixerDataByProperties(&currentHalData->internalHalData->streamsDataListHead,
							       verb,
							       verbToCall,
							       streamCardId);
	if(! toRemoveStreamData)
		return -4;

	returnedErr = HalUtlRemoveStreamDataAndDeleteStreamVerbUsingMixerData(apiHandle,
									      toRemoveStreamData);
	if(returnedErr) {
		AFB_API_ERROR(apiHandle,
			      "Error %i while removing verb and data for stream : '%s'",
			      returnedErr,
			      verb);
		return -5;
	}

	return 0;
}

int HalUtlRemoveAllStreamsDataAndDeleteAllStreamsVerb(afb_api_t apiHandle)
{
	CtlConfigT *ctrlConfig;

	struct HalData *currentHalData;

	struct InternalHalMixerData *currentMixerData, *savedMixerData;

	if(! apiHandle)
		return -1;

	ctrlConfig = (CtlConfigT *) afb_api_get_userdata(apiHandle);
	if(! ctrlConfig)
		return -2;

	currentHalData = (struct HalData *) getExternalData(ctrlConfig);
	if((! currentHalData) ||
	   (! currentHalData->internalHalData))
		return -3;

	cds_list_for_each_entry_safe(currentMixerData, savedMixerData, &currentHalData->internalHalData->streamsDataListHead, node) {
		if(HalUtlRemoveStreamDataAndDeleteStreamVerbUsingMixerData(apiHandle, currentMixerData))
			return -4;
	}

	return 0;
}