/*
 * ownCloud Android client application
 *
 * @author Andy Scherzinger
 * Copyright (C) 2016 ownCloud Inc.
 * <p/>
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2,
 * as published by the Free Software Foundation.
 * <p/>
 * 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.
 * <p/>
 * 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 com.owncloud.android.ui.activity;

import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AccountManagerCallback;
import android.accounts.AccountManagerFuture;
import android.accounts.OperationCanceledException;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;

import com.evernote.android.job.JobRequest;
import com.evernote.android.job.util.support.PersistableBundleCompat;
import com.owncloud.android.MainApp;
import com.owncloud.android.R;
import com.owncloud.android.authentication.AccountUtils;
import com.owncloud.android.datamodel.ArbitraryDataProvider;
import com.owncloud.android.datamodel.FileDataStorageManager;
import com.owncloud.android.files.services.FileDownloader;
import com.owncloud.android.files.services.FileUploader;
import com.owncloud.android.jobs.AccountRemovalJob;
import com.owncloud.android.lib.common.OwnCloudAccount;
import com.owncloud.android.lib.common.utils.Log_OC;
import com.owncloud.android.services.OperationsService;
import com.owncloud.android.ui.adapter.AccountListAdapter;
import com.owncloud.android.ui.adapter.AccountListItem;
import com.owncloud.android.ui.events.AccountRemovedEvent;
import com.owncloud.android.ui.helpers.FileOperationsHelper;
import com.owncloud.android.utils.DisplayUtils;
import com.owncloud.android.utils.ThemeUtils;

import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
import org.parceler.Parcels;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;

import androidx.core.content.ContextCompat;
import androidx.core.graphics.drawable.DrawableCompat;

/**
 * An Activity that allows the user to manage accounts.
 */
