package com.applovin.mediation;

import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;

import com.applovin.adview.AppLovinIncentivizedInterstitial;
import com.applovin.sdk.AppLovinAd;
import com.applovin.sdk.AppLovinAdClickListener;
import com.applovin.sdk.AppLovinAdDisplayListener;
import com.applovin.sdk.AppLovinAdLoadListener;
import com.applovin.sdk.AppLovinAdRewardListener;
import com.applovin.sdk.AppLovinAdVideoPlaybackListener;
import com.applovin.sdk.AppLovinErrorCodes;
import com.applovin.sdk.AppLovinSdk;
import com.google.android.gms.ads.AdRequest;
import com.google.android.gms.ads.mediation.MediationAdRequest;
import com.google.android.gms.ads.mediation.OnContextChangedListener;
import com.google.android.gms.ads.reward.RewardItem;
import com.google.android.gms.ads.reward.mediation.MediationRewardedVideoAdAdapter;
import com.google.android.gms.ads.reward.mediation.MediationRewardedVideoAdListener;

import java.util.HashMap;
import java.util.Map;

import static android.util.Log.DEBUG;
import static android.util.Log.ERROR;

/**
 * AppLovin SDK rewarded video adapter for AdMob.
 * <p>
 * Created by Thomas So on 5/29/17.
 */

