/*
 * 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.adapter;


import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import android.content.Intent;
import androidx.databinding.DataBindingUtil;
import androidx.fragment.app.FragmentActivity;
import androidx.recyclerview.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;

import androidx.databinding.DataBindingUtil;
import androidx.fragment.app.FragmentActivity;
import androidx.recyclerview.widget.RecyclerView;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.daos.KeyRepository;
import org.sufficientlysecure.keychain.databinding.ImportKeysListItemBinding;
import org.sufficientlysecure.keychain.keyimport.HkpKeyserverAddress;
import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry;
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
import org.sufficientlysecure.keychain.keyimport.processing.ImportKeysListener;
import org.sufficientlysecure.keychain.keyimport.processing.ImportKeysOperationCallback;
import org.sufficientlysecure.keychain.keyimport.processing.ImportKeysResultListener;
import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo;
import org.sufficientlysecure.keychain.operations.ImportOperation;
import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing;
import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing.VerificationStatus;
import org.sufficientlysecure.keychain.service.ImportKeyringParcel;
import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper;
import org.sufficientlysecure.keychain.ui.keyview.ViewKeyActivity;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.ui.util.Notify;
import org.sufficientlysecure.keychain.util.ParcelableFileCache;
import timber.log.Timber;


public class ImportKeysAdapter extends RecyclerView.Adapter<ImportKeysAdapter.ViewHolder>
        implements ImportKeysResultListener {

    private FragmentActivity mActivity;
    private ImportKeysResultListener mListener;
    private boolean mNonInteractive;

    private List<ImportKeysListEntry> mData;
    private KeyState[] mKeyStates;

    private KeyRepository mKeyRepository;

    public ImportKeysAdapter(FragmentActivity activity, ImportKeysListener listener,
                             boolean nonInteractive) {

        mActivity = activity;
        mListener = listener;
        mNonInteractive = nonInteractive;

        mKeyRepository = KeyRepository.create(activity);
    }

    public void setData(List<ImportKeysListEntry> data) {
        mData = data;

        mKeyStates = new KeyState[data.size()];
        for (int i = 0; i < mKeyStates.length; i++) {
            ImportKeysListEntry entry = mData.get(i);

            KeyState keyState = new KeyState();
            long keyId = KeyFormattingUtils.convertKeyIdHexToKeyId(entry.getKeyIdHex());
            try {
                VerificationStatus verified;
                if (entry.isSecretKey()) {
                    verified = mKeyRepository.getCanonicalizedSecretKeyRing(keyId).getVerified();
                } else {
                    UnifiedKeyInfo unifiedKeyInfo = mKeyRepository.getUnifiedKeyInfo(keyId);
                    verified = unifiedKeyInfo != null ? unifiedKeyInfo.verified() : null;
                }
                keyState.mAlreadyPresent = true;
                keyState.mVerified = verified != null && verified != VerificationStatus.UNVERIFIED;
            } catch (KeyRepository.NotFoundException ignored) {
            }

            mKeyStates[i] = keyState;
        }

        // If there is only one key, get it automatically
        if (mData.size() == 1) {
            getKeyWithProgress(0, mData.get(0), true);
        }

        notifyDataSetChanged();
    }

    public void clearData() {
        mData = null;
        mKeyStates = null;
        notifyDataSetChanged();
    }

    /**
     * This method returns a list of all selected entries, with public keys sorted
     * before secret keys, see ImportOperation for specifics.
     *
     * @see ImportOperation
     */
    public List<ImportKeysListEntry> getEntries() {
        ArrayList<ImportKeysListEntry> result = new ArrayList<>();
        ArrayList<ImportKeysListEntry> secrets = new ArrayList<>();
        if (mData == null) {
            return result;
        }
        for (ImportKeysListEntry entry : mData) {
            // add this entry to either the secret or the public list
            (entry.isSecretKey() ? secrets : result).add(entry);
        }
        // add secret keys at the end of the list
        result.addAll(secrets);
        return result;
    }

    public class ViewHolder extends RecyclerView.ViewHolder {
        public ImportKeysListItemBinding b;

        public ViewHolder(View view) {
            super(view);
            b = DataBindingUtil.bind(view);
            b.setNonInteractive(mNonInteractive);
        }
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        LayoutInflater inflater = LayoutInflater.from(mActivity);
        return new ViewHolder(inflater.inflate(R.layout.import_keys_list_item, parent, false));
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, final int position) {
        final ImportKeysListItemBinding b = holder.b;
        final ImportKeysListEntry entry = mData.get(position);
        b.setEntry(entry);

        final KeyState keyState = mKeyStates[position];

        b.card.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                if (!keyState.mDownloaded) {
                    getKeyWithProgress(position, entry, true);
                } else {
                    changeShowed(position, !keyState.mShowed);
                }
            }
        });

        b.extra.importKey.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                getKeyWithProgress(position, entry, false);
            }
        });

        b.extra.showKey.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                long keyId = KeyFormattingUtils.convertKeyIdHexToKeyId(entry.getKeyIdHex());
                Intent intent = ViewKeyActivity.getViewKeyActivityIntent(mActivity, keyId);
                mActivity.startActivity(intent);
            }
        });

        b.extraContainer.setVisibility(keyState.mShowed ? View.VISIBLE : View.GONE);

        b.progress.setVisibility(keyState.mProgress ? View.VISIBLE : View.GONE);
        b.extra.importKey.setEnabled(!keyState.mProgress);
    }

    @Override
    public int getItemCount() {
        return mData != null ? mData.size() : 0;
    }

    private void getKeyWithProgress(int position, ImportKeysListEntry entry, boolean skipSave) {
        if (isLoading(position)) {
            return;
        }

        changeProgress(position, true);
        getKey(position, entry, skipSave);
    }

    private void getKey(int position, ImportKeysListEntry entry, boolean skipSave) {
        ImportKeyringParcel inputParcel = prepareKeyOperation(entry, skipSave);
        ImportKeysResultListener listener = skipSave ? this : mListener;
        ImportKeysOperationCallback cb = new ImportKeysOperationCallback(listener, inputParcel, position);
        CryptoOperationHelper opHelper = new CryptoOperationHelper<>(1, mActivity, cb, null);
        opHelper.cryptoOperation();
    }

    private ImportKeyringParcel prepareKeyOperation(ImportKeysListEntry entry, boolean skipSave) {
        ParcelableKeyRing keyRing = entry.getParcelableKeyRing();
        if (keyRing.getBytes() != null) {
            // instead of giving the entries by Intent extra, cache them into a
            // file to prevent Java Binder problems on heavy imports
            // read FileImportCache for more info.
            try {
                // We parcel this iteratively into a file - anything we can
                // display here, we should be able to import.
                ParcelableFileCache<ParcelableKeyRing> cache =
                        new ParcelableFileCache<>(mActivity, ImportOperation.CACHE_FILE_NAME);
                cache.writeCache(keyRing);
            } catch (IOException e) {
                Timber.e(e, "Problem writing cache file");
                Notify.create(mActivity, "Problem writing cache file!", Notify.Style.ERROR).show();
            }

            if (skipSave) {
                return ImportKeyringParcel.createFromFileCacheWithSkipSave();
            } else {
                return ImportKeyringParcel.createFromFileCache();
            }
        } else {
            ArrayList<ParcelableKeyRing> keysList = new ArrayList<>();
            keysList.add(keyRing);

            HkpKeyserverAddress keyserver = entry.getKeyserver();

            if (skipSave) {
                return ImportKeyringParcel.createWithSkipSave(keysList, keyserver);
            } else {
                return ImportKeyringParcel.createImportKeyringParcel(keysList, keyserver);
            }
        }
    }

    @Override
    public void handleResult(ImportKeyResult result, Integer position) {
        if (position == null) {
            throw new IllegalArgumentException("position parameter must be non-null!");
        }

        boolean resultStatus = result.success();
        Timber.e("getKey result: " + resultStatus);
        if (resultStatus) {
            ArrayList<CanonicalizedKeyRing> canKeyRings = result.mCanonicalizedKeyRings;
            if (canKeyRings.size() == 1) {
                CanonicalizedKeyRing keyRing = canKeyRings.get(0);
                Timber.e("Key ID: " + keyRing.getMasterKeyId() +
                        "| isRev: " + keyRing.isRevoked() + "| isExp: " + keyRing.isExpired()
                        + "| isSec: " + keyRing.isSecure());

                ImportKeysListEntry entry = mData.get(position);
                entry.setUpdated(result.isOkUpdated());

                mergeEntryWithKey(entry, keyRing);

                mKeyStates[position].mDownloaded = true;
                changeShowed(position, true);
            } else {
                throw new RuntimeException("getKey retrieved more than one key ("
                        + canKeyRings.size() + ")");
            }
        } else {
            result.createNotify(mActivity).show();
        }

        changeProgress(position, false);
    }

    private void mergeEntryWithKey(ImportKeysListEntry entry, CanonicalizedKeyRing keyRing) {
        entry.setRevoked(keyRing.isRevoked());
        entry.setExpired(keyRing.isExpired());
        entry.setSecure(keyRing.isSecure());

        if (entry.getAlgorithm() == null) {
            int algorithmId = keyRing.getPublicKey().getAlgorithm();
            Integer bitSize = keyRing.getPublicKey().getBitStrength();
            String oid = keyRing.getPublicKey().getCurveOid();
            String algoInfo = KeyFormattingUtils.getAlgorithmInfo(algorithmId, bitSize, oid);

            entry.setAlgorithm(algoInfo);
        }

        Date expectedDate = entry.getDate();
        Date creationDate = keyRing.getCreationDate();
        if (expectedDate == null) {
            entry.setDate(creationDate);
        } else if (!expectedDate.equals(creationDate)) {
            throw new AssertionError("Creation date doesn't match the expected one");
        }
        entry.setKeyId(keyRing.getMasterKeyId());
        entry.setUserIds(keyRing.getUnorderedUserIds());
    }

    private class KeyState {
        boolean mAlreadyPresent = false;
        boolean mVerified = false;

        boolean mProgress = false;
        boolean mDownloaded = false;
        boolean mShowed = false;
    }

    private void changeShowed(int position, boolean showed) {
        KeyState keyState = mKeyStates[position];
        keyState.mShowed = showed;
        notifyItemChanged(position);
    }

    private boolean isLoading(int position) {
        KeyState keyState = mKeyStates[position];
        return keyState.mProgress;
    }

    private void changeProgress(int position, boolean progress) {
        KeyState keyState = mKeyStates[position];
        keyState.mProgress = progress;
        notifyItemChanged(position);
    }
}
