/*
 * 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		       *
 ******************************************************************************/

void HalUtlActionOnMixer(afb_req_t request, enum ActionOnMixerType actionType)
{
	int idx, count;

	char *apiToCall, *returnedError = NULL, *returnedInfo = NULL;

	afb_api_t apiHandle;
	CtlConfigT *ctrlConfig;

	struct HalData *currentHalData;
	struct InternalHalMixerData *currentMixerData = NULL;

	json_object *requestJson, *responseJ = NULL, *toReturnJ = NULL;

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

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

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

	if(! (requestJson = afb_req_json(request))) {
		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)
		json_object_object_add(requestJson, "verbose", json_object_new_boolean(1));

	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;
	}

	currentMixerData = (struct InternalHalMixerData *) afb_req_get_vcbdata(request);
	if(! currentMixerData) {
		afb_req_fail(request, "hal_call_data", "Can't get current call data");
		return;
	}

	switch(actionType) {
		case ACTION_ON_MIXER_STREAM:
			count = 1;
			break;

		case ACTION_ON_MIXER_PLAYBACK:
		case ACTION_ON_MIXER_CAPTURE:
		case ACTION_ON_MIXER_ALL_STREAM:
			count = (int) HalUtlGetNumberOfMixerDataInList(&currentMixerData);
			toReturnJ = json_object_new_object();
			break;

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

	for(idx = 0; idx < count; idx++) {
		if(afb_api_call_sync(apiHandle,
				     apiToCall,
				     currentMixerData->verbToCall,
				     json_object_get(requestJson),
				     &responseJ,
				     &returnedError,
				     &returnedInfo)) {
			if(responseJ)
				json_object_put(responseJ);
			if(toReturnJ)
				json_object_put(toReturnJ);
			afb_req_fail_f(request,
				       "mixer_call",
				       "Something went wrong during call to verb '%s' of api '%s' with error '%s' and info '%s' (call to mixer %i out of %i)",
				       currentMixerData->verbToCall,
				       apiToCall,
				       returnedError ? returnedError : "not returned",
				       returnedInfo ? returnedInfo : "not returned",
				       idx,
				       count);
			free(returnedError);
			free(returnedInfo);
			return;
		}

		if(! responseJ) {
			if(toReturnJ)
				json_object_put(toReturnJ);
			afb_req_fail_f(request,
				       "mixer_call",
				       "Seems that %s call to api %s succeed but no response was returned (call to mixer %i out of %i)",
				       currentMixerData->verbToCall,
				       apiToCall,
				       idx,
				       count);
			free(returnedError);
			free(returnedInfo);
			return;
		}

		// TBD JAI : When mixer events will be available, use them instead of generating events at calls
		if((actionType == ACTION_ON_MIXER_STREAM ||
		    actionType == ACTION_ON_MIXER_ALL_STREAM) &&
		   ((! 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);
		}

		switch(actionType) {
			case ACTION_ON_MIXER_STREAM:
				toReturnJ = responseJ;
				break;

			case ACTION_ON_MIXER_PLAYBACK:
			case ACTION_ON_MIXER_CAPTURE:
				json_object_object_add(toReturnJ, currentMixerData->verbToCall, responseJ);
				currentMixerData = currentMixerData->next;
				break;

			case ACTION_ON_MIXER_ALL_STREAM:
				json_object_object_add(toReturnJ, currentMixerData->verb, responseJ);
				currentMixerData = currentMixerData->next;
				break;

			default:
				break;
		}
	}

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

		case ACTION_ON_MIXER_PLAYBACK:
			afb_req_success(request,
					toReturnJ,
					"Actions correctly transferred to all playbacks without any error raised");
			break;

		case ACTION_ON_MIXER_CAPTURE:
			afb_req_success(request,
					toReturnJ,
					"Actions correctly transferred to all captures without any error raised");
			break;

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

		default:
			break;
	}
}

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

void HalUtlActionOnPlayback(afb_req_t request)
{
	HalUtlActionOnMixer(request, ACTION_ON_MIXER_PLAYBACK);
}

void HalUtlActionOnCapture(afb_req_t request)
{
	HalUtlActionOnMixer(request, ACTION_ON_MIXER_CAPTURE);
}

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)
{
	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->streamsData);
	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->streamsData, createdStreamData);
		return NULL;
	}

	if(! (createdStreamData->event = afb_api_make_event(apiHandle, createdStreamData->verb))) {
		HalUtlRemoveSelectedMixerData(&currentHalData->internalHalData->streamsData, 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->streamsData, createdStreamData);
		return NULL;
	}

	wrap_json_pack(&streamAddedEventJ,
		       "{s:s, s:s, s:s}",
		       "action", "added",
		       "name", createdStreamData->verb,
		       "cardId", createdStreamData->streamCardId);

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

	return createdStreamData;
}

int8_t HalUtlRemoveStreamDataAndDeleteStreamVerb(afb_api_t apiHandle,
						 char *verb,
						 char *verbToCall,
						 char *streamCardId)
{
	int8_t returnedErr = 0;

	json_object *streamRemovedEventJ;

	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->streamsData,
							       verb,
							       verbToCall,
							       streamCardId);
	if(! toRemoveStreamData)
		return -4;

	wrap_json_pack(&streamRemovedEventJ,
		       "{s:s, s:s, s:s}",
		       "action", "removed",
		       "name", toRemoveStreamData->verb,
		       "cardId", toRemoveStreamData->streamCardId);

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

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

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

	return 0;
}