/*
 * Copyright (C) 2018 "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 <string.h>

#include <wrap-json.h>

#include <afb/afb-binding.h>

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

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

#include "../4a-hal-manager/4a-hal-manager.h"

#include "4a-hal-controllers-mixer-link.h"
#include "4a-hal-controllers-cb.h"

/*******************************************************************************
 *		HAL controllers handle mixer calls functions		       *
 ******************************************************************************/

int HalCtlsHandleMixerData(afb_api_t apiHandle, struct CtlHalMixerData **mixerDataList, json_object *currentDataJ, enum MixerDataType dataType)
{
	int idx, mixerDataNb, verbStart, size;
	int err = (int) MIXER_NO_ERROR;

	char *currentDataVerbName, *currentStreamCardId;

	json_type currentDataType;
	json_object *currentJ;

	struct CtlHalMixerData *currentMixerData;

	currentDataType = json_object_get_type(currentDataJ);
	switch(currentDataType) {
		case json_type_object:
			mixerDataNb = 1;
			break;
		case json_type_array:
			mixerDataNb = (unsigned int) json_object_array_length(currentDataJ);
			break;
		default:
			mixerDataNb = 0;
			AFB_API_ERROR(apiHandle, "No data returned");
			return (int) MIXER_ERROR_DATA_EMPTY;
	}

	for(idx = 0; idx < mixerDataNb; idx++) {
		if(currentDataType == json_type_array)
			currentJ = json_object_array_get_idx(currentDataJ, (int) idx);
		else
			currentJ = currentDataJ;

		if(wrap_json_unpack(currentJ, "{s:s}", "verb", &currentDataVerbName)) {
			AFB_API_ERROR(apiHandle, "Can't find verb in current data object");
			err += (int) MIXER_ERROR_DATA_NAME_UNAVAILABLE;
		}
		else if(dataType == MIXER_DATA_STREAMS && wrap_json_unpack(currentJ, "{s:s}", "alsa", &currentStreamCardId)) {
			AFB_API_ERROR(apiHandle, "Can't find card id in current data object");
			err += (int) MIXER_ERROR_DATA_CARDID_UNAVAILABLE;
		}
		else {
			switch(dataType) {
				case MIXER_DATA_STREAMS:
					size = (int) strlen(currentDataVerbName);
					for(verbStart = 0; verbStart < size; verbStart++) {
						if(currentDataVerbName[verbStart] == '#') {
							verbStart++;
							break;
						}
					}

					if(verbStart == size)
						verbStart = 0;

					if(! HalUtlAddStreamDataAndCreateStreamVerb(apiHandle,
										    &currentDataVerbName[verbStart],
										    currentDataVerbName,
										    currentStreamCardId)) {
						AFB_API_ERROR(apiHandle,
							      "Error while adding stream '%s'",
							      currentDataVerbName);
						err += (int) MIXER_ERROR_STREAM_NOT_ADDED;
					}

					break;

				case MIXER_DATA_PLAYBACKS:
				case MIXER_DATA_CAPTURES:
					currentMixerData = HalUtlAddMixerDataToMixerDataList(mixerDataList);

					currentMixerData->verb = strdup((dataType == MIXER_DATA_PLAYBACKS) ? HAL_PLAYBACK_ID : HAL_CAPTURE_ID);
					currentMixerData->verbToCall = strdup(currentDataVerbName);

					if((! currentMixerData->verb) ||
					   (! currentMixerData->verbToCall)) {
						HalUtlRemoveSelectedMixerData(mixerDataList, currentMixerData);
						err += (int) MIXER_ERROR_STREAM_ALLOCATION_FAILED;
					}
					break;

				default:
					break;
			}
		}
	}

	if(dataType == MIXER_DATA_PLAYBACKS) {
		if(afb_api_add_verb(apiHandle,
				    HAL_PLAYBACK_ID,
				    "Playback action transferred to mixer",
				    HalUtlActionOnPlayback,
				    (void *) *mixerDataList,
				    NULL,
				    0,
				    0)) {
			AFB_API_ERROR(apiHandle, "Error while creating verb for playbacks : '%s'", HAL_PLAYBACK_ID);
			err += (int) MIXER_ERROR_PLAYBACK_VERB_NOT_CREATED;
		}
	}

	if(dataType == MIXER_DATA_CAPTURES) {
		if(afb_api_add_verb(apiHandle,
				    HAL_CAPTURE_ID,
				    "Capture action transferred to mixer",
				    HalUtlActionOnCapture,
				    (void *) *mixerDataList,
				    NULL,
				    0,
				    0)) {
			AFB_API_ERROR(apiHandle, "Error while creating verb for captures : '%s'", HAL_CAPTURE_ID);
			err += (int) MIXER_ERROR_CAPTURE_VERB_NOT_CREATED;
		}
	}

	return err;
}

