// Cordova plugin package
package com.chadorivirtual.chadori.mobile.ironsource;

// Android packages
import android.annotation.SuppressLint;
//import android.telecom.Call;
import android.util.Log;
import android.text.TextUtils;
import android.os.AsyncTask;
import android.view.View;
import android.view.ViewGroup;
import android.view.Gravity;
import android.view.ViewGroup.LayoutParams;
import android.widget.RelativeLayout;
//import android.widget.FrameLayout;
import android.widget.LinearLayout;

// Cordova-required packages
import org.apache.cordova.CallbackContext;
import org.apache.cordova.CordovaInterface;
import org.apache.cordova.CordovaPlugin;
import org.apache.cordova.CordovaWebView;
//import org.apache.cordova.PluginResult;

// Java Utilities
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

//import org.w3c.dom.Text;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

// IronSource packages
import com.ironsource.adapters.supersonicads.SupersonicConfig;
import com.ironsource.mediationsdk.IronSource;
import com.ironsource.mediationsdk.IronSourceBannerLayout;
import com.ironsource.mediationsdk.ISBannerSize;
import com.ironsource.mediationsdk.IronSourceSegment;
// import com.ironsource.mediationsdk.integration.IntegrationHelper;
import com.ironsource.mediationsdk.logger.IronSourceError;
//import com.ironsource.mediationsdk.model.InterstitialPlacement;
import com.ironsource.mediationsdk.model.Placement;
import com.ironsource.mediationsdk.sdk.InitializationListener;
import com.ironsource.mediationsdk.sdk.InterstitialListener;
import com.ironsource.mediationsdk.sdk.OfferwallListener;
import com.ironsource.mediationsdk.sdk.RewardedVideoListener;
import com.ironsource.mediationsdk.sdk.BannerListener;
import com.ironsource.mediationsdk.sdk.SegmentListener;

