namespace Yodo1.MAS
{
    using UnityEngine;
    using System;
    using System.Collections.Generic;
    using UnityEngine.SceneManagement;
    using UnityEngine.EventSystems;

    public class Yodo1U3dMasCallback : MonoBehaviour
    {
        #region Instance & SDK State

        private const int FLAG_INITIALIZE = 0;
        private const int FLAG_AD_EVENT = 1;
        private const int FLAG_APP_EVENT = 2;
        private const int FLAG_UMP_EVENT = 3;

        private const int EVENT_INITIALIZE_FAILURE = 0;
        private const int EVENT_INITIALIZE_SUCCESS = 1;

        private const int EVENT_APP_FOREGROUND = 1;

        static bool _initialized = false;
        static Yodo1MasSdkConfiguration configuration = null;

        public enum AdType
        {
            Rewarded = 1,
            Interstitial = 2,
            Banner = 3,
            Native = 4,
            RewardedInterstitial = 5,
            AppOpen = 6,
        }

        public static Yodo1U3dMasCallback Instance { get; private set; }

        public string SdkMethodName
        {
            get
            {
                return "Yodo1U3dMasCallbackResult";
            }
        }

        public string SdkObjectName
        {
            get
            {
                return gameObject.name;
            }
        }

        public static bool isInitialized()
        {
            return _initialized;
        }

        public static Yodo1MasSdkConfiguration GetSdkConfiguration()
        {
            return configuration;
        }

        #endregion

        #region Events

        private static bool CanInvokeEvent(System.Delegate evt)
        {
            if (evt == null) return false;

            if (evt.GetInvocationList().Length > 5)
            {
            }

            return true;
        }

        private static void InvokeEvent(System.Action evt)
        {
            if (!CanInvokeEvent(evt)) return;

            System.Delegate[] handlers = evt.GetInvocationList();
            for (int i = 0; i < handlers.Length; i++)
            {
                try
                {
                    ((System.Action)handlers[i])();
                }
                catch (Exception e)
                {
                    LogSubscriberException(e);
                }
            }
        }

        public static void InvokeEvent<T>(System.Action<T> evt, T param)
        {
            if (!CanInvokeEvent(evt)) return;

            System.Delegate[] handlers = evt.GetInvocationList();
            for (int i = 0; i < handlers.Length; i++)
            {
                try
                {
                    ((System.Action<T>)handlers[i])(param);
                }
                catch (Exception e)
                {
                    LogSubscriberException(e);
                }
            }
        }

        public static void InvokeEvent<T1, T2>(System.Action<T1, T2> evt, T1 param1, T2 param2)
        {
            if (!CanInvokeEvent(evt)) return;

            System.Delegate[] handlers = evt.GetInvocationList();
            for (int i = 0; i < handlers.Length; i++)
            {
                try
                {
                    ((System.Action<T1, T2>)handlers[i])(param1, param2);
                }
                catch (Exception e)
                {
                    LogSubscriberException(e);
                }
            }
        }

        // A throwing subscriber must not abort the ad state machine or a ShowAd() call:
        // for AdOpening that would leave the game paused with no native show in flight.
        private static void LogSubscriberException(Exception e)
        {
            Yodo1U3dLog.LogError("An ad event subscriber threw; the exception was isolated to keep the SDK state consistent: " + e);
        }

        // Fired when the SDK has finished initializing
        private static System.Action<bool, Yodo1U3dAdError> _onSdkInitializedEvent;
        public static event System.Action<bool, Yodo1U3dAdError> OnSdkInitializedEvent
        {
            add
            {
                _onSdkInitializedEvent += value;
            }
            remove
            {
                _onSdkInitializedEvent -= value;
            }
        }

        private static System.Action<Yodo1MasSdkConfiguration, Yodo1U3dAdError> _onSdkInitializationEvent;
        public static event System.Action<Yodo1MasSdkConfiguration, Yodo1U3dAdError> OnSdkInitializationEvent
        {
            add
            {
                _onSdkInitializationEvent += value;
            }
            remove
            {
                _onSdkInitializationEvent -= value;
            }
        }


        private static System.Action _onAppEnterForegroundEvent;
        public static event System.Action OnAppEnterForegroundEvent
        {
            add
            {
                _onAppEnterForegroundEvent += value;
            }
            remove
            {
                _onAppEnterForegroundEvent -= value;
            }
        }

