/*
 * wiadf_play.c
 *
 *  Created on: Feb 22, 2026
 *      Author: damiano
 */
#include <string.h>
#include <wiadf_play.h>
#include <wiadf_glob.h>
#include <wiglobals.h>
#include <wi2cma.h>
#include <winetora.h>
#include <wiadf_glob.h>
#include <wiadf_ll.h>
#include <wiaudio.h>

#include <driver/i2s_common.h>
#include <freertos/task.h>

#include <audio_element.h>
#include <audio_pipeline.h>
#include "audio_event_iface.h"
#include "audio_common.h"
#include "mp3_decoder.h"
#include "wiadf_udp.h"
#include "wiwrap.h"
#include <raw_stream.h>
#include <audio_pipeline.h>
#include <mp3_decoder.h>
#include <aac_decoder.h>
#include <esp_aac_dec.h>    // this is another library
#include <i2s_stream.h>
#include <raw_stream.h>

#include <filter_resample.h>

#include <amrwb_encoder.h>
#include <fatfs_stream.h>


enum PlayFiletype
	{
	PLAY_NULL,
	PLAY_MP3,
	PLAY_AAC
	};



struct PlayPipeline
	{
	struct tskTaskControlBlock *playPipeTaskP;        // the task that manages the pipeline

	FILE *play_fileP;   // if not null then play this mp3 file

   	struct audio_pipeline * pipelineP;

	struct audio_element * fatfs_readerP;     // uses callback to pick up data
	struct audio_element * audio_decoder;     // uses callback to pick up data
	struct audio_element * rsp_filter;        // to change the sample rate to 8khz

	struct audio_event_iface * audio_eventP;

	bool stop_play_file;        // if true the read file callback will return EOF
	enum PlayFiletype play_filetype;

	char play_fname[WISDISK_FNAME_MAX];
	};

struct PlayPipeline playPipe;




/**
 * this is called by the system to read some data from the source file in SD
 * remember that the file must already be open !
 */
static int audio_decoder_read_cb(audio_element_handle_t el, char *to_buffer, int want_len, TickType_t wait_time, void *ctx)
	{
	if ( playPipe.stop_play_file )
		return WIRA_printe(AEL_IO_DONE, "audio_decoder_read_cb stop END");

	int letti = fread(to_buffer ,1 ,want_len ,playPipe.play_fileP);

	WIRA_print("audio_decoder_read_cb want_len=%d letti=%d \n",want_len,letti);

    if ( letti <= 0 )
		return WIRA_printe(AEL_IO_DONE, "audio_decoder_read_cb EOF END");
    
	return letti;
	}


static int parse_audio_decoder_message(audio_event_iface_msg_t *msgP, char *source_str )
	{
	int cmd = msgP->cmd;

    if (  cmd == AEL_MSG_CMD_REPORT_MUSIC_INFO) 
		{
        audio_element_info_t music_info = {0};
        audio_element_getinfo(playPipe.audio_decoder, &music_info);
		int sample_rate = music_info.sample_rates;
		int sample_bits = music_info.bits;
		int sample_chn  = music_info.channels;

        WIRA_print("parse_audio_decoder_message: Music sample_rates=%d, bits=%d, ch=%d \n", sample_rate, sample_bits, sample_chn);

		rsp_filter_change_src_info(playPipe.rsp_filter, sample_rate, sample_chn, sample_bits);
		return ALL_GOOD;
    	}

    if ( msgP->cmd != AEL_MSG_CMD_REPORT_STATUS )
		return WIRA_printe(ALL_GOOD,"parse_audio_decoder_message: %s unsupported cmd=%d %s\n",source_str, msgP->cmd, adf_get_cmd_string(msgP->cmd) );

	int ael_status = (int)msgP->data;

	switch(ael_status)
		{
		case AEL_STATUS_STATE_STOPPED:
		case AEL_STATUS_STATE_FINISHED:
	        return WIRA_printe(ALL_GOOD,"parse_audio_decoder_message: %s STOP ael_status %s \n",source_str, adf_get_AEL_string(ael_status));

		case AEL_STATUS_STATE_RUNNING:
			break;

		default:
	        WIRA_print("parse_audio_decoder_message: %s UNSUPPORTED ael_status %s \n",source_str, adf_get_AEL_string(ael_status));
			break;
		}

	return ALL_GOOD;
	}


