/*
 * Copyright (C) 2017 Schürmann & Breitmoser GbR
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.sufficientlysecure.keychain.ui;


import android.app.Activity;
import android.content.Context;
import androidx.databinding.DataBindingUtil;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.loader.app.LoaderManager;
import androidx.loader.content.Loader;
import androidx.recyclerview.widget.LinearLayoutManager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;

import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.databinding.ImportKeysListFragmentBinding;
import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry;
import org.sufficientlysecure.keychain.keyimport.processing.AsyncTaskResultWrapper;
import org.sufficientlysecure.keychain.keyimport.processing.BytesLoaderState;
import org.sufficientlysecure.keychain.keyimport.processing.CloudLoaderState;
import org.sufficientlysecure.keychain.keyimport.processing.ImportKeysListCloudLoader;
import org.sufficientlysecure.keychain.keyimport.processing.ImportKeysListLoader;
import org.sufficientlysecure.keychain.keyimport.processing.ImportKeysListener;
import org.sufficientlysecure.keychain.keyimport.processing.LoaderState;
import org.sufficientlysecure.keychain.operations.results.GetKeyResult;
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
import org.sufficientlysecure.keychain.ui.adapter.ImportKeysAdapter;
import org.sufficientlysecure.keychain.ui.util.PermissionsUtil;
import org.sufficientlysecure.keychain.util.ParcelableProxy;
import org.sufficientlysecure.keychain.util.Preferences;
import org.sufficientlysecure.keychain.util.Preferences.CloudSearchPrefs;
import org.sufficientlysecure.keychain.network.orbot.OrbotHelper;

import java.util.ArrayList;

public class ImportKeysListFragment extends Fragment implements
        LoaderManager.LoaderCallbacks<AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>> {

    private static final String ARG_DATA_URI = "uri";
    private static final String ARG_BYTES = "bytes";
    public static final String ARG_SERVER_QUERY = "query";
    public static final String ARG_NON_INTERACTIVE = "non_interactive";
    public static final String ARG_CLOUD_SEARCH_PREFS = "cloud_search_prefs";

    private FragmentActivity mActivity;
    private ImportKeysListener mListener;

    private ImportKeysListFragmentBinding mBinding;
    private ParcelableProxy mParcelableProxy;

    private ImportKeysAdapter mAdapter;
    private LoaderState mLoaderState;

    public static final int STATUS_FIRST = 0;
    public static final int STATUS_LOADING = 1;
    public static final int STATUS_LOADED = 2;
    public static final int STATUS_EMPTY = 3;

    private static final int LOADER_ID_BYTES = 0;
    private static final int LOADER_ID_CLOUD = 1;

    private boolean mShowingOrbotDialog;

    /**
     * Creates an interactive ImportKeyListFragment which reads keyrings from bytes, or file specified
     * by dataUri, or searches a keyserver for serverQuery, if parameter is not null, in that order
     * Will immediately load data if non-null bytes/dataUri/serverQuery
     *
     * @param bytes            byte data containing list of keyrings to be imported
     * @param dataUri          file from which keyrings are to be imported
     * @param serverQuery      query to search for on keyserver
     * @param cloudSearchPrefs search parameters to use. If null will retrieve from user's
     *                         preferences.
     * @return fragment with arguments set based on passed parameters
     */
    public static ImportKeysListFragment newInstance(byte[] bytes, Uri dataUri, String serverQuery,
                                                     CloudSearchPrefs cloudSearchPrefs) {

        return newInstance(bytes, dataUri, serverQuery, false, cloudSearchPrefs);
    }

    /**
     * Visually consists of a list of keyrings with checkboxes to specify which are to be imported
     * Will immediately load data if non-null bytes/dataUri/serverQuery is supplied
     *
     * @param bytes            byte data containing list of keyrings to be imported
     * @param dataUri          file from which keyrings are to be imported
     * @param serverQuery      query to search for on keyserver
     * @param nonInteractive   if true, users will not be able to check/uncheck items in the list
     * @param cloudSearchPrefs search parameters to use. If null will retrieve from user's
     *                         preferences.
     * @return fragment with arguments set based on passed parameters
     */
    public static ImportKeysListFragment newInstance(byte[] bytes,
                                                     Uri dataUri,
                                                     String serverQuery,
                                                     boolean nonInteractive,
                                                     CloudSearchPrefs cloudSearchPrefs) {

        Bundle args = new Bundle();
        args.putByteArray(ARG_BYTES, bytes);
        args.putParcelable(ARG_DATA_URI, dataUri);
        args.putString(ARG_SERVER_QUERY, serverQuery);
        args.putBoolean(ARG_NON_INTERACTIVE, nonInteractive);
        args.putParcelable(ARG_CLOUD_SEARCH_PREFS, cloudSearchPrefs);

        ImportKeysListFragment frag = new ImportKeysListFragment();
        frag.setArguments(args);
        return frag;
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle) {
        mBinding = DataBindingUtil.inflate(inflater, R.layout.import_keys_list_fragment, container, false);
        mBinding.setStatus(STATUS_FIRST);
        View view = mBinding.getRoot();

        mActivity = getActivity();

        Bundle args = getArguments();
        Uri dataUri = args.getParcelable(ARG_DATA_URI);
        byte[] bytes = args.getByteArray(ARG_BYTES);
        String query = args.getString(ARG_SERVER_QUERY);
        boolean nonInteractive = args.getBoolean(ARG_NON_INTERACTIVE, false);
        mBinding.basic.setNonInteractive(nonInteractive);

        // Create an empty adapter we will use to display the loaded data.
        mAdapter = new ImportKeysAdapter(mActivity, mListener, nonInteractive);
        mBinding.recyclerView.setAdapter(mAdapter);
        mBinding.recyclerView.setLayoutManager(new LinearLayoutManager(mActivity));

        if (dataUri != null || bytes != null) {
            loadState(new BytesLoaderState(bytes, dataUri));
        } else if (query != null) {
            CloudSearchPrefs cloudSearchPrefs = args.getParcelable(ARG_CLOUD_SEARCH_PREFS);
            if (cloudSearchPrefs == null) {
                cloudSearchPrefs = Preferences.getPreferences(mActivity).getCloudSearchPrefs();
            }
            loadState(new CloudLoaderState(query, cloudSearchPrefs));
        }

        // mBinding.basic is only used for file import
        mBinding.basic.importKeys.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View view) {
                mListener.importKeys(mAdapter.getEntries());
            }
        });
        mBinding.basic.listKeys.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View view) {
                mBinding.setAdvanced(true);
            }
        });

        return view;
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);

        try {
            mListener = (ImportKeysListener) context;
        } catch (ClassCastException e) {
            throw new ClassCastException(context.toString()
                    + " must implement ImportKeysListener");
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode,
                                           @NonNull String[] permissions,
                                           @NonNull int[] grantResults) {

        if (PermissionsUtil.checkReadPermissionResult(mActivity, requestCode, grantResults)) {
            restartLoaders();
        } else {
            mActivity.setResult(Activity.RESULT_CANCELED);
            mActivity.finish();
        }
    }

    /**
     * User may want to go back to single card view if he's now in full key list
     * Check if we are in full key list and if this import operation supports basic mode
     *
     * @return true if activity's back pressed can be performed
     */
    public boolean onBackPressed() {
        boolean advanced = mBinding.getAdvanced();
        if (advanced && mLoaderState.isBasicModeSupported()) {
            mBinding.setAdvanced(false);
            return false;
        }
        return true;
    }

    public void loadState(LoaderState loaderState) {
        mLoaderState = loaderState;

        if (mLoaderState instanceof BytesLoaderState) {
            BytesLoaderState ls = (BytesLoaderState) mLoaderState;

            if (ls.mDataUri != null &&
                    !PermissionsUtil.checkAndRequestReadPermission(this, ls.mDataUri)) {
                return;
            }
        }
        mBinding.setAdvanced(!mLoaderState.isBasicModeSupported());

        restartLoaders();
    }

    private void restartLoaders() {
        LoaderManager loaderManager = getLoaderManager();

        if (mLoaderState instanceof BytesLoaderState) {
            loaderManager.restartLoader(LOADER_ID_BYTES, null, this);
        } else if (mLoaderState instanceof CloudLoaderState) {
            loaderManager.restartLoader(LOADER_ID_CLOUD, null, this);
        }
    }

    @Override
    public Loader<AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>> onCreateLoader(
            int id, Bundle args) {

        Loader<AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>> loader = null;
        switch (id) {
            case LOADER_ID_BYTES: {
                loader = new ImportKeysListLoader(mActivity, (BytesLoaderState) mLoaderState);
                break;
            }
            case LOADER_ID_CLOUD: {
                loader = new ImportKeysListCloudLoader(mActivity, (CloudLoaderState) mLoaderState,
                        mParcelableProxy);
                break;
            }
        }

        if (loader != null) {
            mBinding.setStatus(STATUS_LOADING);
        }

        return loader;
    }

    @Override
    public void onLoadFinished(
            Loader<AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>> loader,
            AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>> data) {

        mAdapter.setData(data.getResult());
        int size = mAdapter.getItemCount();

        mBinding.setNumber(size);
        mBinding.setStatus(size > 0 ? STATUS_LOADED : STATUS_EMPTY);

        GetKeyResult getKeyResult = (GetKeyResult) data.getOperationResult();
        switch (loader.getId()) {
            case LOADER_ID_BYTES:
                if (!getKeyResult.success()) {
                    getKeyResult.createNotify(mActivity).show();
                }
                break;
            case LOADER_ID_CLOUD:
                if (getKeyResult.isPending()) {
                    if (getKeyResult.getRequiredInputParcel().mType ==
                            RequiredInputParcel.RequiredInputType.ENABLE_ORBOT) {
                        if (mShowingOrbotDialog) {
                            // to prevent dialogs stacking
                            return;
                        }

                        // this is because we can't commit fragment dialogs in onLoadFinished
                        Runnable showOrbotDialog = new Runnable() {
                            @Override
                            public void run() {
                                OrbotHelper.DialogActions dialogActions =
                                        new OrbotHelper.DialogActions() {
                                            @Override
                                            public void onOrbotStarted() {
                                                mShowingOrbotDialog = false;
                                                restartLoaders();
                                            }

                                            @Override
                                            public void onNeutralButton() {
                                                mParcelableProxy = ParcelableProxy
                                                        .getForNoProxy();
                                                mShowingOrbotDialog = false;
                                                restartLoaders();
                                            }

                                            @Override
                                            public void onCancel() {
                                                mShowingOrbotDialog = false;
                                            }
                                        };

                                if (OrbotHelper.putOrbotInRequiredState(dialogActions, mActivity)) {
                                    // looks like we didn't have to show the
                                    // dialog after all
                                    mShowingOrbotDialog = false;
                                    restartLoaders();
                                }
                            }
                        };
                        new Handler().post(showOrbotDialog);
                        mShowingOrbotDialog = true;
                    }
                } else if (!getKeyResult.success()) {
                    getKeyResult.createNotify(mActivity).show();
                }
                break;
        }
    }

    @Override
    public void onLoaderReset(
            Loader<AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>> loader) {

        mAdapter.clearData();
    }

}
