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

#ifdef HAVE_CONFIG_H
#include "gitversion.h"
#include "mediastreamer-config.h"
#else
#ifndef MEDIASTREAMER_VERSION
#define MEDIASTREAMER_VERSION "unknown"
#endif
#ifndef GIT_VERSION
#define GIT_VERSION "unknown"
#endif
#endif

#include "mediastreamer2/ms_srtp.h"
#include "mediastreamer2/mscodecutils.h"
#include "mediastreamer2/mscommon.h"
#include "mediastreamer2/msfilter.h"
#include "private.h"

#ifdef __cplusplus
extern "C" {
#endif

extern void __register_ffmpeg_encoders_if_possible(MSFactory *factory);
extern void __register_ffmpeg_h264_decoder_if_possible(MSFactory *factory);
extern void ms_ffmpeg_check_init(void);
extern bool_t libmsandroiddisplay_init(MSFactory *factory);
extern void libmsandroiddisplaybad_init(MSFactory *factory);
extern void libmsandroidopengldisplay_init(MSFactory *factory);
extern void libmsandroidtexturedisplay_init(MSFactory *factory);

#if defined(__APPLE__) && defined(VIDEO_ENABLED)
extern void _register_videotoolbox_if_supported(MSFactory *factory);
#endif

#include "mediastreamer2/mssndcard.h"
#include "mediastreamer2/msvideopresets.h"
#include "mediastreamer2/mswebcam.h"
#include "voipdescs.h"

#ifdef __APPLE__
#include "TargetConditionals.h"
#endif

#ifdef __ANDROID__
#include "android_mediacodec.h"
#include "mediastreamer2/android_utils.h"
#include <android/log.h>
#endif

#ifdef MS2_FILTERS

#ifdef __ALSA_ENABLED__
extern MSSndCardDesc alsa_card_desc;
#endif

#ifdef __QSA_ENABLED__
extern MSSndCardDesc ms_qsa_card_desc;
#endif

#ifdef __OSS_ENABLED__
extern MSSndCardDesc oss_card_desc;
#endif

#ifdef __ARTS_ENABLED__
extern MSSndCardDesc arts_card_desc;
#endif

#ifdef __DIRECTSOUND_ENABLED__
extern MSSndCardDesc winsndds_card_desc;
#endif

#ifdef __MACSND_ENABLED__
extern MSSndCardDesc ca_card_desc;
#endif

#ifdef __PORTAUDIO_ENABLED__
extern MSSndCardDesc pasnd_card_desc;
#endif

#ifdef __MAC_AQ_ENABLED__
extern MSSndCardDesc aq_card_desc;
#endif

#ifdef __PULSEAUDIO_ENABLED__
extern MSSndCardDesc pulse_card_desc;
#endif

#if TARGET_OS_IPHONE
extern MSSndCardDesc au_card_desc;
#endif

#ifdef __ANDROID__
extern MSSndCardDesc msandroid_sound_card_desc;
extern MSSndCardDesc android_native_snd_card_desc;
extern MSSndCardDesc android_native_snd_opensles_card_desc;
#endif

#endif /* MS2_FILTERS */

static MSSndCardDesc *ms_snd_card_descs[] = {
#ifdef MS2_FILTERS

#ifdef __PULSEAUDIO_ENABLED__
    &pulse_card_desc,
#endif

#ifdef __ALSA_ENABLED__
    &alsa_card_desc,
#endif

#ifdef __QSA_ENABLED__
    &ms_qsa_card_desc,
#endif

#ifdef __OSS_ENABLED__
    &oss_card_desc,
#endif

#ifdef __ARTS_ENABLED__
    &arts_card_desc,
#endif

#ifdef __DIRECTSOUND_ENABLED__
    &winsndds_card_desc,
#endif

#ifdef __PORTAUDIO_ENABLED__
    &pasnd_card_desc,
#endif

#ifdef __MACSND_ENABLED__
    &ca_card_desc,
#endif

#if TARGET_OS_IPHONE
    &au_card_desc,
#endif

#ifdef __MAC_AQ_ENABLED__
    &aq_card_desc,
#endif

#ifdef __ANDROID__
    &android_native_snd_card_desc,
    &android_native_snd_opensles_card_desc,
    &msandroid_sound_card_desc,
#endif
#endif /* MS2_FILTERS */
    NULL};

#ifdef VIDEO_ENABLED

#ifdef MS2_FILTERS

#ifdef HAVE_LINUX_VIDEODEV_H
extern MSWebCamDesc v4l_desc;
#endif

#ifdef HAVE_LINUX_VIDEODEV2_H
extern MSWebCamDesc v4l2_card_desc;
#endif

#ifdef _WIN32
extern MSWebCamDesc ms_mfoundationcap_desc;
extern MSWebCamDesc ms_vfw_cam_desc;
#endif

#if defined(_WIN32) && defined(HAVE_DIRECTSHOW)
extern MSWebCamDesc ms_directx_cam_desc;
#endif

#if defined(__MINGW32__) || defined(HAVE_DIRECTSHOW)
extern MSWebCamDesc ms_dshow_cam_desc;
#endif

#if TARGET_OS_MAC
extern MSWebCamDesc ms_v4m_cam_desc;
#endif

extern MSWebCamDesc static_image_desc;

extern MSWebCamDesc ms_mire_webcam_desc;
#ifdef __ANDROID__
extern MSWebCamDesc ms_android_video_capture_desc;
extern MSFilterDesc ms_MediaCodecH264Decoder_desc;
extern MSFilterDesc ms_MediaCodecH264Encoder_desc;
extern MSFilterDesc ms_MediaCodecH265Decoder_desc;
extern MSFilterDesc ms_MediaCodecH265Encoder_desc;
#endif

#if TARGET_OS_IPHONE && !TARGET_IPHONE_SIMULATOR
extern MSWebCamDesc ms_v4ios_cam_desc;
#endif

#ifdef __QNX__
extern MSWebCamDesc ms_bb10_camera_desc;
#endif

#endif /* MS2_FILTERS */

static MSWebCamDesc *ms_web_cam_descs[] = {
#ifdef MS2_FILTERS
#if defined(__ANDROID__)
    &ms_android_video_capture_desc,
#endif
#ifdef HAVE_LINUX_VIDEODEV2_H
    &v4l2_card_desc,
#endif
#ifdef HAVE_LINUX_VIDEODEV_H
    &v4l_desc,
#endif
#if defined(_WIN32)
    &ms_mfoundationcap_desc,
#endif
#if defined(_WIN32) && defined(HAVE_VFW)
    &ms_vfw_cam_desc,
#endif
#if defined(__MINGW32__) || defined(HAVE_DIRECTSHOW)
    &ms_dshow_cam_desc,
#endif
#if TARGET_OS_MAC && !TARGET_OS_IPHONE && !TARGET_IPHONE_SIMULATOR && !TARGET_OS_EMBEDDED
    &ms_v4m_cam_desc,
#endif
#ifdef __QNX__
    &ms_bb10_camera_desc,
#endif
#if TARGET_OS_IPHONE && !TARGET_IPHONE_SIMULATOR
    &ms_v4ios_cam_desc,
#endif
    &ms_mire_webcam_desc,
    &static_image_desc,
#endif /*MS2_FILTERS */
    NULL};

#endif

void ms_factory_init_voip(MSFactory *obj) {
	MSSndCardManager *cm;
	int i;

	if (obj->voip_initd) return;

	ms_srtp_init();
	obj->devices_info = ms_devices_info_new();
#ifdef __ANDROID__
	obj->android_sound_utils = ms_android_sound_utils_create(obj);
#endif

#if defined(VIDEO_ENABLED) && defined(MS2_FILTERS) && !defined(NO_FFMPEG) && defined(HAVE_LIBAVCODEC_AVCODEC_H)
	ms_ffmpeg_check_init();
	__register_ffmpeg_encoders_if_possible(obj);
	__register_ffmpeg_h264_decoder_if_possible(obj);
#endif

#if defined(__APPLE__) && defined(VIDEO_ENABLED)
	_register_videotoolbox_if_supported(obj);
#endif

#if defined(__ANDROID__) && defined(VIDEO_ENABLED)
	if (AMediaImage_isAvailable()) {
		if (AMediaCodec_checkCodecAvailability("video/avc")) {
			ms_factory_register_filter(obj, &ms_MediaCodecH264Decoder_desc);
			ms_factory_register_filter(obj, &ms_MediaCodecH264Encoder_desc);
		}
		if (AMediaCodec_checkCodecAvailability("video/hevc")) {
			ms_factory_register_filter(obj, &ms_MediaCodecH265Decoder_desc);
			ms_factory_register_filter(obj, &ms_MediaCodecH265Encoder_desc);
		}
	}
#endif

	/* register builtin VoIP MSFilter's */
	for (i = 0; ms_voip_filter_descs[i] != NULL; i++) {
		ms_factory_register_filter(obj, ms_voip_filter_descs[i]);
	}

	cm = ms_snd_card_manager_new();
	ms_message("Registering all soundcard handlers");
	cm->factory = obj;
	obj->sndcardmanager = cm;
	for (i = 0; ms_snd_card_descs[i] != NULL; i++) {
		ms_snd_card_manager_register_desc(cm, ms_snd_card_descs[i]);
	}

	{
		MSWebCamManager *wm;
		wm = ms_web_cam_manager_new();
		wm->factory = obj;
		obj->wbcmanager = wm;
#ifdef VIDEO_ENABLED
		ms_message("Registering all webcam handlers");
		for (i = 0; ms_web_cam_descs[i] != NULL; i++) {
			ms_web_cam_manager_register_desc(wm, ms_web_cam_descs[i]);
		}
#endif
	}

#ifdef VIDEO_ENABLED
	{
		MSVideoPresetsManager *vpm = ms_video_presets_manager_new(obj);
		register_video_preset_high_fps(vpm);
	}
	// register all ms2 implemented offer/answer
	ms_factory_register_offer_answer_provider(obj, &h264_offer_answer_provider);
#endif

#if defined(__ANDROID__) && defined(VIDEO_ENABLED)
	{
		if (!libmsandroiddisplay_init(obj)) {
			libmsandroiddisplaybad_init(obj);
		}
		libmsandroidopengldisplay_init(obj);
		libmsandroidtexturedisplay_init(obj);
	}
#endif
	obj->voip_initd = TRUE;
	obj->voip_uninit_func = ms_factory_uninit_voip;
	ms_message("ms_factory_init_voip() done");
}

void ms_factory_uninit_voip(MSFactory *obj) {
	if (obj->voip_initd) {
		ms_snd_card_manager_destroy(obj->sndcardmanager);
		obj->sndcardmanager = NULL;
#ifdef VIDEO_ENABLED
		ms_web_cam_manager_destroy(obj->wbcmanager);
		obj->wbcmanager = NULL;
		ms_video_presets_manager_destroy(obj->video_presets_manager);
#endif
		ms_srtp_shutdown();
		if (obj->devices_info) {
			ms_devices_info_free(obj->devices_info);
			obj->devices_info = NULL;
		}
#ifdef __ANDROID__
		if (obj->android_sound_utils) {
			ms_android_sound_utils_release(obj->android_sound_utils);
			obj->android_sound_utils = NULL;
		}
#endif
		obj->voip_initd = FALSE;
	}
}

MSFactory *ms_factory_new_with_voip(void) {
	MSFactory *f = ms_factory_new();
	ms_factory_init_voip(f);
	ms_factory_init_plugins(f);
	return f;
}

MSFactory *ms_factory_new_with_voip_and_directories(const char *plugins_dir, const char *images_dir) {
	MSFactory *f = ms_factory_new();
	ms_factory_set_plugins_dir(f, plugins_dir);
	ms_factory_set_image_resources_dir(f, images_dir);
	ms_factory_init_voip(f);
	ms_factory_init_plugins(f);
	return f;
}

PayloadType *ms_offer_answer_context_match_payload(MSOfferAnswerContext *context,
                                                   const MSList *local_payloads,
                                                   const PayloadType *remote_payload,
                                                   const MSList *remote_payloads,
                                                   bool_t is_reading) {
	return context->match_payload(context, local_payloads, remote_payload, remote_payloads, is_reading);
}

MSOfferAnswerContext *ms_offer_answer_create_simple_context(MSPayloadMatcherFunc func) {
	MSOfferAnswerContext *ctx = ms_new0(MSOfferAnswerContext, 1);
	ctx->match_payload = func;
	ctx->destroy = (void (*)(MSOfferAnswerContext *))ms_free;
	return ctx;
}

void ms_offer_answer_context_destroy(MSOfferAnswerContext *ctx) {
	if (ctx->destroy) ctx->destroy(ctx);
}

#ifdef _MSC_VER
#pragma warning(disable : 4996)
#else
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#endif

static int ms_voip_ref = 0;
void ms_voip_init() {
	if (ms_voip_ref++ > 0) {
		ms_message("Skipping ms_voip_init, because [%i] ref", ms_voip_ref);
		return;
	}
	ms_factory_init_voip(ms_factory_get_fallback());
}

void ms_voip_exit() {
	if (--ms_voip_ref > 0) {
		ms_message("Skipping ms_voip_exit, still [%i] ref", ms_voip_ref);
		return;
	}
	ms_factory_uninit_voip(ms_factory_get_fallback());
}

#ifdef __cplusplus
}
#endif