public class ManageAccountsActivity extends FileActivity
        implements AccountListAdapter.AccountListAdapterListener, AccountManagerCallback<Boolean>, ComponentsGetter {
    private static final String TAG = ManageAccountsActivity.class.getSimpleName();

    public static final String KEY_ACCOUNT_LIST_CHANGED = "ACCOUNT_LIST_CHANGED";
    public static final String KEY_CURRENT_ACCOUNT_CHANGED = "CURRENT_ACCOUNT_CHANGED";
    public static final String PENDING_FOR_REMOVAL = "PENDING_FOR_REMOVAL";

    private static final String KEY_DISPLAY_NAME = "DISPLAY_NAME";

    private static final int KEY_USER_INFO_REQUEST_CODE = 13;
    private static final int KEY_DELETE_CODE = 101;
    private static final int SINGLE_ACCOUNT = 1;
    private static final int MIN_MULTI_ACCOUNT_SIZE = 2;

    private ListView mListView;
    private final Handler mHandler = new Handler();
    private String mAccountName;
    private AccountListAdapter mAccountListAdapter;
    private ServiceConnection mDownloadServiceConnection;
    private ServiceConnection mUploadServiceConnection;
    private Set<String> mOriginalAccounts;
    private String mOriginalCurrentAccount;
    private Drawable mTintedCheck;

    private ArbitraryDataProvider arbitraryDataProvider;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        mTintedCheck = DrawableCompat.wrap(ContextCompat.getDrawable(this, R.drawable.account_circle_white));
        int tint = ThemeUtils.elementColor(this);
        DrawableCompat.setTint(mTintedCheck, tint);

        setContentView(R.layout.accounts_layout);

        mListView = findViewById(R.id.account_list);

        setupToolbar();
        updateActionBarTitleAndHomeButtonByString(getResources().getString(R.string.prefs_manage_accounts));

        Account[] accountList = AccountManager.get(this).getAccountsByType(MainApp.getAccountType(this));
        mOriginalAccounts = DisplayUtils.toAccountNameSet(Arrays.asList(accountList));

        Account currentAccount = AccountUtils.getCurrentOwnCloudAccount(this);

        if (currentAccount != null) {
            mOriginalCurrentAccount = currentAccount.name;
        }

        setAccount(currentAccount);
        onAccountSet(false);

        arbitraryDataProvider = new ArbitraryDataProvider(getContentResolver());

        mAccountListAdapter = new AccountListAdapter(this, getAccountListItems(), mTintedCheck);

        mListView.setAdapter(mAccountListAdapter);

        final Intent intent = new Intent(this, UserInfoActivity.class);

        mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                AccountListItem item = mAccountListAdapter.getItem(position);

                if (item != null && item.isEnabled()) {
                    Account account = item.getAccount();
                    intent.putExtra(UserInfoActivity.KEY_ACCOUNT, Parcels.wrap(account));
                    try {
                        OwnCloudAccount oca = new OwnCloudAccount(account, MainApp.getAppContext());
                        intent.putExtra(KEY_DISPLAY_NAME, oca.getDisplayName());
                    } catch (com.owncloud.android.lib.common.accounts.AccountUtils.AccountNotFoundException e) {
                        Log_OC.d(TAG, "Failed to find NC account");
                    }

                    startActivityForResult(intent, KEY_USER_INFO_REQUEST_CODE);
                }
            }
        });

        initializeComponentGetters();
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        switch (resultCode) {
            case KEY_DELETE_CODE:
                if (data != null) {
                    Bundle bundle = data.getExtras();
                    if (bundle.containsKey(UserInfoActivity.KEY_ACCOUNT)) {
                        Account account = Parcels.unwrap(bundle.getParcelable(UserInfoActivity.KEY_ACCOUNT));
                        mAccountName = account.name;
                        performAccountRemoval(account);
                    }
                }
                break;
            default:
                break;
        }
    }

    @Override
    public void onBackPressed() {
        Intent resultIntent = new Intent();
        resultIntent.putExtra(KEY_ACCOUNT_LIST_CHANGED, hasAccountListChanged());
        resultIntent.putExtra(KEY_CURRENT_ACCOUNT_CHANGED, hasCurrentAccountChanged());
        setResult(RESULT_OK, resultIntent);

        super.onBackPressed();
    }

    /**
     * checks the set of actual accounts against the set of original accounts when the activity has been started.
     *
     * @return true if account list has changed, false if not
     */
    private boolean hasAccountListChanged() {
        Account[] accountList = AccountManager.get(this).getAccountsByType(MainApp.getAccountType(this));

        ArrayList<Account> newList = new ArrayList<>();
        for (Account account : accountList) {
            boolean pendingForRemoval = arbitraryDataProvider.getBooleanValue(account, PENDING_FOR_REMOVAL);

            if (!pendingForRemoval) {
                newList.add(account);
            }
        }

        Set<String> actualAccounts = DisplayUtils.toAccountNameSet(newList);
        return !mOriginalAccounts.equals(actualAccounts);
    }

    /**
     * checks actual current account against current accounts when the activity has been started.
     *
     * @return true if account list has changed, false if not
     */
    private boolean hasCurrentAccountChanged() {
        Account account = AccountUtils.getCurrentOwnCloudAccount(this);
        if (account == null) {
            return true;
        } else {
            return !account.name.equals(mOriginalCurrentAccount);
        }
    }

    /**
     * Initialize ComponentsGetters.
     */
    private void initializeComponentGetters() {
        mDownloadServiceConnection = newTransferenceServiceConnection();
        if (mDownloadServiceConnection != null) {
            bindService(new Intent(this, FileDownloader.class), mDownloadServiceConnection,
                    Context.BIND_AUTO_CREATE);
        }
        mUploadServiceConnection = newTransferenceServiceConnection();
        if (mUploadServiceConnection != null) {
            bindService(new Intent(this, FileUploader.class), mUploadServiceConnection,
                    Context.BIND_AUTO_CREATE);
        }
    }

    /**
     * creates the account list items list including the add-account action in case multiaccount_support is enabled.
     *
     * @return list of account list items
     */
    private List<AccountListItem> getAccountListItems() {
        Account[] accountList = AccountManager.get(this).getAccountsByType(MainApp.getAccountType(this));
        List<AccountListItem> adapterAccountList = new ArrayList<>(accountList.length);
        for (Account account : accountList) {
            boolean pendingForRemoval = arbitraryDataProvider.getBooleanValue(account, PENDING_FOR_REMOVAL);
            adapterAccountList.add(new AccountListItem(account, !pendingForRemoval));
        }

        // Add Create Account item at the end of account list if multi-account is enabled
        if (getResources().getBoolean(R.bool.multiaccount_support)) {
            adapterAccountList.add(new AccountListItem());
        }

        return adapterAccountList;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        boolean retval = true;
        switch (item.getItemId()) {
            case android.R.id.home:
                onBackPressed();
                break;
            default:
                retval = super.onOptionsItemSelected(item);
                break;
        }
        return retval;
    }

    @Override
    public void showFirstRunActivity() {
        Intent firstRunIntent = new Intent(getApplicationContext(), FirstRunActivity.class);
        firstRunIntent.putExtra(FirstRunActivity.EXTRA_ALLOW_CLOSE, true);
        startActivity(firstRunIntent);
    }

    @Override
    public void createAccount() {
        AccountManager am = AccountManager.get(getApplicationContext());
        am.addAccount(MainApp.getAccountType(this),
                null,
                null,
                null,
                this,
                new AccountManagerCallback<Bundle>() {
                    @Override
                    public void run(AccountManagerFuture<Bundle> future) {
                        if (future != null) {
                            try {
                                Bundle result = future.getResult();
                                String name = result.getString(AccountManager.KEY_ACCOUNT_NAME);
                                AccountUtils.setCurrentOwnCloudAccount(getApplicationContext(), name);
                                mAccountListAdapter = new AccountListAdapter(
                                        ManageAccountsActivity.this,
                                        getAccountListItems(),
                                        mTintedCheck
                                );
                                mListView.setAdapter(mAccountListAdapter);
                                runOnUiThread(new Runnable() {
                                    @Override
                                    public void run() {
                                        mAccountListAdapter.notifyDataSetChanged();
                                    }
                                });
                            } catch (OperationCanceledException e) {
                                Log_OC.d(TAG, "Account creation canceled");
                            } catch (Exception e) {
                                Log_OC.e(TAG, "Account creation finished in exception: ", e);
                            }
                        }
                    }
                }, mHandler);
    }

    @Subscribe(threadMode = ThreadMode.MAIN)
    public void onAccountRemovedEvent(AccountRemovedEvent event) {
        List<AccountListItem> accountListItemArray = getAccountListItems();
        mAccountListAdapter.clear();
        mAccountListAdapter.addAll(accountListItemArray);
        mAccountListAdapter.notifyDataSetChanged();
    }

    @Override
    public void run(AccountManagerFuture<Boolean> future) {
        if (future.isDone()) {
            // after remove account
            Account account = new Account(mAccountName, MainApp.getAccountType(this));
            if (!AccountUtils.exists(account, MainApp.getAppContext())) {
                // Cancel transfers of the removed account
                if (mUploaderBinder != null) {
                    mUploaderBinder.cancel(account);
                }
                if (mDownloaderBinder != null) {
                    mDownloaderBinder.cancel(account);
                }
            }

            if (AccountUtils.getCurrentOwnCloudAccount(this) == null) {
                String accountName = "";
                Account[] accounts = AccountManager.get(this).getAccountsByType(MainApp.getAccountType(this));
                if (accounts.length != 0) {
                    accountName = accounts[0].name;
                }
                AccountUtils.setCurrentOwnCloudAccount(this, accountName);
            }

            List<AccountListItem> accountListItemArray = getAccountListItems();
            if (accountListItemArray.size() > SINGLE_ACCOUNT) {
                mAccountListAdapter = new AccountListAdapter(this, accountListItemArray, mTintedCheck);
                mListView.setAdapter(mAccountListAdapter);
            } else {
                onBackPressed();
            }
        }
    }

    @Override
    protected void onDestroy() {
        if (mDownloadServiceConnection != null) {
            unbindService(mDownloadServiceConnection);
            mDownloadServiceConnection = null;
        }
        if (mUploadServiceConnection != null) {
            unbindService(mUploadServiceConnection);
            mUploadServiceConnection = null;
        }

        super.onDestroy();
    }

    public Handler getHandler() { return mHandler; }

    @Override
    public FileUploader.FileUploaderBinder getFileUploaderBinder() {
        return mUploaderBinder;
    }

    @Override
    public OperationsService.OperationsServiceBinder getOperationsServiceBinder() {
        return null;
    }

    @Override
    public FileDataStorageManager getStorageManager() {
        return super.getStorageManager();
    }

    @Override
    public FileOperationsHelper getFileOperationsHelper() {
        return null;
    }

    protected ServiceConnection newTransferenceServiceConnection() {
        return new ManageAccountsServiceConnection();
    }

    private void performAccountRemoval(Account account) {
        // disable account in list view
        for (int i = 0; i < mAccountListAdapter.getCount(); i++) {
            AccountListItem item = mAccountListAdapter.getItem(i);

            if (item != null && item.getAccount().equals(account)) {
                item.setEnabled(false);
                break;
            }

            mAccountListAdapter.notifyDataSetChanged();
        }

        // store pending account removal
        ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProvider(getContentResolver());
        arbitraryDataProvider.storeOrUpdateKeyValue(account.name, PENDING_FOR_REMOVAL, String.valueOf(true));

        // Cancel transfers
        if (mUploaderBinder != null) {
            mUploaderBinder.cancel(account);
        }
        if (mDownloaderBinder != null) {
            mDownloaderBinder.cancel(account);
        }

        // schedule job
        PersistableBundleCompat bundle = new PersistableBundleCompat();
        bundle.putString(AccountRemovalJob.ACCOUNT, account.name);

        new JobRequest.Builder(AccountRemovalJob.TAG)
                .startNow()
                .setExtras(bundle)
                .setUpdateCurrent(false)
                .build()
                .schedule();

        // immediately select a new account
        Account[] accounts = AccountManager.get(this).getAccountsByType(MainApp.getAccountType(this));

        String newAccountName = "";
        for (Account acc: accounts) {
            if (!account.name.equalsIgnoreCase(acc.name)) {
                newAccountName = acc.name;
                break;
            }
        }

        if (newAccountName.isEmpty()) {
            Log_OC.d(TAG, "new account set to null");
            AccountUtils.resetOwnCloudAccount(this);
        } else {
            Log_OC.d(TAG, "new account set to: " + newAccountName);
            AccountUtils.setCurrentOwnCloudAccount(this, newAccountName);
        }

        // only one to be (deleted) account remaining
        if (accounts.length < MIN_MULTI_ACCOUNT_SIZE) {
            Intent resultIntent = new Intent();
            resultIntent.putExtra(KEY_ACCOUNT_LIST_CHANGED, true);
            resultIntent.putExtra(KEY_CURRENT_ACCOUNT_CHANGED, true);
            setResult(RESULT_OK, resultIntent);

            super.onBackPressed();
        }
    }

    /**
     * Defines callbacks for service binding, passed to bindService()
     */
    private class ManageAccountsServiceConnection implements ServiceConnection {

        @Override
        public void onServiceConnected(ComponentName component, IBinder service) {

            if (component.equals(new ComponentName(ManageAccountsActivity.this, FileDownloader.class))) {
                mDownloaderBinder = (FileDownloader.FileDownloaderBinder) service;

            } else if (component.equals(new ComponentName(ManageAccountsActivity.this, FileUploader.class))) {
                Log_OC.d(TAG, "Upload service connected");
                mUploaderBinder = (FileUploader.FileUploaderBinder) service;
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName component) {
            if (component.equals(new ComponentName(ManageAccountsActivity.this, FileDownloader.class))) {
                Log_OC.d(TAG, "Download service suddenly disconnected");
                mDownloaderBinder = null;
            } else if (component.equals(new ComponentName(ManageAccountsActivity.this, FileUploader.class))) {
                Log_OC.d(TAG, "Upload service suddenly disconnected");
                mUploaderBinder = null;
            }
        }
    }

}
