/*
 * Copyright (C) 2015 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.tv;

import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.ComponentName;
import android.content.Intent;
import android.media.tv.TvInputInfo;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.support.annotation.MainThread;
import android.util.Log;

import com.android.tv.common.CommonConstants;
import com.android.tv.common.SoftPreconditions;
import com.android.tv.common.actions.InputSetupActionUtils;
import com.android.tv.data.ChannelDataManager;
import com.android.tv.data.ChannelDataManager.Listener;
import com.android.tv.data.epg.EpgFetcher;
import com.android.tv.data.epg.EpgInputWhiteList;
import com.android.tv.features.TvFeatures;
import com.android.tv.util.SetupUtils;
import com.android.tv.util.TvInputManagerHelper;
import com.android.tv.util.Utils;

import com.google.android.tv.partner.support.EpgContract;

import dagger.android.AndroidInjection;
import dagger.android.ContributesAndroidInjector;

import java.util.concurrent.TimeUnit;

import javax.inject.Inject;

/**
 * An activity to launch a TV input setup activity.
 *
 * <p>After setup activity is finished, all channels will be browsable.
 */
public class SetupPassthroughActivity extends Activity {
    private static final String TAG = "SetupPassthroughAct";
    private static final boolean DEBUG = false;

    private static final int REQUEST_START_SETUP_ACTIVITY = 200;

    private static ScanTimeoutMonitor sScanTimeoutMonitor;