        private static System.Action<Yodo1U3dAdError> _onUmpCompletionEvent;
        public static event System.Action<Yodo1U3dAdError> OnUmpCompletionEvent
        {
            add
            {
                _onUmpCompletionEvent += value;
            }
            remove
            {
                _onUmpCompletionEvent -= value;
            }
        }

        #endregion

        #region Obsoleted Ad Delegates

        private static System.Action _onBannerAdOpenedEvent;
        private static System.Action<Yodo1U3dAdError> _onBannerAdErrorEvent;
        private static System.Action _onBannerAdClosedEvent;
        [System.Obsolete("Please use `Yodo1U3dBannerAdView` instead", true)]
        public class Banner
        {
            public static event System.Action OnAdOpenedEvent
            {
                add
                {
                    _onBannerAdOpenedEvent += value;
                }
                remove
                {
                    _onBannerAdOpenedEvent -= value;
                }
            }

            public static event System.Action<Yodo1U3dAdError> OnAdErrorEvent
            {
                add
                {
                    _onBannerAdErrorEvent += value;
                }
                remove
                {
                    _onBannerAdErrorEvent -= value;
                }
            }

            public static event System.Action OnAdClosedEvent
            {
                add
                {
                    _onBannerAdClosedEvent += value;
                }
                remove
                {
                    _onBannerAdClosedEvent -= value;
                }
            }
        }

        private static System.Action _onInterstitialAdOpeningEvent;
        private static System.Action _onInterstitialAdOpenedEvent;
        private static System.Action _onInterstitialAdClosedEvent;
        private static System.Action<Yodo1U3dAdError> _onInterstitialAdErrorEvent;

        [Obsolete("Please use Yodo1U3dInterstitialAd.GetInstance()", true)]
        public class Interstitial
        {
            public static event System.Action OnAdOpeningEvent
            {
                add
                {
                    _onInterstitialAdOpeningEvent += value;
                }
                remove
                {
                    _onInterstitialAdOpeningEvent -= value;
                }
            }

            /**
             * Fired when an interstitial ad is displayed (may not be received by Unity until the interstitial ad closes).
             */
            [Obsolete("Please use Yodo1U3dInterstitialAd.GetInstance().OnAdOpenedEvent", true)]
            public static event System.Action OnAdOpenedEvent
            {
                add
                {
                    _onInterstitialAdOpenedEvent += value;
                }
                remove
                {
                    _onInterstitialAdOpenedEvent -= value;
                }
            }
            [Obsolete("Please use Yodo1U3dInterstitialAd.GetInstance().OnAdClosedEvent", true)]
            public static event System.Action OnAdClosedEvent
            {
                add
                {
                    _onInterstitialAdClosedEvent += value;
                }
                remove
                {
                    _onInterstitialAdClosedEvent -= value;
                }
            }

            [Obsolete("Please use Yodo1U3dInterstitialAd.GetInstance().OnAdOpenFailedEvent", true)]
            public static event System.Action<Yodo1U3dAdError> OnAdErrorEvent
            {
                add
                {
                    _onInterstitialAdErrorEvent += value;
                }
                remove
                {
                    _onInterstitialAdErrorEvent -= value;
                }
            }
        }

        private static System.Action _onRewardedAdOpenedEvent;
        private static System.Action _onRewardedAdClosedEvent;
        private static System.Action _onRewardedAdReceivedRewardEvent;
        private static System.Action<Yodo1U3dAdError> _onRewardedAdErrorEvent;

        [Obsolete("Please use Yodo1U3dRewardAd.GetInstance()", true)]
        public class Rewarded
        {
            /**
             * Fired when an rewarded ad is displayed (may not be received by Unity until the rewarded ad closes).
             */
            [Obsolete("Please use Yodo1U3dRewardAd.GetInstance().OnAdOpenedEvent", true)]
            public static event System.Action OnAdOpenedEvent
            {
                add
                {
                    _onRewardedAdOpenedEvent += value;
                }
                remove
                {
                    _onRewardedAdOpenedEvent -= value;
                }
            }
            [Obsolete("Please use Yodo1U3dRewardAd.GetInstance().OnAdClosedEvent", true)]
            public static event System.Action OnAdClosedEvent
            {
                add
                {
                    _onRewardedAdClosedEvent += value;
                }
                remove
                {
                    _onRewardedAdClosedEvent -= value;
                }
            }
            [Obsolete("Please use Yodo1U3dRewardAd.GetInstance().OnAdEarnedEvent", true)]
            public static event System.Action OnAdReceivedRewardEvent
            {
                add
                {
                    _onRewardedAdReceivedRewardEvent += value;
                }
                remove
                {
                    _onRewardedAdReceivedRewardEvent -= value;
                }
            }

