/*
 * Copyright (c) 2010-2022 Belledonne Communications SARL.
 *
 * This file is part of mediastreamer2
 * (see https://gitlab.linphone.org/BC/public/mediastreamer2).
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

#include "mediastreamer2_tester_private.h"
#include <bctoolbox/tester.h>

#include "mediastreamer2/dtmfgen.h"
#include "mediastreamer2/msfileplayer.h"
#include "mediastreamer2/msfilerec.h"
#include "mediastreamer2/msrtp.h"
#include "mediastreamer2/mssndcard.h"
#include "mediastreamer2/mstonedetector.h"
#include "mediastreamer2/mswebcam.h"

typedef struct {
	MSDtmfGenCustomTone generated_tone;
	MSToneDetectorDef expected_tone;
} tone_test_def_t;

static tone_test_def_t tone_definition[] = {{{"", 400, {2000, 0}, 0.6f, 0, 0}, {"", 2000, 300, 0.5f}},
                                            {{"", 600, {1500, 0}, 1.0f, 0, 0}, {"", 1500, 500, 0.9f}},
                                            {{"", 500, {941, 0}, 0.8f, 0, 0}, {"", 941, 400, 0.7f}}};

MSTicker *ms_tester_ticker = NULL;
MSFilter *ms_tester_fileplay = NULL;
MSFilter *ms_tester_filerec = NULL;
MSFilter *ms_tester_dtmfgen = NULL;
MSFilter *ms_tester_tonedet = NULL;
MSFilter *ms_tester_voidsource = NULL;
MSFilter *ms_tester_voidsink = NULL;
MSFilter *ms_tester_encoder = NULL;
MSFilter *ms_tester_decoder = NULL;
MSFilter *ms_tester_rtprecv = NULL;
MSFilter *ms_tester_rtpsend = NULL;
MSFilter *ms_tester_resampler = NULL;
MSFilter *ms_tester_soundwrite = NULL;
MSFilter *ms_tester_soundread = NULL;
MSFilter *ms_tester_videocapture = NULL;
char *ms_tester_codec_mime = "pcmu";
char *ms_tester_plugin_location = NULL;
unsigned char ms_tester_tone_detected;

int base_port = 1024;

MSFactory *ms_tester_factory_new(void) {
	MSFactory *factory = ms_factory_new();

	if (ms_tester_plugin_location != NULL) ms_factory_set_plugins_dir(factory, ms_tester_plugin_location);

	ms_factory_init_voip(factory);
	ms_factory_init_plugins(factory);

	return factory;
}

static MSTicker *create_ticker(void) {
	MSTickerParams params;
	params.name = "Tester MSTicker";
	params.prio = MS_TICKER_PRIO_NORMAL;
	return ms_ticker_new_with_params(&params);
}

void ms_tester_create_ticker(void) {
	BC_ASSERT_PTR_NULL(ms_tester_ticker);
	ms_tester_ticker = create_ticker();
	BC_ASSERT_PTR_NOT_NULL(ms_tester_ticker);
}

void ms_tester_destroy_ticker(void) {
	if (ms_tester_ticker) {
		ms_ticker_destroy(ms_tester_ticker);
		ms_tester_ticker = NULL;
	}
}

// This allow multiple tester to be run on the same machine
// Try to avoid Ephemeral port range as they are used on the test
// machine by liblinphone test (bind on port 0)
// Get a Random port in range 6000 - 32240
// with the last 5 bytes to 0 as ports are used from base to base +28
void ms_tester_set_random_port(void) {
	base_port = (bctbx_random() % 820) * 32 + 6000;
}

void ms_tester_get_random_token(char token[7]) {
	char alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
	int i = 0;
	for (i = 0; i < 6; i++) {
		token[i] = alphabet[(bctbx_random() % 52)];
	}
	token[6] = '\0';
}

char *ms_tester_get_random_filename(const char *basename, const char *suffix) {
	size_t baseSize = strlen(basename);
	size_t suffixSize = (suffix == NULL) ? 0 : strlen(suffix);
	char *ret = (char *)bctbx_malloc(baseSize + 6 + suffixSize + 1);
	memcpy(ret, basename, baseSize);
	ms_tester_get_random_token(ret + baseSize);
	memcpy(ret + baseSize + 6, suffix, suffixSize);
	ret[baseSize + 6 + suffixSize] = '\0';
	return ret;
}

#define CREATE_FILTER(mask, filter, factory, id)                                                                       \
	if (filter_mask & mask) {                                                                                          \
		BC_ASSERT_PTR_NULL(filter);                                                                                    \
		filter = ms_factory_create_filter(factory, id);                                                                \
		BC_ASSERT_PTR_NOT_NULL(filter);                                                                                \
	}

void ms_tester_create_filter(MSFilter **filter, MSFilterId id, MSFactory *f) {
	BC_ASSERT_PTR_NOT_NULL(filter);
	BC_ASSERT_PTR_NULL(*filter);
	*filter = ms_factory_create_filter(f, id);
	BC_ASSERT_PTR_NOT_NULL(*filter);
}

void ms_tester_create_filters(unsigned int filter_mask, MSFactory *f) {
	MSSndCardManager *snd_manager;
	MSWebCamManager *cam_manager;
	MSSndCard *playcard;
	MSSndCard *captcard;
	MSWebCam *camera;

	CREATE_FILTER(FILTER_MASK_FILEPLAY, ms_tester_fileplay, f, MS_FILE_PLAYER_ID);
	CREATE_FILTER(FILTER_MASK_FILEREC, ms_tester_filerec, f, MS_FILE_REC_ID);
	CREATE_FILTER(FILTER_MASK_DTMFGEN, ms_tester_dtmfgen, f, MS_DTMF_GEN_ID);
	CREATE_FILTER(FILTER_MASK_TONEDET, ms_tester_tonedet, f, MS_TONE_DETECTOR_ID);
	CREATE_FILTER(FILTER_MASK_VOIDSOURCE, ms_tester_voidsource, f, MS_VOID_SOURCE_ID);
	CREATE_FILTER(FILTER_MASK_VOIDSINK, ms_tester_voidsink, f, MS_VOID_SINK_ID);
	if (filter_mask & FILTER_MASK_ENCODER) {
		BC_ASSERT_PTR_NULL(ms_tester_encoder);
		ms_tester_encoder = ms_factory_create_encoder(f, ms_tester_codec_mime);
		BC_ASSERT_PTR_NOT_NULL(ms_tester_encoder);
	}
	if (filter_mask & FILTER_MASK_DECODER) {
		BC_ASSERT_PTR_NULL(ms_tester_decoder);
		ms_tester_decoder = ms_factory_create_decoder(f, ms_tester_codec_mime);
		BC_ASSERT_PTR_NOT_NULL(ms_tester_decoder);
	}
	CREATE_FILTER(FILTER_MASK_RTPRECV, ms_tester_rtprecv, f, MS_RTP_RECV_ID);
	CREATE_FILTER(FILTER_MASK_RTPSEND, ms_tester_rtpsend, f, MS_RTP_SEND_ID);
	CREATE_FILTER(FILTER_MASK_RESAMPLER, ms_tester_resampler, f, MS_RESAMPLE_ID);
	if (filter_mask & FILTER_MASK_SOUNDWRITE) {
		BC_ASSERT_PTR_NULL(ms_tester_soundwrite);
		snd_manager = ms_factory_get_snd_card_manager(f);
		playcard = ms_snd_card_manager_get_default_playback_card(snd_manager);
		BC_ASSERT_PTR_NOT_NULL(playcard);
		ms_tester_soundwrite = ms_snd_card_create_writer(playcard);
		BC_ASSERT_PTR_NOT_NULL(ms_tester_soundwrite);
	}
	if (filter_mask & FILTER_MASK_SOUNDREAD) {
		BC_ASSERT_PTR_NULL(ms_tester_soundread);
		snd_manager = ms_factory_get_snd_card_manager(f);
		captcard = ms_snd_card_manager_get_default_capture_card(snd_manager);
		BC_ASSERT_PTR_NOT_NULL(captcard);
		ms_tester_soundread = ms_snd_card_create_reader(captcard);
		BC_ASSERT_PTR_NOT_NULL(ms_tester_soundread);
	}
	if (filter_mask & FILTER_MASK_VIDEOCAPTURE) {
		BC_ASSERT_PTR_NULL(ms_tester_videocapture);
		cam_manager = ms_factory_get_web_cam_manager(f);
		camera = ms_web_cam_manager_get_default_cam(cam_manager);
		BC_ASSERT_PTR_NOT_NULL(camera);
		ms_tester_videocapture = ms_web_cam_create_reader(camera);
		BC_ASSERT_PTR_NOT_NULL(ms_tester_videocapture);
	}
}

#define DESTROY_FILTER(mask, filter)                                                                                   \
	if ((filter_mask & mask) && (filter != NULL)) {                                                                    \
		ms_filter_destroy(filter);                                                                                     \
		filter = NULL;                                                                                                 \
	}

void ms_tester_destroy_filter(MSFilter **filter) {
	BC_ASSERT_PTR_NOT_NULL(filter);
	if (*filter != NULL) {
		ms_filter_destroy(*filter);
		*filter = NULL;
	}
}

void ms_tester_destroy_filters(unsigned int filter_mask) {
	DESTROY_FILTER(FILTER_MASK_FILEPLAY, ms_tester_fileplay)
	DESTROY_FILTER(FILTER_MASK_FILEREC, ms_tester_filerec)
	DESTROY_FILTER(FILTER_MASK_DTMFGEN, ms_tester_dtmfgen)
	DESTROY_FILTER(FILTER_MASK_TONEDET, ms_tester_tonedet)
	DESTROY_FILTER(FILTER_MASK_VOIDSOURCE, ms_tester_voidsource)
	DESTROY_FILTER(FILTER_MASK_VOIDSINK, ms_tester_voidsink)
	DESTROY_FILTER(FILTER_MASK_ENCODER, ms_tester_encoder)
	DESTROY_FILTER(FILTER_MASK_DECODER, ms_tester_decoder)
	DESTROY_FILTER(FILTER_MASK_RTPRECV, ms_tester_rtprecv)
	DESTROY_FILTER(FILTER_MASK_RTPSEND, ms_tester_rtpsend)
	DESTROY_FILTER(FILTER_MASK_RESAMPLER, ms_tester_resampler)
	DESTROY_FILTER(FILTER_MASK_SOUNDWRITE, ms_tester_soundwrite)
	DESTROY_FILTER(FILTER_MASK_SOUNDREAD, ms_tester_soundread)
	DESTROY_FILTER(FILTER_MASK_VIDEOCAPTURE, ms_tester_videocapture)
}

void ms_tester_tone_generation_loop(void) {
	unsigned int i;

	for (i = 0; i < (sizeof(tone_definition) / sizeof(tone_definition[0])); i++) {
		ms_filter_call_method(ms_tester_dtmfgen, MS_DTMF_GEN_PLAY_CUSTOM, &tone_definition[i].generated_tone);
		ms_sleep(1);
	}
}

void ms_tester_tone_detection_loop(void) {
	unsigned int i;

	for (i = 0; i < (sizeof(tone_definition) / sizeof(tone_definition[0])); i++) {
		ms_tester_tone_detected = FALSE;
		ms_filter_call_method(ms_tester_tonedet, MS_TONE_DETECTOR_CLEAR_SCANS, NULL);
		ms_filter_call_method(ms_tester_tonedet, MS_TONE_DETECTOR_ADD_SCAN, &tone_definition[i].expected_tone);
		ms_sleep(1);
		BC_ASSERT_EQUAL(ms_tester_tone_detected, TRUE, int, "%d");
	}
}

void ms_tester_tone_generation_and_detection_loop(void) {
	unsigned int i;

	for (i = 0; i < (sizeof(tone_definition) / sizeof(tone_definition[0])); i++) {
		ms_tester_tone_detected = FALSE;
		BC_ASSERT_EQUAL(0, ms_filter_call_method(ms_tester_tonedet, MS_TONE_DETECTOR_CLEAR_SCANS, NULL), int, "%d");
		BC_ASSERT_EQUAL(
		    0, ms_filter_call_method(ms_tester_tonedet, MS_TONE_DETECTOR_ADD_SCAN, &tone_definition[i].expected_tone),
		    int, "%d");
		BC_ASSERT_EQUAL(
		    0, ms_filter_call_method(ms_tester_dtmfgen, MS_DTMF_GEN_PLAY_CUSTOM, &tone_definition[i].generated_tone),
		    int, "%d");
		ms_sleep(1);
		BC_ASSERT_EQUAL(ms_tester_tone_detected, TRUE, int, "%d");
	}
}

RtpProfile *ms_tester_create_rtp_profile(void) {
	RtpProfile *profile = rtp_profile_new("Tester profile");
	rtp_profile_set_payload(profile, PCMU8_PAYLOAD_TYPE, &payload_type_pcmu8000);
	rtp_profile_set_payload(profile, PCMA8_PAYLOAD_TYPE, &payload_type_pcma8000);
	rtp_profile_set_payload(profile, H263_PAYLOAD_TYPE, &payload_type_h263);
	rtp_profile_set_payload(profile, H264_PAYLOAD_TYPE, &payload_type_h264);
	rtp_profile_set_payload(profile, VP8_PAYLOAD_TYPE, &payload_type_vp8);
	rtp_profile_set_payload(profile, MP4V_PAYLOAD_TYPE, &payload_type_mp4v);
	rtp_profile_set_payload(profile, OPUS_PAYLOAD_TYPE, &payload_type_opus);
	rtp_profile_set_payload(profile, SPEEX_PAYLOAD_TYPE, &payload_type_speex_wb);
	rtp_profile_set_payload(profile, SILK_PAYLOAD_TYPE, &payload_type_silk_wb);
	return profile;
}

bool_t wait_for_list(bctbx_list_t *mss, int *counter, int value, int timeout_ms) {
	return wait_for_list_with_parse_events(mss, counter, value, timeout_ms, NULL, NULL);
}

bool_t wait_for_list_with_parse_events(
    bctbx_list_t *mss, int *counter, int value, int timeout_ms, bctbx_list_t *cbs, bctbx_list_t *ptrs) {
	bctbx_list_t *msi;
	bctbx_list_t *cbi;
	bctbx_list_t *ptri;
	int retry = 0;

	while ((*counter < value) && (retry++ < (timeout_ms / 100))) {
		for (msi = mss, cbi = cbs, ptri = ptrs; msi != NULL; msi = msi->next) {
			MediaStream *stream = (MediaStream *)msi->data;
			ms_tester_iterate_cb cb = NULL;
#if VIDEO_ENABLED
			if (stream->type == MSVideo) {
				/* video_stream_iterate does more than just calling media_stream_iterate */
				video_stream_iterate((VideoStream *)stream);
			} else {
#endif
				media_stream_iterate(stream);
#if VIDEO_ENABLED
			}