/**
 * this happens when the writer to i2s receives a stop
 */
static int parse_rsp_filter_message(audio_event_iface_msg_t *msgP, char *source_str )
	{
    if ( msgP->cmd != AEL_MSG_CMD_REPORT_STATUS )
        return WIRA_printe(ALL_GOOD,"parse_rsp_filter_message: %s unsupported cmd=%d %s\n",source_str, msgP->cmd, adf_get_cmd_string(msgP->cmd) );

    int ael_status = (int)msgP->data;

	switch(ael_status)
		{
		case AEL_STATUS_STATE_STOPPED:
		case AEL_STATUS_STATE_FINISHED:
			// when STOP is received, an error exit is generated
	        return WIRA_printe(1,"parse_rsp_filter_message: %s STOP ael_status %s \n",source_str, adf_get_AEL_string(ael_status));

		case AEL_STATUS_STATE_RUNNING:
			break;

		default:
	        WIRA_print("parse_rsp_filter_message: %s UNSUPPORTED ael_status %s \n",source_str, adf_get_AEL_string(ael_status));
			break;
		}

	return ALL_GOOD;
	}



/**
 * return ALL_GOOD if normal handling, an error code if need to stop
 */
static int parse_audio_event( audio_event_iface_msg_t *msgP )
	{
	int s_type = msgP->source_type;

	if ( s_type != AUDIO_ELEMENT_TYPE_ELEMENT )
		return WIRA_printe(ALL_GOOD,"parse_audio_event: BAD s_type %s \n",wiadf_map_source_type(s_type));

    if ( msgP->source ==  playPipe.audio_decoder ) 
		return parse_audio_decoder_message(msgP, "audio_decoder");

    if ( msgP->source ==  playPipe.rsp_filter ) 
		return parse_rsp_filter_message(msgP, "rsp_filter");

	WIRA_print("parse_audio_event unsupported source TASK \n");

	return ALL_GOOD;
	}

/**
 * A source that has no time reference eg: a sdcard read NEEDs to have a limit on the data out flow
 * So, this one MUST wait for data to be written, before adding another buffer
 * Also, since I normally write in chunk of 20ms, I need to accumulate this much
 */
static int to_i2s_queue_callback(audio_element_handle_t self, char *buffer, int put_datalen, TickType_t ticks_to_wait, void *context)
	{
	int f_retcode = put_datalen;

	// I do not want to do operations bigger than this
	int chunk_datalen=1024;

	while ( put_datalen > 0 )
		{
		if ( put_datalen < chunk_datalen )
			chunk_datalen = put_datalen;

		// this will eventually "pause", waiting for space to write
		int bytes_sent = wiadf_u32_push(buffer, chunk_datalen, WIAUDIO_SRC_SDCARD, true);

		// here, I can also send udp, no ?
		wiudp_send_audio(buffer, chunk_datalen, WIAUDIO_SRC_SDCARD );

		buffer = buffer + bytes_sent;
		put_datalen -= bytes_sent;
		}

	return f_retcode;
	}

#define USE_CALLBACK
//#define USE_FATFSREAD





/**
 * the file is already open
 */