public class IronSourceC extends CordovaPlugin implements
		RewardedVideoListener, InterstitialListener, OfferwallListener, SegmentListener {

	////////////////////////////////////////////////////////
	// Variables

	// Properties
	private boolean isBanner = true;
	private boolean isInterstitial = true;
	private boolean isRewardedVideo = true;
	private boolean isOfferwall = true;

	// State
	private boolean isBannerLoaded;
	private boolean isBannerShowing;

	// Hidden State
	private boolean isInitialized = false;
	private boolean bannerTop = false;
	private boolean bannerOverlap = true;
	private boolean bannerAutoShow = false;

	//** Utilities **//

	// Cordova
	private CordovaWebView webview;

	// Android
	private RelativeLayout bannerContainerLayout;
	private ViewGroup parentLayout;

	// IronSource
	private IronSourceBannerLayout mIronSourceBannerLayout;
	private IronSourceSegment mIronSegment = new IronSourceSegment();

	////////////////////////////////////////////////////////
	// Constants

	// Class name for logs.
	private static final String TAG = "IronSourceC";

	// Default user id
	private static final String FALLBACK_USER_ID = "userId";

	//** Events **//

	private static final String EVENT_INITIALIZATION = "initializationCompleted";

	private static final String EVENT_REWARDED_VIDEO_OPENED = "rewardedVideoOpened";
	private static final String EVENT_REWARDED_VIDEO_CLOSED = "rewardedVideoClosed";
	private static final String EVENT_REWARDED_VIDEO_AVAILABILITY_CHANGED = "rewardedVideoAvailabilityChanged";
	private static final String EVENT_REWARDED_VIDEO_REWARDED = "rewardedVideoRewardReceived";
	private static final String EVENT_REWARDED_VIDEO_FAILED = "rewardedVideoFailed";
	private static final String EVENT_REWARDED_VIDEO_CLICKED = "rewardedVideoClicked";
	private static final String EVENT_REWARDED_VIDEO_STARTED = "rewardedVideoStarted";
	private static final String EVENT_REWARDED_VIDEO_ENDED = "rewardedVideoEnded";

	private static final String EVENT_INTERSTITIAL_READY = "interstitialReady";
	private static final String EVENT_INTERSTITIAL_LOAD_FAILED = "interstitialLoadFailed";
	private static final String EVENT_INTERSTITIAL_OPENED = "interstitialOpened";
	private static final String EVENT_INTERSTITIAL_CLOSED = "interstitialClosed";
	private static final String EVENT_INTERSTITIAL_SHOW_SUCCEEDED = "interstitialShowSucceeded";
	private static final String EVENT_INTERSTITIAL_SHOW_FAILED = "interstitialShowFailed";
	private static final String EVENT_INTERSTITIAL_CLICKED = "interstitialClicked";

	private static final String EVENT_BANNER_LOADED = "bannerLoaded";
	private static final String EVENT_BANNER_LOAD_FAILED = "bannerLoadFailed";
	private static final String EVENT_BANNER_CLICKED = "bannerClicked";
	private static final String EVENT_BANNER_SCREEN_PRESENTED = "bannerScreenPresented";
	private static final String EVENT_BANNER_SCREEN_DISMISSED = "bannerScreenDismissed";
	private static final String EVENT_BANNER_LEFT_APPLICATION = "bannerLeftApplication";

	private static final String EVENT_OFFERWALL_AVAILABILITY_CHANGED = "offerwallAvailabilityChanged";
	private static final String EVENT_OFFERWALL_OPENED = "offerwallOpened";
	private static final String EVENT_OFFERWALL_SHOW_FAILED = "offerwallShowFailed";
	private static final String EVENT_OFFERWALL_CREDITED = "offerwallCredited";
	private static final String EVENT_OFFERWALL_CREDITS_FAILED = "offerwallCreditsFailed";
	private static final String EVENT_OFFERWALL_CLOSED = "offerwallClosed";

	private static final String EVENT_SEGMENT_RECEIVED = "segmentReceived";

	////////////////////////////////////////////////////////
	// Cordova Android

	@Override
	public void initialize(final CordovaInterface cordova, final CordovaWebView webView) {
		this.webview = webView;
		this.isBannerLoaded = false;
		this.isBannerShowing = false;
		super.initialize(cordova, webView);
	}

	@Override
	public boolean execute(final String action, JSONArray args, final CallbackContext callbackContext) {

		final IronSourceC self = this;

		cordova.getActivity().runOnUiThread(() -> {

			switch (action)
			{
				//** General **//
				case "validateIntegration":
					self.validateIntegration(callbackContext);
					break;
				case "setMode":
					self.setMode(args, callbackContext);
					break;
				case "initialize":
					self.init(args, callbackContext);
					break;
				//** Settings **//
				case "setConsent":
					self.setConsent(args, callbackContext);
					break;
				case "setCompliance":
					self.setCompliance(args, callbackContext);
					break;
				case "setChildDirected":
					self.setChildDirected(args, callbackContext);
					break;
				case "setDeviceIdOptOut":
					self.setDeviceIdOptOut(args, callbackContext);
					break;					
				case "setMetaData":
					self.setMetaData(args, callbackContext);
					break;
				case "setMetaDataBool":
					self.setMetaDataBool(args, callbackContext);
					break;	
				case "setSegment":
					self.setSegment(args, callbackContext);
					break;
				//** Settings: Rewarded Video **//
				case "setVideoServerParams":
					self.setVideoServerParams(args, callbackContext);
					break;
				case "clearVideoServerParams":
					self.clearVideoServerParams(callbackContext);
					break;
				//** Settings: Offerwall **//
				case "setOfferwallCustomParams":
					self.setOfferwallCustomParams(args, callbackContext);
					break;
				//** Rewarded Video **//
				case "setShouldTrackNetworkState":
					self.setShouldTrackNetworkState(args, callbackContext);
					break;
				case "isRewardVideoAvailable":
					self.isRewardVideoAvailable(callbackContext);
					break;
				case "showRewardedVideo":
					self.showRewardedVideo(args, callbackContext);
					break;
				case "getVideoPlacementInfo":
					self.getVideoPlacementInfo(args, callbackContext);
					break;
				case "isVideoPlacementCapped":
					self.isVideoPlacementCapped(args, callbackContext);
					break;
				case "setDynamicUserID":
					self.setDynamicUserID(args, callbackContext);
					break;
				//** Interstitial **//
				case "createInterstitial":
					self.createInterstitial(callbackContext);
					break;
				case "isInterstitialReady":
					self.isInterstitialReady(callbackContext);
					break;
				case "isInterstitialPlacementCapped":
					self.isInterstitialPlacementCapped(args, callbackContext);
					break;
				case "showInterstitial":
					self.showInterstitial(args, callbackContext);
					break;
				//** Banner **//
				case "createBanner":
					self.createBanner(args, callbackContext);
					break;
				case "showBanner":
					self.showBanner(callbackContext);
					break;
				case "hideBanner":
					self.hideBanner(callbackContext);
					break;
				case "destroyBanner":
					self.destroyBanner(callbackContext);
					break;
				case "isBannerPlacementCapped":
					self.isBannerPlacementCapped(args, callbackContext);
					break;
				//** Offerwall **//
				case "showOfferwall":
					self.showOfferwall(args, callbackContext);
					break;
				case "getOfferwallCredits":
					self.getOfferwallCredits(callbackContext);
					break;
				case "setClientSideCallbacks":
					self.setClientSideCallbacks(args, callbackContext);
					break;
				//** Tools **//
				case "getMode":
					self.getMode(callbackContext);
					break;
				case "setBannerOverlap":
					self.setBannerOverlap(args, callbackContext);
					break;
				//** Default **//
				default:
					callbackContext.error("\"" + action + "\" is not a recognized \"IronSourceC\" method.");
			}
		});

		return true;
	}

	////////////////////////////////////////////////////////
	// Listeners

	@Override
	public void onResume(final boolean multitasking) {
		super.onResume(multitasking);
		IronSource.onResume(this.cordova.getActivity());
	}

	@Override
	public void onPause(final boolean multitasking) {
		super.onPause(multitasking);
		IronSource.onPause(this.cordova.getActivity());
	}

	////////////////////////////////////////////////////////
	// Cordova Methods

	//** General **//

	private void validateIntegration(final CallbackContext callbackContext) {

		// IntegrationHelper.validateIntegration(this.cordova.getActivity());
		callbackContext.success();
	}

	private void setMode(final JSONArray args, final CallbackContext callbackContext) {

		try {
			final JSONObject mode = args.getJSONObject(0);

			this.isRewardedVideo = mode.getBoolean("reward");
			this.isInterstitial = mode.getBoolean("interstitial");
			this.isOfferwall = mode.getBoolean("offerwall");
			this.isBanner = mode.getBoolean("banner");

			callbackContext.success();

		} catch (JSONException e) {

			callbackContext.error(this.debugErrorLog("setMode"));
		}
	}

	private void init(final JSONArray args, final CallbackContext callbackContext) {

		try {

			final IronSourceC self = this;

			final String appKey = args.getString(0);
			final int userIdType = args.getInt(1); // Enum: [Advertising, IronSource, Specify]
			final String userId = args.getString(2);

			final boolean isAdvertisingId = userIdType == 0;
			final boolean isSpecified = userIdType == 2;

			// We're using an advertisingId as the user id.
			if (isAdvertisingId)
			{
				// Getting advertiser id should be done on a background thread.
				@SuppressLint("StaticFieldLeak") AsyncTask<Void, Void, String> task = new AsyncTask<Void, Void, String>() {

					@Override
					protected String doInBackground(Void... params) {
						return IronSource.getAdvertiserId(self.cordova.getActivity());
					}
					@Override
					protected void onPostExecute(String advertisingId) {
						if (isStringEmpty(advertisingId)) {

							_initIronSource(appKey, "", callbackContext);
							Log.i(TAG, "The built-in IronSource user id generator has been used. (" + "" + ")");
						}
						else
						{
							_initIronSource(appKey, advertisingId, callbackContext);
							Log.i(TAG, "The user's advertising id has been used. (" + advertisingId + ")");
						}
					}
				};
				task.execute();
			}
			// We're using the specified user id.
			else if (isSpecified)
			{
				if (isStringEmpty(userId))
				{
					_initIronSource(appKey, "", callbackContext);
					Log.i(TAG, "The built-in IronSource user id generator has been used. (" + "" + ")");
				}
				else
				{
					_initIronSource(appKey, userId, callbackContext);
					Log.i(TAG, "The user's advertising id has been used. (" + userId + ")");
				}
			}
			// We're using the built-in IronSource user id generator.
			else
			{
				_initIronSource(appKey, "", callbackContext);
				Log.i(TAG, "The built-in IronSource user id generator has been used. (" + "" + ")");
			}

		} catch (JSONException e) {

			callbackContext.error(this.debugErrorLog("init"));
			return;
		}
	}

	//** Settings **//

	private void setConsent(final JSONArray args, final CallbackContext callbackContext) {

		try {

			final boolean isGiven = args.getBoolean(0);

			IronSource.setConsent(isGiven);
			callbackContext.success();

		} catch (JSONException e) {

			callbackContext.error(this.debugErrorLog("setConsent"));
		}
	}

	private void setCompliance(final JSONArray args, final CallbackContext callbackContext) {

		try {

			final boolean toggle = args.getBoolean(0);
			final String toggleStr = String.valueOf(toggle);

			IronSource.setMetaData("do_not_sell", toggleStr);
			callbackContext.success();

		} catch (JSONException e) {
			callbackContext.error(this.debugErrorLog("setCompliance"));
		}
	}

	private void setChildDirected(final JSONArray args, final CallbackContext callbackContext) {

		try {

			final boolean toggle = args.getBoolean(0);
			final String toggleStr = String.valueOf(toggle);

			IronSource.setMetaData("is_child_directed", toggleStr);
			callbackContext.success();

		} catch (JSONException e) {
			callbackContext.error(this.debugErrorLog("setChildDirected"));
		}
	}

	private void setDeviceIdOptOut(final JSONArray args, final CallbackContext callbackContext) {

		try {

			final boolean toggle = args.getBoolean(0);
			final String toggleStr = String.valueOf(toggle);

			IronSource.setMetaData("is_deviceid_optout", toggleStr);
			callbackContext.success();

		} catch (JSONException e) {
			callbackContext.error(this.debugErrorLog("setDeviceIdOptOut"));
		}
	}	

	private void setMetaData(final JSONArray args, final CallbackContext callbackContext) {

		try {

			final String key = args.getString(0);
			final String data = args.getString(1);

			IronSource.setMetaData(key, data);
			callbackContext.success();

		} catch (JSONException e) {
			callbackContext.error(this.debugErrorLog("setMetaData"));
		}
	}

	private void setMetaDataBool(final JSONArray args, final CallbackContext callbackContext) {

		try {

			final String key = args.getString(0);
			final boolean toggle = args.getBoolean(1);
			final String toggleStr = String.valueOf(toggle);

			IronSource.setMetaData(key, toggleStr);
			callbackContext.success();

		} catch (JSONException e) {
			callbackContext.error(this.debugErrorLog("setMetaDataBool"));
		}
	}

	private void setSegment(final JSONArray args, final CallbackContext callbackContext) {

		try {

			final JSONObject data = args.getJSONObject(0);
			final JSONArray keys = data.names();

			for (int i = 0; i < keys.length(); i++) {

				final String key = keys.getString(i);

				switch (key) {

					case "segmentName":
						mIronSegment.setSegmentName(data.getString(key));
						break;
					case "age":
						mIronSegment.setAge(data.getInt(key));
						break;
					case "gender":
						mIronSegment.setGender(data.getString(key));
						break;
					case "level":
						mIronSegment.setLevel(data.getInt(key));
						break;
					case "userCreationDate":
						mIronSegment.setUserCreationDate(data.getLong(key));
						break;
					case "iapTotal":
						mIronSegment.setIAPTotal(data.getDouble(key));
						break;
					case "isPaying":
						mIronSegment.setIsPaying(data.getBoolean(key));
						break;
					default:
						mIronSegment.setCustom(key, data.getString(key));
				}
			}

			IronSource.setSegment(mIronSegment);
			callbackContext.success();

		} catch (JSONException e) {

			callbackContext.error(this.debugErrorLog("setSegment"));
		}
	}

	//** Settings: Rewarded Video **//

	private void setVideoServerParams(final JSONArray args, final CallbackContext callbackContext) {

		try {

			final JSONObject data = args.getJSONObject(0);
			final JSONArray keys = data.names();

			final Map<String, String> params = new HashMap<>();

			for (int i = 0; i < keys.length(); i++) {

				String key = keys.getString(i);
				String value = data.getString(key);

				params.put(key, value);
			}

			IronSource.setRewardedVideoServerParameters(params);
			callbackContext.success();

		} catch (JSONException e) {

			callbackContext.error(this.debugErrorLog("setVideoServerParams"));
		}
	}

	private void clearVideoServerParams(final CallbackContext callbackContext){

		IronSource.clearRewardedVideoServerParameters();
		callbackContext.success();
	}

	//** Settings: Offerwall **//

	private void setOfferwallCustomParams(final JSONArray args, final CallbackContext callbackContext) {

		try {

			final JSONObject data = args.getJSONObject(0);
			final JSONArray keys = data.names();

			final Map<String, String> params = new HashMap<>();

			for (int i = 0; i < keys.length(); i++) {

				final String key = keys.getString(i);
				final String value = data.getString(key);

				params.put(key, value);
			}

			SupersonicConfig.getConfigObj().setOfferwallCustomParams(params);
			callbackContext.success();

		} catch (JSONException e) {

			callbackContext.error(this.debugErrorLog("setOfferwallCustomParams"));
		}
	}

	//** Rewarded Video **//

	private void setShouldTrackNetworkState(final JSONArray args, final CallbackContext callbackContext) {

		try {

			final boolean track = args.getBoolean(0);

			IronSource.shouldTrackNetworkState(this.cordova.getActivity(), track);
			callbackContext.success();

		} catch (JSONException e) {

			callbackContext.error(this.debugErrorLog("setShouldTrackNetworkState"));
		}
	}

	private void isRewardVideoAvailable(final CallbackContext callbackContext) {

		try {

			final Boolean isAvailable = IronSource.isRewardedVideoAvailable();
			final String availabilityKey = "isAvailable";

			final JSONObject data = new JSONObject();
			data.put(availabilityKey, isAvailable);

			callbackContext.success(data);

		} catch (JSONException e) {

			callbackContext.error(this.debugErrorLog("isRewardVideoAvailable"));
		}
	}

	private void showRewardedVideo(final JSONArray args, final CallbackContext callbackContext) {

		try {

			final String placementName = args.getString(0);

			if (isStringEmpty(placementName))
			{
				IronSource.showRewardedVideo();
			}
			else
			{
				IronSource.showRewardedVideo(placementName);
			}

			callbackContext.success();

		} catch (JSONException e) {
			callbackContext.error(this.debugErrorLog("showRewardedVideo"));
		}
	}

	private void getVideoPlacementInfo(final JSONArray args, final CallbackContext callbackContext) {

		try {
			final String placementName = args.getString(0);
			final Placement placement = IronSource.getRewardedVideoPlacementInfo(placementName);

			final JSONObject data = new JSONObject();

			if (placement == null)
			{
				data.put("placementName", placementName);
				data.put("rewardName", "");
				data.put("rewardAmount", 0);
			}
			else
			{
				data.put("placementName", placement.getPlacementName());
				data.put("rewardName", placement.getRewardName());
				data.put("rewardAmount", placement.getRewardAmount());
			}

			callbackContext.success(data);

		} catch (JSONException e) {
			callbackContext.error(this.debugErrorLog("getVideoPlacementInfo"));
		}
	}

	private void isVideoPlacementCapped(final JSONArray args, final CallbackContext callbackContext) {

		try {
			final String placementName = args.getString(0);
			final Boolean isCapped = IronSource.isRewardedVideoPlacementCapped(placementName);

			final JSONObject data = new JSONObject();
			data.put("isCapped", isCapped);
			data.put("placementName", placementName);

			callbackContext.success(data);

		} catch (JSONException e) {
			callbackContext.error(this.debugErrorLog("isVideoPlacementCapped"));
		}
	}

	private void setDynamicUserID(final JSONArray args, final CallbackContext callbackContext){

		try {
			final String dynamicUserID = args.getString(0);

			IronSource.setDynamicUserId(dynamicUserID);

			callbackContext.success();

		} catch (JSONException e) {
			callbackContext.error(this.debugErrorLog("setDynamicUserID"));
		}
	}

	//** Interstitial **//

	private void createInterstitial(final CallbackContext callbackContext){

		IronSource.loadInterstitial();
		callbackContext.success();
	}

	private void isInterstitialReady(final CallbackContext callbackContext) {

		try {

			final boolean isAvailable = IronSource.isInterstitialReady();
			final String availabilityKey = "isReady";

			final JSONObject data = new JSONObject();
			data.put(availabilityKey, isAvailable);

			callbackContext.success(data);

		} catch (JSONException e) {

			callbackContext.error(this.debugErrorLog("isInterstitialReady"));
		}
	}

	private void isInterstitialPlacementCapped(final JSONArray args, final CallbackContext callbackContext){

		try {
			final String placementName = args.getString(0);
			final boolean isCapped = IronSource.isInterstitialPlacementCapped(placementName);

			final JSONObject data = new JSONObject();
			data.put("isCapped", isCapped);
			data.put("placementName", placementName);

			callbackContext.success(data);

		} catch (JSONException e) {
			callbackContext.error(this.debugErrorLog("isInterstitialPlacementCapped"));
		}
	}

	private void showInterstitial(final JSONArray args, final CallbackContext callbackContext){

		try {

			final String placementName = args.getString(0);

			if (isStringEmpty(placementName))
			{
				IronSource.showInterstitial();
			}
			else
			{
				IronSource.showInterstitial(placementName);
			}

			callbackContext.success();

		} catch (JSONException e) {
			callbackContext.error(this.debugErrorLog("showInterstitial"));
		}
	}

	//** Banner **//

	private void createBanner(final JSONArray args, final CallbackContext callbackContext){

		try {

			final String placementName = args.getString(0);

			final boolean top = args.getBoolean(1);

			final int size = args.getInt(2); // Enum: [Banner, Large, Rectangle, Smart, Custom]
			final int width = args.getInt(3); // Custom Width
			final int height = args.getInt(4); // Custom Height

			final boolean autoShow = args.getBoolean(5);
			final boolean isAdaptiveBanner = args.getBoolean(6);

			ISBannerSize bannerSize;

			switch (size) {

				case 0:
					bannerSize = ISBannerSize.BANNER;
					break;
				case 1:
					bannerSize = ISBannerSize.LARGE;
					break;
				case 2:
					bannerSize = ISBannerSize.RECTANGLE;
					break;
				case 3:
					bannerSize = ISBannerSize.SMART;
					break;
				default:
					bannerSize = new ISBannerSize(width, height);
			}

			// Set adaptive banner
			bannerSize.setAdaptive(isAdaptiveBanner);

			this.bannerTop = top;
			this.bannerAutoShow = autoShow;

			this._hideBanner();
			this._destroyBanner();
			this._loadBanner(bannerSize, placementName);

			callbackContext.success();

		} catch (JSONException e) {
			callbackContext.error(this.debugErrorLog("createBanner"));
		}
	}

	private void showBanner(final CallbackContext callbackContext) {

		this._showBanner();
		callbackContext.success();
	}

	private void hideBanner(final CallbackContext callbackContext) {

		this._hideBanner();
		callbackContext.success();
	}

	private void destroyBanner(final CallbackContext callbackContext) {

		this._hideBanner();
		this._destroyBanner();

		callbackContext.success();
	}

	private void isBannerPlacementCapped(final JSONArray args, final CallbackContext callbackContext){

		try {
			final String placementName = args.getString(0);
			final boolean isCapped = IronSource.isBannerPlacementCapped(placementName);

			final JSONObject data = new JSONObject();
			data.put("isCapped", isCapped);
			data.put("placementName", placementName);

			callbackContext.success(data);

		} catch (JSONException e) {
			callbackContext.error(this.debugErrorLog("isBannerPlacementCapped"));
		}
	}

	//** Offerwall **//

	private void showOfferwall(final JSONArray args, final CallbackContext callbackContext) {

		try {

			final String placementName = args.getString(0);

			if (isStringEmpty(placementName))
			{
				IronSource.showOfferwall();
			}
			else
			{
				IronSource.showOfferwall(placementName);
			}

			callbackContext.success();

		} catch (JSONException e) {
			callbackContext.error(this.debugErrorLog("showOfferwall"));
		}
	}

	private void getOfferwallCredits(final CallbackContext callbackContext) {

		IronSource.getOfferwallCredits();
		callbackContext.success();
	}

	private void setClientSideCallbacks(final JSONArray args, final CallbackContext callbackContext) {

		try {

			final boolean receive = args.getBoolean(0);

			SupersonicConfig.getConfigObj().setClientSideCallbacks(receive);
			callbackContext.success();

		} catch (JSONException e) {

			callbackContext.error(this.debugErrorLog("setClientSideCallbacks"));
		}
	}

	//** Tools **//

	private void getMode(final CallbackContext callbackContext) {

		try {

			final JSONObject data = new JSONObject();
			data.put("isBanner", isBanner);
			data.put("isInterstitial", isInterstitial);
			data.put("isRewardedVideo", isRewardedVideo);
			data.put("isOfferwall", isOfferwall);

			callbackContext.success(data);

		} catch (JSONException e) {

			callbackContext.error(debugErrorLog("getMode"));
		}
	}

	private void setBannerOverlap(final JSONArray args, final CallbackContext callbackContext) {

		if (this.isInitialized) { return; }

		try {

			this.bannerOverlap = args.getBoolean(0);

			callbackContext.success();

		} catch (JSONException e) {

			callbackContext.error(debugErrorLog("setBannerOverlapMode"));
		}
	}

	////////////////////////////////////////////////////////
	// IronSource

	//** General **//

	private void _initIronSource(final String appKey, final String userId, CallbackContext callbackContext) {

		// Set the user id, if not blank.
		if (!isStringEmpty(userId)) { IronSource.setUserId(userId); }

		// // Mediation Testing
		// IronSource.setAdaptersDebug(true);

		// [Reminder] We shouldn't support automatically detecting ad-units from the IronSource console
		//            since we activate features based on the plugin toggles.
		//            This is because the IronSource SDK doesn't have a way to retrieve which toggles
		//            are set in the IronSource Console.

		// Rewarded Video
		if (this.isRewardedVideo) {
			IronSource.setRewardedVideoListener(this);
			IronSource.init(this.cordova.getActivity(), appKey, IronSource.AD_UNIT.REWARDED_VIDEO);
		}

		// Init Interstitial
		if (this.isInterstitial) {
			IronSource.setInterstitialListener(this);
			IronSource.init(this.cordova.getActivity(), appKey, IronSource.AD_UNIT.INTERSTITIAL);
		}

		// Init Offerwall
		if (this.isOfferwall) {
			IronSource.setOfferwallListener(this);
			IronSource.init(this.cordova.getActivity(), appKey, IronSource.AD_UNIT.OFFERWALL);
		}

		// Init Banner
		if (this.isBanner) {
			IronSource.init(this.cordova.getActivity(), appKey, IronSource.AD_UNIT.BANNER);
		}

		// After setting the delegates you can go ahead and initialize the SDK.

		// If all ad-units are deactivated.
		if (!this.isRewardedVideo && !this.isInterstitial && !this.isOfferwall && !this.isBanner)
		{
			// Initialize immediately.
			this.isInitialized = true;
			// Even if the IronSource SDK didn't truly initialize, just fake fire 
			// initialization because there is no ad-unit selected.
			this.fireEventTrigger(EVENT_INITIALIZATION);
		}
		// If any ad-unit is activated.
		else
		{
			IronSource.init(this.cordova.getActivity(), appKey, (InitializationListener) () -> {
				this.isInitialized = true;
				this.fireEventTrigger(EVENT_INITIALIZATION);
		});
		}

		callbackContext.success();
	}

	//** Banner View **//

	private void _loadBanner(final ISBannerSize bannerSize, final String placementName){

		final IronSourceC self = this;

		// Instantiate IronSourceBanner object, using the IronSource.createBanner API
		mIronSourceBannerLayout = IronSource.createBanner(self.cordova.getActivity(), bannerSize);

		if (mIronSourceBannerLayout != null) {

			mIronSourceBannerLayout.setBannerListener(new BannerListener() {

				////////////////////////////////////////////////////////
				// Banner :: Listeners

				@Override
				public void onBannerAdLoaded() {

					self.fireEventTrigger(EVENT_BANNER_LOADED);
					self.isBannerLoaded = true;

					if (self.bannerAutoShow) { self._showBanner(); }
				}

				@Override
				public void onBannerAdLoadFailed(IronSourceError ironSourceError) {

					self.fireEventTrigger(EVENT_BANNER_LOAD_FAILED, self.ironSourceErrorJSON(ironSourceError));
				}

				@Override
				public void onBannerAdClicked() {

					self.fireEventTrigger(EVENT_BANNER_CLICKED);
				}

				@Override
				public void onBannerAdScreenPresented() {

					self.fireEventTrigger(EVENT_BANNER_SCREEN_PRESENTED);
				}

				@Override
				public void onBannerAdScreenDismissed() {

					self.fireEventTrigger(EVENT_BANNER_SCREEN_DISMISSED);
				}

				@Override
				public void onBannerAdLeftApplication() {

					self.fireEventTrigger(EVENT_BANNER_LEFT_APPLICATION);
				}
			});
		}

		if (isStringEmpty(placementName)) {
			IronSource.loadBanner(mIronSourceBannerLayout);
		} else {
			IronSource.loadBanner(mIronSourceBannerLayout, placementName);
		}
	}

	private void _showBanner() {

		if (mIronSourceBannerLayout == null || !isBannerLoaded || isBannerShowing) {
			return;
		}

		final boolean top = bannerTop;
		final boolean overlap = bannerOverlap;

		if (overlap)
		{
			parentLayout = (ViewGroup) webview.getView().getParent();

			bannerContainerLayout = new RelativeLayout(this.cordova.getActivity());
			RelativeLayout.LayoutParams bannerContainerLayoutParams = new RelativeLayout.LayoutParams(
					LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);

			RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(
					RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
			layoutParams.addRule(top ? RelativeLayout.ALIGN_PARENT_TOP : RelativeLayout.ALIGN_PARENT_BOTTOM);
			layoutParams.addRule(RelativeLayout.CENTER_HORIZONTAL);
			bannerContainerLayout.setGravity(top ? Gravity.TOP : Gravity.BOTTOM);

			parentLayout.addView(bannerContainerLayout, bannerContainerLayoutParams);

			bannerContainerLayout.addView(mIronSourceBannerLayout, layoutParams);
		}
		else
		{
			parentLayout = (ViewGroup) webview.getView().getParent();
			View view = webview.getView();

			ViewGroup wvParentView = (ViewGroup) view.getParent();
			LinearLayout parentView = new LinearLayout(webview.getContext());

			bannerContainerLayout = new RelativeLayout(this.cordova.getActivity());

			RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(
					RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);

			if (wvParentView != null && wvParentView != parentView)
			{
				ViewGroup rootView = (ViewGroup) (view.getParent());
				wvParentView.removeView(view);
				((LinearLayout) parentView).setOrientation(LinearLayout.VERTICAL);
				parentView.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
						ViewGroup.LayoutParams.MATCH_PARENT, 0.0F));
				view.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
						ViewGroup.LayoutParams.MATCH_PARENT, 1.0F));
				parentView.addView(view);
				rootView.addView(parentView);
			}

			bannerContainerLayout.setGravity(top ? Gravity.TOP : Gravity.BOTTOM);

			layoutParams.addRule(RelativeLayout.CENTER_HORIZONTAL);
			bannerContainerLayout.addView(mIronSourceBannerLayout, layoutParams);
			mIronSourceBannerLayout.setLayoutParams(layoutParams);

			if (top) {
				parentView.addView(bannerContainerLayout, 0);
			} else {
				parentView.addView(bannerContainerLayout);
			}
		}

		this.isBannerShowing = true;
	}

	private void _hideBanner() {

		isBannerShowing = false;

		if (mIronSourceBannerLayout != null) {
			if (parentLayout != null && bannerContainerLayout != null) {

				if (mIronSourceBannerLayout.getParent() != null) {
					bannerContainerLayout.removeView(mIronSourceBannerLayout);
				}
				if (bannerContainerLayout.getParent() != null) {
					parentLayout.removeView(bannerContainerLayout);
				}
			}
		}
	}

	private void _destroyBanner() {

		this.isBannerLoaded = false;

		if (mIronSourceBannerLayout != null) {
			IronSource.destroyBanner(mIronSourceBannerLayout);
			mIronSourceBannerLayout = null;
		}
	}	

	////////////////////////////////////////////////////////
	// Cordova Tools

	private JSONObject ironSourceErrorJSON(final IronSourceError ironSourceError){

		final JSONObject data = new JSONObject();
		final JSONObject errorData = new JSONObject();

		try {

			errorData.put("code", ironSourceError.getErrorCode());
			errorData.put("message", ironSourceError.getErrorMessage());
			data.put("error", errorData);

		} catch (JSONException e) {

			Log.w(TAG, "A failure event's expected argument is undefined or null.", e);
		}

		return data;
	}

	private String debugErrorLog(final String method) {

		return "Java: \"" + method + "\" method execution error. Invalid JSON data.";
	}

	private void fireEventTrigger(final String event) {

		final CordovaWebView view = this.webview;

		cordova.getActivity().runOnUiThread(() -> {

			final String output = "javascript:" +
					"cordova.fireWindowEvent(" +
					"'%s'" +
					");";

			view.loadUrl(String.format(output, event));
		});
	}

	private void fireEventTrigger(final String event, final JSONObject data) {

		final CordovaWebView view = this.webview;

		cordova.getActivity().runOnUiThread(() -> {

			final String output = "javascript:" +
					"cordova.fireWindowEvent(" +
					"'%s'" +
					", " +
					"%s" +
					");";

			view.loadUrl(String.format(output, event, data.toString()));
		});
	}

	////////////////////////////////////////////////////////
	// Java Tools

	private boolean isStringEmpty(final String str) {

		if (TextUtils.isEmpty(str) || str == null) { return true; }
		return false;
	}

	////////////////////////////////////////////////////////
	// Rewarded Video :: Listeners

	@Override
	public void onRewardedVideoAdOpened() {
		this.fireEventTrigger(EVENT_REWARDED_VIDEO_OPENED);
	}

	@Override
	public void onRewardedVideoAdClosed() {
		this.fireEventTrigger(EVENT_REWARDED_VIDEO_CLOSED);
	}

	@Override
	public void onRewardedVideoAvailabilityChanged(final boolean isAvailable) {
		try {
			final JSONObject data = new JSONObject();
			
			data.put("isAvailable", isAvailable);

			this.fireEventTrigger(EVENT_REWARDED_VIDEO_AVAILABILITY_CHANGED, data);
		} catch (JSONException e) {
			Log.w(TAG, "An event's expected argument is undefined or null.", e);
		}
	}

	@Override
	public void onRewardedVideoAdStarted() {
		this.fireEventTrigger(EVENT_REWARDED_VIDEO_STARTED);
	}

	@Override
	public void onRewardedVideoAdEnded() {
		this.fireEventTrigger(EVENT_REWARDED_VIDEO_ENDED);
	}

	@Override
	public void onRewardedVideoAdRewarded(final Placement placement) {

		try {
			final JSONObject data = new JSONObject();
				
			if (placement == null)
			{
				data.put("placementName", "");
				data.put("rewardName", "");
				data.put("rewardAmount", 0);
			}
			else
			{
				data.put("placementName", placement.getPlacementName());
				data.put("rewardName", placement.getRewardName());
				data.put("rewardAmount", placement.getRewardAmount());
			}

			this.fireEventTrigger(EVENT_REWARDED_VIDEO_REWARDED, data);
		} catch (JSONException e) {
			Log.w(TAG, "An event's expected argument is undefined or null.", e);
		}
	}

	@Override
	public void onRewardedVideoAdShowFailed(final IronSourceError ironSourceError) {
		this.fireEventTrigger(EVENT_REWARDED_VIDEO_FAILED, this.ironSourceErrorJSON(ironSourceError));
	}

	@Override
	public void onRewardedVideoAdClicked(final Placement placement) {
	
		try {
			final JSONObject data = new JSONObject();
			
			if (placement == null)
			{
				data.put("placementName", "");
				data.put("rewardName", "");
				data.put("rewardAmount", 0);
			}
			else
			{
				data.put("placementName", placement.getPlacementName());
				data.put("rewardName", placement.getRewardName());
				data.put("rewardAmount", placement.getRewardAmount());
			}

			this.fireEventTrigger(EVENT_REWARDED_VIDEO_CLICKED, data);
		} catch (JSONException e) {
			Log.w(TAG, "An event's expected argument is undefined or null.", e);
		}
	}

	////////////////////////////////////////////////////////
	// Interstitial :: Listeners

	@Override
	public void onInterstitialAdReady() {
		this.fireEventTrigger(EVENT_INTERSTITIAL_READY);
	}

	@Override
	public void onInterstitialAdLoadFailed(final IronSourceError ironSourceError) {
		this.fireEventTrigger(EVENT_INTERSTITIAL_LOAD_FAILED, this.ironSourceErrorJSON(ironSourceError));
	}

	@Override
	public void onInterstitialAdOpened() {
		this.fireEventTrigger(EVENT_INTERSTITIAL_OPENED);
	}

	@Override
	public void onInterstitialAdClosed() {
		this.fireEventTrigger(EVENT_INTERSTITIAL_CLOSED);
	}

	@Override
	public void onInterstitialAdShowSucceeded() {
		this.fireEventTrigger(EVENT_INTERSTITIAL_SHOW_SUCCEEDED);
	}

	@Override
	public void onInterstitialAdShowFailed(final IronSourceError ironSourceError) {
		this.fireEventTrigger(EVENT_INTERSTITIAL_SHOW_FAILED, this.ironSourceErrorJSON(ironSourceError));
	}

	@Override
	public void onInterstitialAdClicked() {
		this.fireEventTrigger(EVENT_INTERSTITIAL_CLICKED);
	}

	////////////////////////////////////////////////////////
	// Offerwall :: Listeners

	@Override
	public void onOfferwallAvailable(final boolean isAvailable) {
		
		try {
			final JSONObject data = new JSONObject();

			data.put("isAvailable", isAvailable);

			this.fireEventTrigger(EVENT_OFFERWALL_AVAILABILITY_CHANGED, data);
		} catch (JSONException e) {
			Log.w(TAG, "An event's expected argument is undefined or null.", e);
		}
	}

	@Override
	public void onOfferwallOpened() {
		this.fireEventTrigger(EVENT_OFFERWALL_OPENED);
	}

	@Override
	public void onOfferwallShowFailed(final IronSourceError ironSourceError) {
		this.fireEventTrigger(EVENT_OFFERWALL_SHOW_FAILED, this.ironSourceErrorJSON(ironSourceError));
	}

	@Override
	public boolean onOfferwallAdCredited(final int credits, final int totalCredits, final boolean totalCreditsFlag) {
		
		try {
			final JSONObject data = new JSONObject();

			data.put("credits", credits);
			data.put("totalCredits", totalCredits);
			data.put("totalCreditsFlag", totalCreditsFlag);

			this.fireEventTrigger(EVENT_OFFERWALL_CREDITED, data);
		} catch (JSONException e) {
			Log.w(TAG, "An event's expected argument is undefined or null.", e);
		}

		// Claim reward. [Claim reward automatically]
		return true;
	}

	@Override
	public void onGetOfferwallCreditsFailed(final IronSourceError ironSourceError) {
		this.fireEventTrigger(EVENT_OFFERWALL_CREDITS_FAILED, this.ironSourceErrorJSON(ironSourceError));
	}

	@Override
	public void onOfferwallClosed() {
		this.fireEventTrigger(EVENT_OFFERWALL_CLOSED);
	}

	////////////////////////////////////////////////////////
	// Segment :: Listeners

	@Override
	public void onSegmentReceived(final String segmentName) {
		
		try {
			final JSONObject data = new JSONObject();

			data.put("segmentName", segmentName);
			
			this.fireEventTrigger(EVENT_SEGMENT_RECEIVED, data);
		} catch (JSONException e) {
			Log.w(TAG, "An event's expected argument is undefined or null.", e);
		}
	}

}