            [Obsolete("Please use Yodo1U3dRewardAd.GetInstance().OnAdOpenFailedEvent", true)]
            public static event System.Action<Yodo1U3dAdError> OnAdErrorEvent
            {
                add
                {
                    _onRewardedAdErrorEvent += value;
                }
                remove
                {
                    _onRewardedAdErrorEvent -= value;
                }
            }
        }

        #endregion

        #region Unity Lifecycle

        public void Awake()
        {
            if (Instance == null)
            {
                Instance = this;
                DontDestroyOnLoad(gameObject);
            }
        }
#if UNITY_EDITOR
        private void OnEnable()
        {
            SceneManager.sceneLoaded += OnSceneLoaded;
        }
#endif

        private void OnDisable()
        {
#if UNITY_EDITOR
            SceneManager.sceneLoaded -= OnSceneLoaded;
#endif
            HandleCallbackTeardown("disabled");
        }

        private void OnDestroy()
        {
            HandleCallbackTeardown("destroyed");
            if (Instance == this)
            {
                Instance = null;
            }
        }

        // Without Update() and native messages there is no way back to UnPause(), so tearing
        // the callback object down mid-ad must restore the game immediately.
        private void HandleCallbackTeardown(string reason)
        {
            if (!_isPaused)
            {
                return;
            }

            Yodo1U3dLog.LogWarning("Yodo1U3dMasCallback was " + reason
                + " while a fullscreen ad was showing; restoring the game state.");
            _adSessionTerminated = true;
            _adSessionOpenAtBackground = false;
            UnPause();
        }

#if UNITY_EDITOR
        void OnSceneLoaded(Scene scene, LoadSceneMode mode)
        {
#if UNITY_2023_2_OR_NEWER
            EventSystem sceneEventSystem = FindFirstObjectByType<EventSystem>();
#else
            EventSystem sceneEventSystem = FindObjectOfType<EventSystem>();
#endif
            if (GameObject.Find("Yodo1AdCanvas") == null)
            {
                Yodo1EditorAds.AdHolder = Instantiate(Resources.Load("SampleAds/AdHolder") as GameObject);
                Yodo1EditorAds.AdHolder.name = "Yodo1AdCanvas";
                Yodo1EditorAds.AdHolderCanvas = Yodo1EditorAds.AdHolder.transform.GetChild(0).GetComponent<Canvas>();
                Yodo1EditorAds.AdHolderCanvas.sortingOrder = Yodo1EditorAds.HighestOrder;
            }
            if (sceneEventSystem == null)
            {
                var eventSystem = new GameObject("EventSystem", typeof(EventSystem), typeof(StandaloneInputModule));
            }
            Yodo1EditorAds.InitializeAds();

        }
#endif

        #endregion

        #region Pause game

        private static bool _autoPauseGame = true;
        private static bool _isPaused = false;
        private static float _savedTimeScale = 1f;
        private static float _savedAudioVolume = 1f;
        private static bool _savedAudioPause = false;

        // Watchdog timeout (foreground seconds) for a fullscreen ad that never reports a terminal
        // event; last-resort safety net for a lost AdClosed/AdOpenFail callback. <= 0 disables it.
        private const float DEFAULT_PAUSE_TIMEOUT_SECONDS = 300f; // 5 minutes
        private static float _pauseTimeoutSeconds = DEFAULT_PAUSE_TIMEOUT_SECONDS;
        private static float _pauseDeadlineRealtime = 0f; // realtime deadline; 0 = disarmed

        // Time spent in the background is excluded from the watchdog, and the watchdog never
        // fires while the app is backgrounded.
        private static bool _appBackgrounded = false;
        private static float _backgroundedAtRealtime = 0f;
        private static bool _inAppPause = false;