static int file_play_pipeline(void)
	{
    WIRA_print("file_play_pipeline ----------- START\n");

    audio_pipeline_cfg_t pipeline_cfg = DEFAULT_AUDIO_PIPELINE_CONFIG();
    playPipe.pipelineP = audio_pipeline_init(&pipeline_cfg);

    if ( ! playPipe.pipelineP) 
        return WIRA_printe(ESP_ERR_NO_MEM,"audio_pipeline_init FAILED\n");

#ifdef USE_FATFSREAD
    fatfs_stream_cfg_t fatfs_cfg = FATFS_STREAM_CFG_DEFAULT();
    fatfs_cfg.type = AUDIO_STREAM_READER;
    playPipe.fatfs_readerP = fatfs_stream_init(&fatfs_cfg);

    if ( ! playPipe.fatfs_readerP) 
        return WIRA_printe(ESP_ERR_NO_MEM,"fatfs_stream_init FAILED\n");
#endif

    if ( playPipe.play_filetype == PLAY_MP3 )
		{
	    WIRA_print("  - Create MP3 decoder to decode file\n");
		mp3_decoder_cfg_t mp3_cfg = DEFAULT_MP3_DECODER_CONFIG();
		mp3_cfg.stack_in_ext = false;          // to avoid error CONFIG_SPIRAM_ALLOW_STACK_EXTERNAL_MEMORY
	    playPipe.audio_decoder = mp3_decoder_init(&mp3_cfg);
		}
    else
		{
	    WIRA_print("  - Create AAC decoder to decode file\n");
		aac_decoder_cfg_t aac_cfg = DEFAULT_AAC_DECODER_CONFIG();
//		aac_cfg.stack_in_ext = false;          // to avoid error CONFIG_SPIRAM_ALLOW_STACK_EXTERNAL_MEMORY
//		aac_cfg.out_rb_size = 1024;
//		aac_cfg.plus_enable=true;
	    playPipe.audio_decoder = aac_decoder_init(&aac_cfg);
		}

#ifdef USE_CALLBACK
    audio_element_set_read_cb(playPipe.audio_decoder, audio_decoder_read_cb, NULL);
#endif

    WIRA_print("  - Create a resampler to go to 8kHz \n");
    rsp_filter_cfg_t rsp_cfg = DEFAULT_RESAMPLE_FILTER_CONFIG();
	rsp_cfg.stack_in_ext = false;          // to avoid error CONFIG_SPIRAM_ALLOW_STACK_EXTERNAL_MEMORY
    rsp_cfg.dest_rate = WIAUDIO_SRATE_8kHz;
    rsp_cfg.mode = RESAMPLE_DECODE_MODE;
//    rsp_cfg.complexity = 0;
    playPipe.rsp_filter = rsp_filter_init(&rsp_cfg);

	if ( ! playPipe.rsp_filter )
		return WIRA_printe(ESP_ERR_NO_MEM,"audio.rsp_filter_init FAIL\n");

	// in theory I wish to be called when something is written
	int errcode = audio_element_set_write_cb(playPipe.rsp_filter, to_i2s_queue_callback, NULL);
	if ( errcode )
		return WIRA_printe(0,"audio_element_set_write_cb FAIL A\n");	


/**
esp_err_t audio_element_set_write_cb(audio_element_handle_tel, stream_funcfn, void *context)
This API allows the application to set a write callback for the last audio_element in the pipeline for allowing the pipeline to 
interface with other systems. The callback is invoked every time the audio element has a processed data that needs to be passed forward.
https://docs.espressif.com/projects/esp-adf/en/v2.0/api-reference/framework/audio_element.html
*/

    WIRA_print("  - Register all elements to audio pipeline \n");

#ifdef USE_FATFSREAD

    if ( audio_pipeline_register(playPipe.pipelineP, playPipe.fatfs_readerP, "reader") )
		return WIRA_printe(0,"audio_pipeline_register reader FAIL\n");

    audio_element_set_uri(playPipe.fatfs_readerP, playPipe.play_fname);
#endif

    if ( audio_pipeline_register(playPipe.pipelineP, playPipe.audio_decoder, "decoder") )
		return WIRA_printe(0,"audio_pipeline_register decoder FAIL\n");

    if ( audio_pipeline_register(playPipe.pipelineP, playPipe.rsp_filter, "rsp") )
		return WIRA_printe(0,"audio_pipeline_register RSP FAIL\n");

    WIRA_print("  - Link it together decoder --> resamp \n");

#ifdef USE_FATFSREAD
    const char *link_tag[] = { "reader", "decoder", "rsp"};
#else
	const char *link_tag[] = { "decoder", "rsp"};
#endif

    if ( audio_pipeline_link(playPipe.pipelineP,link_tag, ARRAY_SIZE(link_tag)) )
		return WIRA_printe(0,"audio_pipeline_link FAIL\n");

    WIRA_print("  - Set up  event listener \n");
    audio_event_iface_cfg_t evt_cfg = AUDIO_EVENT_IFACE_DEFAULT_CFG();
    playPipe.audio_eventP = audio_event_iface_init(&evt_cfg);

	if ( ! playPipe.audio_eventP )
		return WIRA_printe(ESP_ERR_NO_MEM,"audio_event_iface_init FAIL\n");

    WIRA_print("  - Listening event from all elements of pipeline\n");
    audio_pipeline_set_listener(playPipe.pipelineP, playPipe.audio_eventP);

    WIRA_print("  - Start audio_pipeline\n");

	audio_play_this(WIAUDIO_SRC_SDCARD);
	audio_source_this(WIAUDIO_SRC_SDCARD);

    audio_pipeline_run(playPipe.pipelineP);

	audio_event_iface_msg_t msg;

	while ( audio_event_iface_read(playPipe.audio_eventP, &msg, portMAX_DELAY) == ESP_OK )
		if ( parse_audio_event(&msg) )
			break;

	audio_play_this(WIAUDIO_SRC_UDP);
	audio_source_this(WIAUDIO_SRC_I2S);

    WIRA_print("  - STOP audio_pipeline \n");

    audio_pipeline_stop(playPipe.pipelineP);  // this will complain that mp3 and i2s are already stopped
    audio_pipeline_wait_for_stop(playPipe.pipelineP);

    audio_pipeline_terminate(playPipe.pipelineP);

    audio_pipeline_unregister(playPipe.pipelineP, playPipe.audio_decoder);
    audio_pipeline_unregister(playPipe.pipelineP, playPipe.rsp_filter);

    // Terminate the pipeline before removing the listener 
    audio_pipeline_remove_listener(playPipe.pipelineP);

    // Make sure audio_pipeline_remove_listener is called before destroying event_iface 
    audio_event_iface_destroy(playPipe.audio_eventP);

    // Release all resources 
    audio_element_deinit(playPipe.audio_decoder);
    audio_element_deinit(playPipe.rsp_filter);

    audio_pipeline_deinit(playPipe.pipelineP);

	return ESP_OK;
	}