#endif
			if ((retry % 10) == 0) {
				ms_message("stream [%p] bandwidth usage: rtp [d=%.1f,u=%.1f] kbit/sec", stream,
				           media_stream_get_down_bw(stream) / 1000, media_stream_get_up_bw(stream) / 1000);
				ms_message("stream [%p] bandwidth usage: rtcp[d=%.1f,u=%.1f] kbit/sec", stream,
				           media_stream_get_rtcp_down_bw(stream) / 1000, media_stream_get_rtcp_up_bw(stream) / 1000);
			}
			if (cbi && ptri) {
				cb = (ms_tester_iterate_cb)cbi->data;
				cb(stream, ptri->data);
			}
			if (cbi) cbi = cbi->next;
			if (ptri) ptri = ptri->next;
		}
		ms_usleep(100000);
	}

	if (*counter < value) return FALSE;
	return TRUE;
}

bool_t wait_for_until(MediaStream *ms_1, MediaStream *ms_2, int *counter, int value, int timeout_ms) {
	return wait_for_until_with_parse_events(ms_1, ms_2, counter, value, timeout_ms, NULL, NULL, NULL, NULL);
}

bool_t wait_for_until_with_parse_events(MediaStream *ms1,
                                        MediaStream *ms2,
                                        int *counter,
                                        int value,
                                        int timeout_ms,
                                        ms_tester_iterate_cb cb1,
                                        void *ptr1,
                                        ms_tester_iterate_cb cb2,
                                        void *ptr2) {
	bctbx_list_t *mss = NULL;
	bctbx_list_t *cbs = NULL;
	bctbx_list_t *ptrs = NULL;
	bool_t result;

	if (ms1) {
		mss = bctbx_list_append(mss, ms1);
		if (cb1 && ptr1) {
			cbs = bctbx_list_append(cbs, cb1);
			ptrs = bctbx_list_append(ptrs, ptr1);
		}
	}
	if (ms2) {
		mss = bctbx_list_append(mss, ms2);
		if (cb2 && ptr2) {
			cbs = bctbx_list_append(cbs, cb2);
			ptrs = bctbx_list_append(ptrs, ptr2);
		}
	}
	result = wait_for_list_with_parse_events(mss, counter, value, timeout_ms, cbs, ptrs);
	bctbx_list_free(mss);
	bctbx_list_free(cbs);
	bctbx_list_free(ptrs);
	return result;
}

bool_t wait_for(MediaStream *ms_1, MediaStream *ms_2, int *counter, int value) {
	return wait_for_until(ms_1, ms_2, counter, value, 2000);
}