int HalCtlsHandleMixerAttachResponse(afb_api_t apiHandle, struct CtlHalSpecificData *currentHalSpecificData, json_object *mixerResponseJ)
{
	int err = (int) MIXER_NO_ERROR;

	json_object *mixerStreamsJ = NULL, *mixerPlaybacksJ = NULL, *mixerCapturesJ = NULL;

	if(! apiHandle) {
		AFB_API_ERROR(apiHandle, "Can't get current hal api handle");
		return (int) MIXER_ERROR_API_UNAVAILABLE;
	}

	if(wrap_json_unpack(mixerResponseJ, "{s?:o s?:o s?:o}", "streams", &mixerStreamsJ, "playbacks", &mixerPlaybacksJ, "captures", &mixerCapturesJ)) {
		AFB_API_ERROR(apiHandle, "Can't get streams|playbacks|captures object in '%s'", json_object_get_string(mixerResponseJ));
		return (int) MIXER_ERROR_DATA_UNAVAILABLE;
	}

	if(mixerStreamsJ && (err += HalCtlsHandleMixerData(apiHandle, &currentHalSpecificData->ctlHalStreamsData, mixerStreamsJ, MIXER_DATA_STREAMS)))
		AFB_API_ERROR(apiHandle, "Error during handling response mixer streams data '%s'", json_object_get_string(mixerStreamsJ));

	if(mixerPlaybacksJ && (err += HalCtlsHandleMixerData(apiHandle, &currentHalSpecificData->ctlHalPlaybacksData, mixerPlaybacksJ, MIXER_DATA_PLAYBACKS)))
		AFB_API_ERROR(apiHandle, "Error during handling response mixer playbacks data '%s'", json_object_get_string(mixerPlaybacksJ));

	if(mixerCapturesJ && (err += HalCtlsHandleMixerData(apiHandle, &currentHalSpecificData->ctlHalCapturesData, mixerCapturesJ, MIXER_DATA_CAPTURES)))
		AFB_API_ERROR(apiHandle, "Error during handling response mixer captures data '%s'", json_object_get_string(mixerCapturesJ));

	if(! currentHalSpecificData->ctlHalStreamsData) {
		AFB_API_WARNING(apiHandle, "No stream detected in mixer response, %s verb won't be created", HAL_ALL_STREAMS_VERB);
	}
	else if(afb_api_add_verb(apiHandle,
				 HAL_ALL_STREAMS_VERB,
				 "Send a stream action on all streams",
				 HalUtlActionOnAllStream,
				 (void *) currentHalSpecificData->ctlHalStreamsData,
				 NULL,
				 0,
				 0)) {
		AFB_API_ERROR(apiHandle, "Error while creating verb for all streams : '%s'", HAL_ALL_STREAMS_VERB);
		return (int) MIXER_ERROR_ALL_STREAMS_VERB_NOT_CREATED;
	}

	return err;
}