        // On Android, returning to the foreground while a fullscreen ad session is still open
        // means the ad Activity is gone but its terminal callback was lost. Force-resume shortly
        // after queued callbacks have had a chance to arrive.
        private const float ANDROID_RESUME_GRACE_SECONDS = 2f;

        // Whether a fullscreen ad session was already open when the app left the foreground.
        // Only such a session can have lost its terminal callback to an Activity transition;
        // an ad shown after returning (e.g. App Open) must not be force-resumed.
        private static bool _adSessionOpenAtBackground = false;

        // True once the current fullscreen ad session produced a terminal event. Guards against a
        // late/reordered AdOpened arriving after AdClosed re-pausing the game with no way back.
        private static bool _adSessionTerminated = true;

        public static void SetAutoPauseGame(bool autoPauseGame)
        {
            _autoPauseGame = autoPauseGame;
            if (!autoPauseGame && _isPaused)
            {
                // Opting out while an ad is showing must not leave the game frozen.
                Instance?.UnPause();
            }
            PrintAutoPauseGameInfo();
        }

        /// <summary>
        /// Sets the auto-pause watchdog timeout in seconds of foreground time. &lt;= 0 disables it
        /// (default 300). Takes effect immediately, including while a fullscreen ad is showing.
        /// </summary>
        public static void SetAutoPauseTimeout(float seconds)
        {
            _pauseTimeoutSeconds = seconds;
            if (_isPaused)
            {
                ArmWatchdog();
            }
        }

        /// <summary>
        /// Marks the start of a fullscreen ad session. Call unconditionally on every show request,
        /// even when IsLoaded() is false (AdOpening is skipped then, so a later AdOpened must not
        /// be treated as stale).
        /// </summary>
        public static void BeginAdSession()
        {
            _adSessionTerminated = false;
            // The snapshot describes the previous session; a new show request (possibly made
            // while backgrounded with runInBackground) must not inherit its fast-path arming.
            _adSessionOpenAtBackground = false;
            if (_isPaused)
            {
                // A new show request while paused starts a new session; the previous session's
                // deadline must not force-resume the new ad.
                ArmWatchdog();
            }
        }

        /// <summary>
        /// Pauses on AdOpening/AdOpened, resumes on AdClosed/AdOpenFail.
        /// </summary>
        public static void HandleFullscreenAdLifecycle(Yodo1U3dAdEvent adEvent)
        {
            switch (adEvent)
            {
                case Yodo1U3dAdEvent.AdOpening:
                    Instance?.Pause();
                    break;
                case Yodo1U3dAdEvent.AdOpened:
                    // Ignore a stale AdOpened arriving after the session already ended.
                    if (!_adSessionTerminated)
                    {
                        Instance?.Pause();
                    }
                    break;
                case Yodo1U3dAdEvent.AdOpenFail:
                case Yodo1U3dAdEvent.AdClosed:
                    _adSessionTerminated = true;
                    Instance?.UnPause();
                    break;
            }
        }

        public void Pause()
        {
            if (!_autoPauseGame || _isPaused)
            {
                return;
            }

            _savedTimeScale = Time.timeScale;
            _savedAudioVolume = AudioListener.volume;
            _savedAudioPause = AudioListener.pause;
            _isPaused = true;

            Time.timeScale = 0;
            AudioListener.volume = 0;
            AudioListener.pause = true;

            ArmWatchdog();
        }

        public void UnPause()
        {
            // _autoPauseGame only gates pausing. Restoring must always succeed, otherwise
            // disabling auto-pause mid-ad would leave the game frozen forever.
            if (!_isPaused)
            {
                return;
            }

            _isPaused = false;
            _pauseDeadlineRealtime = 0f;

            Time.timeScale = _savedTimeScale;
            AudioListener.volume = _savedAudioVolume;
            AudioListener.pause = _savedAudioPause;
        }

        private static void ArmWatchdog()
        {
            _pauseDeadlineRealtime = _pauseTimeoutSeconds > 0f
                ? Time.realtimeSinceStartup + _pauseTimeoutSeconds
                : 0f;
        }

