package org.schabi.newpipe.fragments;

import android.content.Intent;
import android.os.Bundle;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;

import com.jakewharton.rxbinding2.view.RxView;

import org.schabi.newpipe.BaseFragment;
import org.schabi.newpipe.MainActivity;
import org.schabi.newpipe.R;
import org.schabi.newpipe.ReCaptchaActivity;
import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException;
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.util.ExtractorHelper;
import org.schabi.newpipe.util.InfoCache;

import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

import icepick.State;
import io.reactivex.android.schedulers.AndroidSchedulers;

import static org.schabi.newpipe.util.AnimationUtils.animateView;

public abstract class BaseStateFragment<I> extends BaseFragment implements ViewContract<I> {

    @State
    protected AtomicBoolean wasLoading = new AtomicBoolean();
    protected AtomicBoolean isLoading = new AtomicBoolean();

    @Nullable
    protected View emptyStateView;
    @Nullable
    protected ProgressBar loadingProgressBar;

    protected View errorPanelRoot;
    protected Button errorButtonRetry;
    protected TextView errorTextView;

    @Override
    public void onViewCreated(View rootView, Bundle savedInstanceState) {
        super.onViewCreated(rootView, savedInstanceState);
        doInitialLoadLogic();
    }

    @Override
    public void onPause() {
        super.onPause();
        wasLoading.set(isLoading.get());
    }


    /*//////////////////////////////////////////////////////////////////////////
    // Init
    //////////////////////////////////////////////////////////////////////////*/


    @Override
    protected void initViews(View rootView, Bundle savedInstanceState) {
        super.initViews(rootView, savedInstanceState);

        emptyStateView = rootView.findViewById(R.id.empty_state_view);
        loadingProgressBar = rootView.findViewById(R.id.loading_progress_bar);

        errorPanelRoot = rootView.findViewById(R.id.error_panel);
        errorButtonRetry = rootView.findViewById(R.id.error_button_retry);
        errorTextView = rootView.findViewById(R.id.error_message_view);
    }

    @Override
    protected void initListeners() {
        super.initListeners();
        RxView.clicks(errorButtonRetry)
                .debounce(300, TimeUnit.MILLISECONDS)
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(o -> onRetryButtonClicked());
    }

    protected void onRetryButtonClicked() {
        reloadContent();
    }

    public void reloadContent() {
        startLoading(true);
    }

    /*//////////////////////////////////////////////////////////////////////////
    // Load
    //////////////////////////////////////////////////////////////////////////*/

    protected void doInitialLoadLogic() {
        startLoading(true);
    }

    protected void startLoading(boolean forceLoad) {
        if (DEBUG) Log.d(TAG, "startLoading() called with: forceLoad = [" + forceLoad + "]");
        showLoading();
        isLoading.set(true);
    }

    /*//////////////////////////////////////////////////////////////////////////
    // Contract
    //////////////////////////////////////////////////////////////////////////*/

    @Override
    public void showLoading() {
        if (emptyStateView != null) animateView(emptyStateView, false, 150);
        if (loadingProgressBar != null) animateView(loadingProgressBar, true, 400);
        animateView(errorPanelRoot, false, 150);
    }

    @Override
    public void hideLoading() {
        if (emptyStateView != null) animateView(emptyStateView, false, 150);
        if (loadingProgressBar != null) animateView(loadingProgressBar, false, 0);
        animateView(errorPanelRoot, false, 150);
    }

    @Override
    public void showEmptyState() {
        isLoading.set(false);
        if (emptyStateView != null) animateView(emptyStateView, true, 200);
        if (loadingProgressBar != null) animateView(loadingProgressBar, false, 0);
        animateView(errorPanelRoot, false, 150);
    }

    @Override
    public void showError(String message, boolean showRetryButton) {
        if (DEBUG) Log.d(TAG, "showError() called with: message = [" + message + "], showRetryButton = [" + showRetryButton + "]");
        isLoading.set(false);
        InfoCache.getInstance().clearCache();
        hideLoading();

        errorTextView.setText(message);
        if (showRetryButton) animateView(errorButtonRetry, true, 600);
        else animateView(errorButtonRetry, false, 0);
        animateView(errorPanelRoot, true, 300);
    }