static bool string_equal(void *aP, void *bP)
	{
	return strcmp(aP,bP) == 0;
	}


static enum PlayFiletype get_filetype(void)
	{
	char *nameP = playPipe.play_fname;

	int namelen = strlen(nameP);

	if ( namelen < 5 )
		{
		// the file name must have AT least one char, len 5... onward
		WIRA_print("get_filetype: string too short %d\n",namelen);
		return PLAY_NULL;
		}

	char *fsuffixP = nameP + (namelen - 4);
	
	if ( string_equal(fsuffixP,".mp3"))
		return PLAY_MP3;

	if ( string_equal(fsuffixP,".MP3"))
		return PLAY_MP3;

	if ( string_equal(fsuffixP,".aac"))
		return PLAY_AAC;

	if ( string_equal(fsuffixP,".AAC"))
		return PLAY_AAC;

	WIRA_print("get_filetype: unsupported extension %s \n",nameP);

	return PLAY_NULL;
	}

/**
 * this should open and play
 */
static void file_play_fun_a(void)
	{
	playPipe.play_filetype = get_filetype();

	if ( ! playPipe.play_filetype )
		return;

	playPipe.play_fileP = fopen(playPipe.play_fname, "r");

	if ( ! 	playPipe.play_fileP )
		{
		WIRA_print("file_play_fun_a: Cannot open %s\n",playPipe.play_fname);
		return;
		}

	playPipe.stop_play_file = false;

	printf("file_play_fun_a: File %s opened \n",playPipe.play_fname);

	file_play_pipeline();

	// I know the file was opened
	fclose(playPipe.play_fileP);
	playPipe.play_fileP=NULL;
	}

/**
 * Need this since I need vTaskDelete and file play pipeline has many exit
 */
static void file_play_fun(void *params)
	{
	file_play_fun_a();

	playPipe.playPipeTaskP=NULL;
    vTaskDelete(NULL);
	}

/**
 * start a thread to play some audio file
 * now, I possibly need to arrange to have a mixer of audio coming from UDP and audio coming from storage
 */
esp_err_t wiaudio_file_play_start(char *filename)
	{
	if ( playPipe.playPipeTaskP )
		{
		WIRA_print("wiraudio_file_play_start: TASK is running, stopping \n");
		// this will trigger pipeline stop
		playPipe.stop_play_file = true;
		return ALL_GOOD;
		}

	strncpy(playPipe.play_fname, filename, sizeof(playPipe.play_fname));

	return wira_new_task_s(file_play_fun, "File Play", &playPipe.playPipeTaskP, 1024*4);	
	}


void play_audio_task_info(struct WiraTaskInfo *infoP)
	{
	strncpy(infoP->task_name,"play audio task info",sizeof(infoP->task_name));
	infoP->free_stack_high=playPipe.playPipeTaskP ? uxTaskGetStackHighWaterMark(playPipe.playPipeTaskP) : 0;
	}