        // If the foreground lifecycle callback is lost, _appBackgrounded would suppress the
        // watchdog forever. Application.isFocused is a direct query and can self-heal on mobile;
        // the delay avoids racing the real callback right after backgrounding.
        private const float FOCUS_FALLBACK_MIN_BACKGROUND_SECONDS = 1f;

        private static bool IsMobilePlatform
        {
            get
            {
                RuntimePlatform platform = Application.platform;
                return platform == RuntimePlatform.Android || platform == RuntimePlatform.IPhonePlayer;
            }
        }

        /// <summary>Watchdog: force-resumes if no terminal ad event arrived within the foreground timeout.</summary>
        private void Update()
        {
            if (!_isPaused)
            {
                return;
            }

            if (_appBackgrounded && IsMobilePlatform && Application.isFocused
                && Time.realtimeSinceStartup - _backgroundedAtRealtime >= FOCUS_FALLBACK_MIN_BACKGROUND_SECONDS)
            {
                Yodo1U3dLog.LogWarning("Auto-pause: the app reports focus but the foreground callback was lost; "
                    + "clearing the background state.");
                _inAppPause = false;
                HandleAppVisibility(true);
            }

            if (_appBackgrounded || _pauseDeadlineRealtime <= 0f)
            {
                return;
            }

            if (Time.realtimeSinceStartup > _pauseDeadlineRealtime)
            {
                Yodo1U3dLog.LogWarning("Auto-pause watchdog fired after " + _pauseTimeoutSeconds +
                    "s of foreground time without an ad close/fail callback; force-resuming the game.");

                _adSessionTerminated = true;
                UnPause();
            }
        }

        // Unity calls both when the app is backgrounded or interrupted; de-duplicated by _appBackgrounded.
        // The Android fast-path is driven by OnApplicationPause only: on Android, OnApplicationFocus
        // also toggles for the on-screen keyboard, which must not be treated as leaving the app.
        private void OnApplicationPause(bool paused)
        {
            bool wasPaused = _inAppPause;
            _inAppPause = paused;

            if (paused)
            {
                // Snapshot before the app leaves: sessions started after returning must not
                // be mistaken for an ad whose Activity closed while backgrounded.
                _adSessionOpenAtBackground = !_adSessionTerminated;
            }

            HandleAppVisibility(!paused);

            if (wasPaused && !paused)
            {
                TryArmAndroidResumeRecovery();
            }
        }

        private void OnApplicationFocus(bool focused)
        {
            HandleAppVisibility(focused);
        }

        private static void HandleAppVisibility(bool foreground)
        {
            if (!foreground)
            {
                if (_appBackgrounded)
                {
                    return;
                }

                _appBackgrounded = true;
                _backgroundedAtRealtime = Time.realtimeSinceStartup;
                return;
            }

            if (!_appBackgrounded)
            {
                return;
            }

            _appBackgrounded = false;
            if (_isPaused && _pauseDeadlineRealtime > 0f && _backgroundedAtRealtime > 0f)
            {
                // Exclude the background period from the watchdog budget.
                _pauseDeadlineRealtime += Time.realtimeSinceStartup - _backgroundedAtRealtime;
            }

            _backgroundedAtRealtime = 0f;
        }

        private static void TryArmAndroidResumeRecovery()
        {
            bool sessionWasOpen = _adSessionOpenAtBackground;
            _adSessionOpenAtBackground = false;

            if (!sessionWasOpen)
            {
                return;
            }

            if (Application.platform != RuntimePlatform.Android || !_isPaused || _adSessionTerminated ||
                _pauseDeadlineRealtime <= 0f)
            {
                return;
            }

            // The ad Activity is gone (Unity's activity resumed); give queued
            // AdClosed/AdOpenFail callbacks a moment, then force-resume if none arrived.
            _pauseDeadlineRealtime = Mathf.Min(_pauseDeadlineRealtime,
                Time.realtimeSinceStartup + ANDROID_RESUME_GRACE_SECONDS);
        }

        #endregion

        #region Safe Area Fix

        private static bool _safeAreaFixEnabled = false;

        public static void SetSafeAreaFixEnabled(bool enabled)
        {
            _safeAreaFixEnabled = enabled;
            if (Application.platform == RuntimePlatform.IPhonePlayer)
            {
#if UNITY_IPHONE
                Yodo1U3dAdsIOS.SetSafeAreaFixEnabled(enabled);
#endif
            }
            PrintSafeAreaFixInfo();
        }