public class ApplovinAdapter
        implements MediationRewardedVideoAdAdapter, OnContextChangedListener,
        AppLovinAdLoadListener, AppLovinAdDisplayListener, AppLovinAdClickListener, AppLovinAdVideoPlaybackListener, AppLovinAdRewardListener
{
    private static final boolean LOGGING_ENABLED = true;
    private static final Handler UI_HANDLER      = new Handler( Looper.getMainLooper() );
    private static final String  DEFAULT_ZONE    = "";

    // A map of Zone -> `AppLovinIncentivizedInterstitial` to be shared by instances of the custom event.
    // This prevents skipping of ads as this adapter will be re-created and preloaded (along with underlying `AppLovinIncentivizedInterstitial`)
    // on every ad load regardless if ad was actually displayed or not.
    private static final Map<String, AppLovinIncentivizedInterstitial> GLOBAL_INCENTIVIZED_INTERSTITIAL_ADS = new HashMap<String, AppLovinIncentivizedInterstitial>();

    private boolean initialized;

    private AppLovinIncentivizedInterstitial incentivizedInterstitial;
    private Context                          context;
    private MediationRewardedVideoAdListener listener;

    private boolean    fullyWatched;
    private RewardItem reward;

    //
    // AdMob Custom Event Methods
    //

    @Override
    public void initialize(final Context context, final MediationAdRequest adRequest, final String userId, final MediationRewardedVideoAdListener listener, final Bundle serverParameters, final Bundle networkExtras)
    {
        // SDK versions BELOW 7.2.0 require a instance of an Activity to be passed in as the context
        if ( AppLovinSdk.VERSION_CODE < 720 && !( context instanceof Activity ) )
        {
            log( ERROR, "Unable to request AppLovin rewarded video. Invalid context provided." );
            listener.onInitializationFailed( this, AdRequest.ERROR_CODE_INVALID_REQUEST );

            return;
        }

        log( DEBUG, "Initializing AppLovin rewarded video..." );

        this.context = context;
        this.listener = listener;

        if ( !initialized )
        {
            AppLovinSdk.initializeSdk( context );
            AppLovinSdk.getInstance( context ).setPluginVersion( "AdMob-2.2.1" );

            initialized = true;
        }

        listener.onInitializationSucceeded( this );
    }

    @Override
    public boolean isInitialized()
    {
        return initialized;
    }

    @Override
    public void loadAd(final MediationAdRequest adRequest, final Bundle serverParameters, final Bundle networkExtras)
    {
        log( DEBUG, "Requesting AppLovin rewarded video with networkExtras: " + networkExtras );

        // Zones support is available on AppLovin SDK 7.5.0 and higher
        final String zoneId;
        if ( AppLovinSdk.VERSION_CODE >= 750 && networkExtras != null && networkExtras.containsKey( "zone_id" ) )
        {
            zoneId = networkExtras.getString( "zone_id" );
        }
        else
        {
            zoneId = DEFAULT_ZONE;
        }

        // Check if incentivized ad for zone already exists
        if ( GLOBAL_INCENTIVIZED_INTERSTITIAL_ADS.containsKey( zoneId ) )
        {
            incentivizedInterstitial = GLOBAL_INCENTIVIZED_INTERSTITIAL_ADS.get( zoneId );
        }
        else
        {
            // If this is a default Zone, create the incentivized ad normally
            if ( DEFAULT_ZONE.equals( zoneId ) )
            {
                incentivizedInterstitial = AppLovinIncentivizedInterstitial.create( this.context );
            }
            // Otherwise, use the Zones API
            else
            {
                incentivizedInterstitial = AppLovinIncentivizedInterstitial.create( zoneId, AppLovinSdk.getInstance( this.context ) );
            }

            GLOBAL_INCENTIVIZED_INTERSTITIAL_ADS.put( zoneId, incentivizedInterstitial );
        }

        incentivizedInterstitial.preload( this );
    }

    @Override
    public void showVideo()
    {
        if ( incentivizedInterstitial.isAdReadyToDisplay() )
        {
            fullyWatched = false;
            reward = null;

            incentivizedInterstitial.show( context, null, this, this, this, this );
        }
        else
        {
            log( ERROR, "Failed to show an AppLovin rewarded video before one was loaded" );
            listener.onAdFailedToLoad( this, AdRequest.ERROR_CODE_INTERNAL_ERROR );
        }
    }

    @Override
    public void onPause() {}

    @Override
    public void onResume() {}

    @Override
    public void onDestroy() {}

    @Override
    public void onContextChanged(final Context context)
    {
        if ( context != null )
        {
            log( DEBUG, "Context changed: " + context );
            this.context = context;
        }
    }

    //
    // Ad Load Listener
    //

    @Override
    public void adReceived(final AppLovinAd ad)
    {
        log( DEBUG, "Rewarded video did load ad: " + ad.getAdIdNumber() );

        runOnUiThread( new Runnable()
        {
            @Override
            public void run()
            {
                listener.onAdLoaded( ApplovinAdapter.this );
            }
        } );
    }

    @Override
    public void failedToReceiveAd(final int errorCode)
    {
        log( DEBUG, "Rewarded video failed to load with error: " + errorCode );

        runOnUiThread( new Runnable()
        {
            @Override
            public void run()
            {
                listener.onAdFailedToLoad( ApplovinAdapter.this, toAdMobErrorCode( errorCode ) );
            }
        } );
    }

    //
    // Ad Display Listener
    //

    @Override
    public void adDisplayed(final AppLovinAd ad)
    {
        log( DEBUG, "Rewarded video displayed" );
        listener.onAdOpened( this );
    }

    @Override
    public void adHidden(final AppLovinAd ad)
    {
        log( DEBUG, "Rewarded video dismissed" );

        if ( fullyWatched && reward != null )
        {
            log( DEBUG, "Rewarded " + reward.getAmount() + " " + reward.getType() );
            listener.onRewarded( this, reward );
        }

        listener.onAdClosed( this );
    }

    //
    // Ad Click Listener
    //

    @Override
    public void adClicked(final AppLovinAd ad)
    {
        log( DEBUG, "Rewarded video clicked" );

        listener.onAdClicked( this );
        listener.onAdLeftApplication( this );
    }

    //
    // Video Playback Listener
    //

    @Override
    public void videoPlaybackBegan(AppLovinAd ad)
    {
        log( DEBUG, "Rewarded video playback began" );
        listener.onVideoStarted( this );
    }

    @Override
    public void videoPlaybackEnded(AppLovinAd ad, double percentViewed, boolean fullyWatched)
    {
        log( DEBUG, "Rewarded video playback ended at playback percent: " + percentViewed );
        this.fullyWatched = fullyWatched;
    }

    //
    // Reward Listener
    //

    @Override
    public void userOverQuota(final AppLovinAd appLovinAd, final Map map)
    {
        log( ERROR, "Rewarded video validation request for ad did exceed quota with response: " + map );
    }

    @Override
    public void validationRequestFailed(final AppLovinAd appLovinAd, final int errorCode)
    {
        log( ERROR, "Rewarded video validation request for ad failed with error code: " + errorCode );
    }

    @Override
    public void userRewardRejected(final AppLovinAd appLovinAd, final Map map)
    {
        log( ERROR, "Rewarded video validation request was rejected with response: " + map );
    }

    @Override
    public void userDeclinedToViewAd(final AppLovinAd appLovinAd)
    {
        log( DEBUG, "User declined to view rewarded video" );
    }

    @Override
    public void userRewardVerified(final AppLovinAd ad, final Map map)
    {
        final String currency = (String) map.get( "currency" );
        final String amountStr = (String) map.get( "amount" );
        final int amount = (int) Double.parseDouble( amountStr ); // AppLovin returns amount as double

        log( DEBUG, "Verified " + amount + " " + currency );

        reward = new AppLovinRewardItem( amount, currency );
    }

    //
    // Utility Methods
    //

    private static void log(final int priority, final String message)
    {
        if ( LOGGING_ENABLED )
        {
            Log.println( priority, "AppLovinRewardedVideo", message );
        }
    }

    private static int toAdMobErrorCode(final int applovinErrorCode)
    {
        if ( applovinErrorCode == AppLovinErrorCodes.NO_FILL )
        {
            return AdRequest.ERROR_CODE_NO_FILL;
        }
        else if ( applovinErrorCode == AppLovinErrorCodes.NO_NETWORK || applovinErrorCode == AppLovinErrorCodes.FETCH_AD_TIMEOUT )
        {
            return AdRequest.ERROR_CODE_NETWORK_ERROR;
        }
        else
        {
            return AdRequest.ERROR_CODE_INTERNAL_ERROR;
        }
    }

    /**
     * Reward item wrapper class.
     */
    private static final class AppLovinRewardItem
            implements RewardItem
    {
        private final int    amount;
        private final String type;

        private AppLovinRewardItem(final int amount, final String type)
        {
            this.amount = amount;
            this.type = type;
        }

        @Override
        public String getType()
        {
            return type;
        }

        @Override
        public int getAmount()
        {
            return amount;
        }
    }

    /**
     * Performs the given runnable on the main thread.
     */
    public static void runOnUiThread(final Runnable runnable)
    {
        if ( Looper.myLooper() == Looper.getMainLooper() )
        {
            runnable.run();
        }
        else
        {
            UI_HANDLER.post( runnable );
        }
    }
}