    private TvInputInfo mTvInputInfo;
    private Intent mActivityAfterCompletion;
    private boolean mEpgFetcherDuringScan;
    @Inject EpgInputWhiteList mEpgInputWhiteList;
    @Inject TvInputManagerHelper mInputManager;
    @Inject SetupUtils mSetupUtils;
    @Inject ChannelDataManager mChannelDataManager;
    @Inject EpgFetcher mEpgFetcher;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        if (DEBUG) Log.d(TAG, "onCreate");
        AndroidInjection.inject(this);
        super.onCreate(savedInstanceState);
        Intent intent = getIntent();
        String inputId = intent.getStringExtra(InputSetupActionUtils.EXTRA_INPUT_ID);
        mTvInputInfo = mInputManager.getTvInputInfo(inputId);
        mActivityAfterCompletion = InputSetupActionUtils.getExtraActivityAfter(intent);
        boolean needToFetchEpg =
                mTvInputInfo != null && Utils.isInternalTvInput(this, mTvInputInfo.getId());
        if (needToFetchEpg) {
            // In case when the activity is restored, this flag should be restored as well.
            mEpgFetcherDuringScan = true;
        }
        if (savedInstanceState == null) {
            SoftPreconditions.checkArgument(
                    InputSetupActionUtils.hasInputSetupAction(intent),
                    TAG,
                    "Unsupported action %s",
                    intent.getAction());
            if (DEBUG) Log.d(TAG, "TvInputId " + inputId + " / TvInputInfo " + mTvInputInfo);
            if (mTvInputInfo == null) {
                Log.w(TAG, "There is no input with the ID " + inputId + ".");
                finish();
                return;
            }
            if (intent.getExtras() == null) {
                Log.w(TAG, "There is no extra info in the intent");
                finish();
                return;
            }
            Intent setupIntent = InputSetupActionUtils.getExtraSetupIntent(intent);
            if (DEBUG) Log.d(TAG, "Setup activity launch intent: " + setupIntent);
            if (setupIntent == null) {
                Log.w(TAG, "The input (" + mTvInputInfo.getId() + ") doesn't have setup.");
                finish();
                return;
            }
            if (DEBUG) Log.d(TAG, "Activity after completion " + mActivityAfterCompletion);
            // If EXTRA_SETUP_INTENT is not removed, an infinite recursion happens during
            // setupIntent.putExtras(intent.getExtras()).
            Bundle extras = intent.getExtras();
            InputSetupActionUtils.removeSetupIntent(extras);
            setupIntent.putExtras(extras);
            try {
                ComponentName callingActivity = getCallingActivity();
                if (callingActivity == null
                        || !callingActivity.getPackageName().equals(CommonConstants.BASE_PACKAGE)) {
                   String name =
                        callingActivity == null ? "null" : callingActivity.getPackageName();
                    Log.w(TAG,
                            "Calling activity " + name + " is not trusted. Not forwarding intent.");
                    finish();
                    return;
                }
                SetupUtils.grantEpgPermission(this, mTvInputInfo.getServiceInfo().packageName);
                startActivityForResult(setupIntent, REQUEST_START_SETUP_ACTIVITY);
            } catch (ActivityNotFoundException e) {
                Log.e(TAG, "Can't find activity: " + setupIntent.getComponent());
                finish();
                return;
            }
            if (needToFetchEpg) {
                if (sScanTimeoutMonitor == null) {
                    sScanTimeoutMonitor = new ScanTimeoutMonitor(mEpgFetcher, mChannelDataManager);
                }
                sScanTimeoutMonitor.startMonitoring();
                mEpgFetcher.onChannelScanStarted();
            }
        }
    }

    @Override
    public void onActivityResult(int requestCode, final int resultCode, final Intent data) {
        if (DEBUG)
            Log.d(TAG, "onActivityResult(" + requestCode + ",  " + resultCode + ",  " + data + ")");
        if (sScanTimeoutMonitor != null) {
            sScanTimeoutMonitor.stopMonitoring();
        }
        // Note: It's not guaranteed that this method is always called after scanning.
        boolean setupComplete =
                requestCode == REQUEST_START_SETUP_ACTIVITY && resultCode == Activity.RESULT_OK;
        // Tells EpgFetcher that channel source setup is finished.

        if (mEpgFetcherDuringScan) {
            mEpgFetcher.onChannelScanFinished();
        }
        if (!setupComplete) {
            setResult(resultCode, data);
            finish();
            return;
        }
        if (TvFeatures.CLOUD_EPG_FOR_3RD_PARTY.isEnabled(this)
                && data != null
                && data.getBooleanExtra(EpgContract.EXTRA_USE_CLOUD_EPG, false)) {
            if (DEBUG) Log.d(TAG, "extra " + data.getExtras());
            String inputId = data.getStringExtra(TvInputInfo.EXTRA_INPUT_ID);
            if (mEpgInputWhiteList.isInputWhiteListed(inputId)) {
                mEpgFetcher.fetchImmediately();
            }
        }

        if (mTvInputInfo == null) {
            Log.w(
                    TAG,
                    "There is no input with ID "
                            + getIntent().getStringExtra(InputSetupActionUtils.EXTRA_INPUT_ID)
                            + ".");
            setResult(resultCode, data);
            finish();
            return;
        }
        mSetupUtils.onTvInputSetupFinished(
                mTvInputInfo.getId(),
                () -> {
                    if (mActivityAfterCompletion != null) {
                        try {
                            startActivity(mActivityAfterCompletion);
                        } catch (ActivityNotFoundException e) {
                            Log.w(TAG, "Activity launch failed", e);
                        }
                    }
                    setResult(resultCode, data);
                    finish();
                });
    }

    /**
     * Monitors the scan progress and notifies the timeout of the scanning. The purpose of this
     * monitor is to call EpgFetcher.onChannelScanFinished() in case when
     * SetupPassthroughActivity.onActivityResult() is not called properly. b/36008534
     */
    @MainThread
    private static class ScanTimeoutMonitor {
        // Set timeout long enough. The message in Sony TV says the scanning takes about 30 minutes.
        private static final long SCAN_TIMEOUT_MS = TimeUnit.MINUTES.toMillis(30);

        private final EpgFetcher mEpgFetcher;
        private final ChannelDataManager mChannelDataManager;
        private final Handler mHandler = new Handler(Looper.getMainLooper());
        private final Runnable mScanTimeoutRunnable =
                () -> {
                    Log.w(
                            TAG,
                            "No channels has been added for a while."
                                    + " The scan might have finished unexpectedly.");
                    onScanTimedOut();
                };
        private final Listener mChannelDataManagerListener =
                new Listener() {
                    @Override
                    public void onLoadFinished() {
                        setupTimer();
                    }

                    @Override
                    public void onChannelListUpdated() {
                        setupTimer();
                    }

                    @Override
                    public void onChannelBrowsableChanged() {}
                };
        private boolean mStarted;

        private ScanTimeoutMonitor(EpgFetcher epgFetcher, ChannelDataManager mChannelDataManager) {
            mEpgFetcher = epgFetcher;
            this.mChannelDataManager = mChannelDataManager;
        }

        private void startMonitoring() {
            if (!mStarted) {
                mStarted = true;
                mChannelDataManager.addListener(mChannelDataManagerListener);
            }
            if (mChannelDataManager.isDbLoadFinished()) {
                setupTimer();
            }
        }

        private void stopMonitoring() {
            if (mStarted) {
                mStarted = false;
                mHandler.removeCallbacks(mScanTimeoutRunnable);
                mChannelDataManager.removeListener(mChannelDataManagerListener);
            }
        }

        private void setupTimer() {
            mHandler.removeCallbacks(mScanTimeoutRunnable);
            mHandler.postDelayed(mScanTimeoutRunnable, SCAN_TIMEOUT_MS);
        }

        private void onScanTimedOut() {
            stopMonitoring();
            mEpgFetcher.onChannelScanFinished();
        }
    }

    /** Exports {@link MainActivity} for Dagger codegen to create the appropriate injector. */
    @dagger.Module
    public abstract static class Module {
        @ContributesAndroidInjector
        abstract SetupPassthroughActivity contributesSetupPassthroughActivity();
    }
}