        #endregion

        #region Feature Info

        public static void PrintFeaturesInfo()
        {
            PrintAutoPauseGameInfo();
            PrintSafeAreaFixInfo();
        }

        private static void PrintAutoPauseGameInfo()
        {
            if (_autoPauseGame)
            {
                Yodo1U3dLog.LogInfo("The feature of auto pause game is enabled, please call `Yodo1U3dMas.SetAutoPauseGame(false)` if you want to disable it");
            }
            else
            {
                Yodo1U3dLog.LogInfo("The feature of auto pause game is disabled, please call `Yodo1U3dMas.SetAutoPauseGame(true)` if you want to enable it");
            }
        }

        private static void PrintSafeAreaFixInfo()
        {
#if UNITY_IOS
            if (_safeAreaFixEnabled)
            {
                Yodo1U3dLog.LogInfo("The feature of safe area fix is enabled, please call `Yodo1U3dMas.SetSafeAreaFixEnabled(false)` if you want to disable it");
            }
            else
            {
                Yodo1U3dLog.LogInfo("The feature of safe area fix is disabled, please call `Yodo1U3dMas.SetSafeAreaFixEnabled(true)` if you want to enable it");
            }
#endif
        }

        #endregion

        #region Native Event Handling

        public void Yodo1U3dMasCallbackResult(string result)
        {
            Yodo1U3dLog.LogVerbose("SDK callback result: " + result);
            Dictionary<string, object> obj = (Dictionary<string, object>)Yodo1JSON.Deserialize(result);
            if (obj == null)
            {
                return;
            }

            if (!obj.ContainsKey("flag") || !obj.ContainsKey("data"))
            {
                return;
            }

            string jsonData = obj["data"].ToString();
            Dictionary<string, object> dataDic = (Dictionary<string, object>)Yodo1JSON.Deserialize(jsonData);
            if (dataDic == null)
            {
                return;
            }

            int flag = int.Parse(obj["flag"].ToString());
            if (flag == FLAG_INITIALIZE)
            {
                bool success = false;
                Yodo1U3dAdError error = null;

                if (dataDic.ContainsKey("success"))
                {
                    success = int.Parse(dataDic["success"].ToString()) == EVENT_INITIALIZE_SUCCESS;
                }
                if (dataDic.ContainsKey("configuration"))
                {
                    configuration = Yodo1MasSdkConfiguration.CreateWithJson(dataDic["configuration"].ToString());
                }
                if (dataDic.ContainsKey("error"))
                {
                    error = Yodo1U3dAdError.CreateWithJson(dataDic["error"].ToString());
                }
                else
                {
                    error = new Yodo1U3dAdError();
                }

                Yodo1U3dLog.LogInfo("SDK initialized: " + (success ? "success" : "failed")
                    + (error != null ? ", error: " + error.Message : "")
                    + (configuration != null
                        ? ", geograph: " + configuration.ConsentFlowUserGeography
                          + ", userAge: " + configuration.UserAge
                          + ", att: " + configuration.AttTrackingStatus
                        : ""));

                _initialized = success;
                if (_onSdkInitializedEvent != null && _onSdkInitializedEvent.GetInvocationList().Length > 0)
                {
                    InvokeEvent(_onSdkInitializedEvent, success, error);
                }
                if (_onSdkInitializationEvent != null && _onSdkInitializationEvent.GetInvocationList().Length > 0)
                {
                    InvokeEvent(_onSdkInitializationEvent, configuration, error);
                }
            }
            else if (flag == FLAG_AD_EVENT)
            {
                AdType type = AdType.Rewarded;
                if (dataDic.ContainsKey("type"))
                {
                    type = (AdType)int.Parse(dataDic["type"].ToString());
                }
                else
                {
                    return;
                }
                Yodo1U3dAdEvent adEvent = Yodo1U3dAdEvent.AdError;
                if (dataDic.ContainsKey("code"))
                {
                    adEvent = (Yodo1U3dAdEvent)int.Parse(dataDic["code"].ToString());
                }
                string message;
                if (dataDic.ContainsKey("message"))
                {
                    message = dataDic["message"].ToString();
                }
                Yodo1U3dAdError adError = null;
                if (dataDic.ContainsKey("error"))
                {
                    adError = Yodo1U3dAdError.CreateWithJson(Yodo1JSON.Serialize(dataDic["error"]));
                }

                Yodo1U3dAdValue adValue = null;
                if (dataDic.ContainsKey("adValue"))
                {
                    adValue = Yodo1U3dAdValue.CreateWithJson(Yodo1JSON.Serialize(dataDic["adValue"]));
                }

                string indexId = string.Empty;
                if (dataDic.ContainsKey("indexId"))
                {
                    indexId = dataDic["indexId"].ToString();
                }

                switch (type)
                {
                    case AdType.Rewarded:
                        {
                            Yodo1U3dRewardAd.DispatchAdEvent(adEvent, adError, adValue);
                        }
                        break;
                    case AdType.Interstitial:
                        {
                            Yodo1U3dInterstitialAd.DispatchAdEvent(adEvent, adError, adValue);
                        }
                        break;
                    case AdType.Banner:
                        {
                            Yodo1U3dBannerAdView.DispatchAdEvent(adEvent, adError, indexId, adValue);
                        }
                        break;
                    case AdType.Native:
                        {
                            Yodo1U3dNativeAdView.DispatchAdEvent(adEvent, adError, indexId, adValue);
                        }
                        break;
                    case AdType.RewardedInterstitial:
                        {
                        }
                        break;
                    case AdType.AppOpen:
                        {
                            Yodo1U3dAppOpenAd.DispatchAdEvent(adEvent, adError, adValue);
                        }
                        break;
                    default:
                        break;
                }
            }
            else if (flag == FLAG_APP_EVENT)
            {
                if (dataDic.ContainsKey("status"))
                {
                    int status = int.Parse(dataDic["status"].ToString());
                    if (status == EVENT_APP_FOREGROUND)
                    {
                        InvokeEvent(_onAppEnterForegroundEvent);
                    }
                }
            }
            else if (flag == FLAG_UMP_EVENT)
            {
                Yodo1U3dAdError error = null;
                if (dataDic.ContainsKey("error"))
                {
                    error = Yodo1U3dAdError.CreateWithJson(dataDic["error"].ToString());
                }
                InvokeEvent(_onUmpCompletionEvent, error);
            }
        }