    @Override
    public void handleResult(I result) {
        if (DEBUG) Log.d(TAG, "handleResult() called with: result = [" + result + "]");
        hideLoading();
    }

    /*//////////////////////////////////////////////////////////////////////////
    // Error handling
    //////////////////////////////////////////////////////////////////////////*/

    /**
     * Default implementation handles some general exceptions
     *
     * @return if the exception was handled
     */
    protected boolean onError(Throwable exception) {
        if (DEBUG) Log.d(TAG, "onError() called with: exception = [" + exception + "]");
        isLoading.set(false);

        if (isDetached() || isRemoving()) {
            if (DEBUG) Log.w(TAG, "onError() is detached or removing = [" + exception + "]");
            return true;
        }

        if (ExtractorHelper.isInterruptedCaused(exception)) {
            if (DEBUG) Log.w(TAG, "onError() isInterruptedCaused! = [" + exception + "]");
            return true;
        }

        if (exception instanceof ReCaptchaException) {
            onReCaptchaException((ReCaptchaException) exception);
            return true;
        } else if (exception instanceof ContentNotAvailableException) {
            showError(getString(R.string.content_not_available), false);
            return true;
        } else if (exception instanceof IOException) {
            showError(getString(R.string.network_error), true);
            return true;
        }

        return false;
    }

    public void onReCaptchaException(ReCaptchaException exception) {
        if (DEBUG) Log.d(TAG, "onReCaptchaException() called");
        Toast.makeText(activity, R.string.recaptcha_request_toast, Toast.LENGTH_LONG).show();
        // Starting ReCaptcha Challenge Activity
        Intent intent = new Intent(activity, ReCaptchaActivity.class);
        intent.putExtra(ReCaptchaActivity.RECAPTCHA_URL_EXTRA, exception.getUrl());
        startActivityForResult(intent, ReCaptchaActivity.RECAPTCHA_REQUEST);

        showError(getString(R.string.recaptcha_request_toast), false);
    }

    public void onUnrecoverableError(Throwable exception, UserAction userAction, String serviceName, String request, @StringRes int errorId) {
        onUnrecoverableError(Collections.singletonList(exception), userAction, serviceName, request, errorId);
    }

    public void onUnrecoverableError(List<Throwable> exception, UserAction userAction, String serviceName, String request, @StringRes int errorId) {
        if (DEBUG) Log.d(TAG, "onUnrecoverableError() called with: exception = [" + exception + "]");

        if (serviceName == null) serviceName = "none";
        if (request == null) request = "none";

        ErrorActivity.reportError(getContext(), exception, MainActivity.class, null, ErrorActivity.ErrorInfo.make(userAction, serviceName, request, errorId));
    }

    public void showSnackBarError(Throwable exception, UserAction userAction, String serviceName, String request, @StringRes int errorId) {
        showSnackBarError(Collections.singletonList(exception), userAction, serviceName, request, errorId);
    }

    /**
     * Show a SnackBar and only call ErrorActivity#reportError IF we a find a valid view (otherwise the error screen appears)
     */
    public void showSnackBarError(List<Throwable> exception, UserAction userAction, String serviceName, String request, @StringRes int errorId) {
        if (DEBUG) {
            Log.d(TAG, "showSnackBarError() called with: exception = [" + exception + "], userAction = [" + userAction + "], request = [" + request + "], errorId = [" + errorId + "]");
        }
        View rootView = activity != null ? activity.findViewById(android.R.id.content) : null;
        if (rootView == null && getView() != null) rootView = getView();
        if (rootView == null) return;

        ErrorActivity.reportError(getContext(), exception, MainActivity.class, rootView,
                ErrorActivity.ErrorInfo.make(userAction, serviceName, request, errorId));
    }
}