int HalCtlsAttachToMixer(afb_api_t apiHandle)
{
	int err = 0, mixerError;

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

	CtlConfigT *ctrlConfig;

	struct SpecificHalData *currentCtlHalData, *concurentHalData = NULL;

	json_object *responseJ = NULL;

	if(! apiHandle) {
		AFB_API_ERROR(apiHandle, "Can't get current hal api handle");
		return -1;
	}

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

	if(! (currentCtlHalData = (struct SpecificHalData *) getExternalData(ctrlConfig))) {
		AFB_API_ERROR(apiHandle, "Can't get current hal controller data");
		return -3;
	}

	switch(currentCtlHalData->status) {
		case HAL_STATUS_UNAVAILABLE:
			AFB_API_ERROR(apiHandle, "Seems that the hal corresponding card was not found by alsacore at startup");
			return -4;

		case HAL_STATUS_READY:
			AFB_API_NOTICE(apiHandle, "Seems that the hal mixer is already initialized");
			return 1;

		case HAL_STATUS_AVAILABLE:
			break;
	}

	concurentHalData = HalUtlSearchReadyHalDataByCardId(HalMngGetHalDataList(), currentCtlHalData->sndCardId);
	if(concurentHalData) {
		AFB_API_ERROR(apiHandle,
			      "Trying to attach mixer for hal '%s' but the alsa device %i is already in use with mixer by hal '%s'",
			      currentCtlHalData->apiName,
			      currentCtlHalData->sndCardId,
			      concurentHalData->apiName);
		return -5;
	}

	if(! (apiToCall = currentCtlHalData->ctlHalSpecificData->mixerApiName)) {
		AFB_API_ERROR(apiHandle, "Can't get mixer api");
		return -6;
	}

	if(afb_api_call_sync(apiHandle,
			     apiToCall,
			     MIXER_ATTACH_VERB,
			     json_object_get(currentCtlHalData->ctlHalSpecificData->halMixerJ),
			     &responseJ,
			     &returnedError,
			     &returnedInfo)) {
		AFB_API_ERROR(apiHandle,
			      "Something went wrong during call to verb '%s' of api '%s' with error '%s' and info '%s'",
			      MIXER_ATTACH_VERB,
			      apiToCall,
			      returnedError ? returnedError : "not returned",
			      returnedInfo ? returnedInfo : "not returned");
		err = -7;
	}
	else if(! responseJ) {
		AFB_API_ERROR(apiHandle,
			      "Seems that %s call to api %s succeed but no response was returned",
			      MIXER_ATTACH_VERB,
			      apiToCall);
		err = -8;
	}
	else if((mixerError = HalCtlsHandleMixerAttachResponse(apiHandle, currentCtlHalData->ctlHalSpecificData, responseJ)) != (int) MIXER_NO_ERROR) {
		AFB_API_ERROR(apiHandle,
			      "Seems that %s call to api %s succeed but this warning was risen by response decoder : %i '%s'",
			      MIXER_ATTACH_VERB,
			      apiToCall,
			      mixerError,
			      json_object_get_string(responseJ));
		err = -9;
	}
	else {
		AFB_API_NOTICE(apiHandle,
			       "Seems that %s call to api %s succeed with no warning raised : '%s'",
			       MIXER_ATTACH_VERB,
			       apiToCall,
			       json_object_get_string(responseJ));
		currentCtlHalData->status = HAL_STATUS_READY;
	}

	if(responseJ)
		json_object_put(responseJ);

	free(returnedError);
	free(returnedInfo);

	return err;
}

int HalCtlsGetInfoFromMixer(afb_api_t apiHandle,
			    char *apiToCall,
			    json_object *requestJson,
			    json_object **toReturnJ,
			    char **returnedError,
			    char **returnedInfo)
{
	json_object *responseJ = NULL;

	if(! apiHandle) {
		AFB_API_ERROR(apiHandle, "Can't get current hal api handle");
		return -1;
	}

	if(! apiToCall) {
		AFB_API_ERROR(apiHandle, "Can't get mixer api");
		return -2;
	}

	if(! requestJson) {
		AFB_API_ERROR(apiHandle, "Can't get request json");
		return -3;
	}

	if(*returnedError || *returnedInfo) {
		AFB_API_ERROR(apiHandle, "'returnedError' and 'returnedInfo' strings should be empty and set to 'NULL'");
		return -4;
	}

	if(*toReturnJ) {
		AFB_API_ERROR(apiHandle, "'toReturnJ' should be empty and set to 'NULL'");
		return -5;
	}

	if(afb_api_call_sync(apiHandle,
			     apiToCall,
			     MIXER_INFO_VERB,
			     json_object_get(requestJson),
			     &responseJ,
			     returnedError,
			     returnedInfo)) {
		AFB_API_ERROR(apiHandle,
			      "Something went wrong during call to verb '%s' of api '%s' with error '%s' and info '%s'",
			      apiToCall,
			      MIXER_INFO_VERB,
			      *returnedError ? *returnedError : "not returned",
			      *returnedInfo ? *returnedInfo : "not returned");
		return -6;
	}
	else if(! responseJ) {
		AFB_API_ERROR(apiHandle,
			      "Seems that %s call to api %s succeed but no response was returned",
			      MIXER_INFO_VERB,
			      apiToCall);
		json_object_put(responseJ);
		return -7;
	}
	else {
		AFB_API_NOTICE(apiHandle,
			       "Seems that %s call to api %s succeed with no warning raised : '%s'",
			       MIXER_INFO_VERB,
			       apiToCall,
			       json_object_get_string(responseJ));
		*toReturnJ = responseJ;
	}

	return 0;
}