        #endregion

        #region Editor Event Forwarding

        public static void ForwardEvent(string val)
        {
            if (string.Equals(val, "onSdkInitializedEvent"))
            {
                Yodo1U3dLog.LogInfo("SDK initialized: success (editor)");

                Yodo1U3dAdError error = new Yodo1U3dAdError();
                error.Message = "No error in initialization";
                _initialized = true;
                InvokeEvent(_onSdkInitializedEvent, true, error);

                Yodo1MasSdkConfiguration configuration = new Yodo1MasSdkConfiguration();
                InvokeEvent(_onSdkInitializationEvent, configuration, error);
            }
            else if (string.Equals(val, "onRewardedAdLoadedEvent"))
            {
                Yodo1U3dRewardAd.DispatchAdEvent(Yodo1U3dAdEvent.AdLoaded, null);
            }
            else if (string.Equals(val, "onRewardedAdLoadFailedEvent"))
            {
                Yodo1U3dAdError error = new Yodo1U3dAdError();
                error.Message = "No ads found.";
                Yodo1U3dRewardAd.DispatchAdEvent(Yodo1U3dAdEvent.AdLoadFail, error);
            }
            else if (string.Equals(val, "onRewardedAdOpenedEvent"))
            {
                InvokeEvent(_onRewardedAdOpenedEvent);
                Yodo1U3dRewardAd.DispatchAdEvent(Yodo1U3dAdEvent.AdOpened, null);
            }
            else if (string.Equals(val, "onRewardedAdOpenFailedEvent"))
            {
                Yodo1U3dAdError error = new Yodo1U3dAdError();
                error.Message = "Ad failed to play.";
                Yodo1U3dRewardAd.DispatchAdEvent(Yodo1U3dAdEvent.AdOpenFail, error);
            }
            else if (string.Equals(val, "onRewardedAdClickedEvent"))
            {
                Yodo1U3dRewardAd.DispatchAdEvent(Yodo1U3dAdEvent.AdClicked, null);
            }
            else if (string.Equals(val, "onRewardedAdClosedEvent"))
            {
                InvokeEvent(_onRewardedAdClosedEvent);
                Yodo1U3dRewardAd.DispatchAdEvent(Yodo1U3dAdEvent.AdClosed, null);
            }
            else if (string.Equals(val, "onRewardedAdReceivedRewardEvent"))
            {
                InvokeEvent(_onRewardedAdReceivedRewardEvent);
                Yodo1U3dRewardAd.DispatchAdEvent(Yodo1U3dAdEvent.AdReward, null);
            }
            else if (string.Equals(val, "onInterstitialAdLoadedEvent"))
            {
                Yodo1U3dInterstitialAd.DispatchAdEvent(Yodo1U3dAdEvent.AdLoaded, null);
            }
            else if (string.Equals(val, "onInterstitialAdLoadFailedEvent"))
            {
                Yodo1U3dAdError error = new Yodo1U3dAdError();
                error.Message = "No Ads found.";
                Yodo1U3dInterstitialAd.DispatchAdEvent(Yodo1U3dAdEvent.AdLoadFail, error);
            }
            else if (string.Equals(val, "onInterstitialAdOpenedEvent"))
            {
                InvokeEvent(_onInterstitialAdOpenedEvent);
                Yodo1U3dInterstitialAd.DispatchAdEvent(Yodo1U3dAdEvent.AdOpened, null);
            }
            else if (string.Equals(val, "onInterstitialAdOpenFailedEvent"))
            {
                Yodo1U3dAdError error = new Yodo1U3dAdError();
                error.Message = "Ad failed to play.";
                Yodo1U3dInterstitialAd.DispatchAdEvent(Yodo1U3dAdEvent.AdOpenFail, error);
            }
            else if (string.Equals(val, "onInterstitialAdClickedEvent"))
            {
                Yodo1U3dInterstitialAd.DispatchAdEvent(Yodo1U3dAdEvent.AdClicked, null);
            }
            else if (string.Equals(val, "onInterstitialAdClosedEvent"))
            {
                InvokeEvent(_onInterstitialAdClosedEvent);
                Yodo1U3dInterstitialAd.DispatchAdEvent(Yodo1U3dAdEvent.AdClosed, null);
            }
            else if (string.Equals(val, "onAppOpenAdLoadedEvent"))
            {
                Yodo1U3dAppOpenAd.DispatchAdEvent(Yodo1U3dAdEvent.AdLoaded, null);
            }
            else if (string.Equals(val, "onAppOpenAdLoadFailedEvent"))
            {
                Yodo1U3dAdError error = new Yodo1U3dAdError();
                error.Message = "No ads found.";
                Yodo1U3dAppOpenAd.DispatchAdEvent(Yodo1U3dAdEvent.AdLoadFail, error);
            }
            else if (string.Equals(val, "onAppOpenAdOpenedEvent"))
            {
                Yodo1U3dAppOpenAd.DispatchAdEvent(Yodo1U3dAdEvent.AdOpened, null);
            }
            else if (string.Equals(val, "onAppOpenAdOpenFailedEvent"))
            {
                Yodo1U3dAdError error = new Yodo1U3dAdError();
                error.Message = "Ad failed to play.";
                Yodo1U3dAppOpenAd.DispatchAdEvent(Yodo1U3dAdEvent.AdOpenFail, error);
            }
            else if (string.Equals(val, "onAppOpenAdClickedEvent"))
            {
                Yodo1U3dAppOpenAd.DispatchAdEvent(Yodo1U3dAdEvent.AdClicked, null);
            }
            else if (string.Equals(val, "onAppOpenAdClosedEvent"))
            {
                Yodo1U3dAppOpenAd.DispatchAdEvent(Yodo1U3dAdEvent.AdClosed, null);
            }
            else if (string.Equals(val, "onBannerAdOpenedEvent"))
            {
                InvokeEvent(_onBannerAdOpenedEvent);
            }
            else if (string.Equals(val, "onBannerAdClosedEvent"))
            {
                InvokeEvent(_onBannerAdClosedEvent);
            }

        }

        #endregion
    }
}