/*
 * Copyright (C) 2014 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.email.activity.setup;

import android.accounts.AccountManagerFuture;
import android.accounts.AuthenticatorException;
import android.accounts.OperationCanceledException;
import android.app.Fragment;
import android.app.LoaderManager;
import android.content.Context;
import android.content.Intent;
import android.content.Loader;
import android.os.Bundle;
import android.os.Handler;
import android.os.RemoteException;

import com.android.email.provider.EmailProvider;
import com.android.email.service.EmailServiceUtils;
import com.android.email2.ui.MailActivityEmail;
import com.android.emailcommon.provider.Account;
import com.android.emailcommon.service.EmailServiceProxy;
import com.android.mail.preferences.AccountPreferences;
import com.android.mail.ui.MailAsyncTaskLoader;
import com.android.mail.utils.LogUtils;

import java.io.IOException;

/**
 * This retained headless fragment acts as a container for the multi-step task of creating the
 * AccountManager account and saving our account object to the database, as well as some misc
 * related background tasks.
 */
public class AccountCreationFragment extends Fragment {
    public static final String TAG = "AccountCreationFragment";

    public static final int REQUEST_CODE_ACCEPT_POLICIES = 1;

    private static final String ACCOUNT_TAG = "account";
    private static final String SYNC_EMAIL_TAG = "email";
    private static final String SYNC_CALENDAR_TAG = "calendar";
    private static final String SYNC_CONTACTS_TAG = "contacts";
    private static final String NOTIFICATIONS_TAG = "notifications";

    private static final String SAVESTATE_STAGE = "AccountCreationFragment.stage";
    private static final int STAGE_BEFORE_ACCOUNT_SECURITY = 0;
    private static final int STAGE_REFRESHING_ACCOUNT = 1;
    private static final int STAGE_WAITING_FOR_ACCOUNT_SECURITY = 2;
    private static final int STAGE_AFTER_ACCOUNT_SECURITY = 3;
    private int mStage = 0;

    private Context mAppContext;
    private final Handler mHandler;

    public interface Callback {
        void onAccountCreationFragmentComplete();
        void destroyAccountCreationFragment();
        void showCreateAccountErrorDialog();
        void setAccount(Account account);
    }

    public AccountCreationFragment() {
        mHandler = new Handler();
    }

    public static AccountCreationFragment newInstance(Account account, boolean syncEmail,
            boolean syncCalendar, boolean syncContacts, boolean enableNotifications) {
        final Bundle args = new Bundle(5);
        args.putParcelable(AccountCreationFragment.ACCOUNT_TAG, account);
        args.putBoolean(AccountCreationFragment.SYNC_EMAIL_TAG, syncEmail);
        args.putBoolean(AccountCreationFragment.SYNC_CALENDAR_TAG, syncCalendar);
        args.putBoolean(AccountCreationFragment.SYNC_CONTACTS_TAG, syncContacts);
        args.putBoolean(AccountCreationFragment.NOTIFICATIONS_TAG, enableNotifications);

        final AccountCreationFragment f = new AccountCreationFragment();
        f.setArguments(args);
        return f;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setRetainInstance(true);
        if (savedInstanceState != null) {
            mStage = savedInstanceState.getInt(SAVESTATE_STAGE);
        }
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        mAppContext = getActivity().getApplicationContext();
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putInt(SAVESTATE_STAGE, mStage);
    }

    @Override
    public void onResume() {
        super.onResume();

        switch (mStage) {
            case STAGE_BEFORE_ACCOUNT_SECURITY:
                kickBeforeAccountSecurityLoader();
                break;
            case STAGE_REFRESHING_ACCOUNT:
                kickRefreshingAccountLoader();
                break;
            case STAGE_WAITING_FOR_ACCOUNT_SECURITY:
                // TODO: figure out when we might get here and what to do if we do
                break;
            case STAGE_AFTER_ACCOUNT_SECURITY:
                kickAfterAccountSecurityLoader();
                break;
        }
    }

    private void kickBeforeAccountSecurityLoader() {
        final LoaderManager loaderManager = getLoaderManager();

        loaderManager.destroyLoader(STAGE_REFRESHING_ACCOUNT);
        loaderManager.destroyLoader(STAGE_AFTER_ACCOUNT_SECURITY);
        loaderManager.initLoader(STAGE_BEFORE_ACCOUNT_SECURITY, getArguments(),
                new BeforeAccountSecurityCallbacks());
    }

    private void kickRefreshingAccountLoader() {
        final LoaderManager loaderManager = getLoaderManager();

        loaderManager.destroyLoader(STAGE_BEFORE_ACCOUNT_SECURITY);
        loaderManager.destroyLoader(STAGE_AFTER_ACCOUNT_SECURITY);
        loaderManager.initLoader(STAGE_REFRESHING_ACCOUNT, getArguments(),
                new RefreshAccountCallbacks());
    }

    private void kickAfterAccountSecurityLoader() {
        final LoaderManager loaderManager = getLoaderManager();

        loaderManager.destroyLoader(STAGE_BEFORE_ACCOUNT_SECURITY);
        loaderManager.destroyLoader(STAGE_REFRESHING_ACCOUNT);
        loaderManager.initLoader(STAGE_AFTER_ACCOUNT_SECURITY, getArguments(),
                new AfterAccountSecurityCallbacks());
    }

    private class BeforeAccountSecurityCallbacks
            implements LoaderManager.LoaderCallbacks<Boolean> {
        public BeforeAccountSecurityCallbacks() {}

        @Override
        public Loader<Boolean> onCreateLoader(int id, Bundle args) {
            final Account account = args.getParcelable(ACCOUNT_TAG);
            final boolean email = args.getBoolean(SYNC_EMAIL_TAG);
            final boolean calendar = args.getBoolean(SYNC_CALENDAR_TAG);
            final boolean contacts = args.getBoolean(SYNC_CONTACTS_TAG);
            final boolean notificationsEnabled = args.getBoolean(NOTIFICATIONS_TAG);

            /**
             * Task loader returns true if we created the account, false if we bailed out.
             */
            return new MailAsyncTaskLoader<Boolean>(mAppContext) {
                @Override
                protected void onDiscardResult(Boolean result) {}

                @Override
                public Boolean loadInBackground() {
                    // Set the incomplete flag here to avoid reconciliation issues
                    account.mFlags |= Account.FLAGS_INCOMPLETE;

                    AccountSettingsUtils.commitSettings(mAppContext, account);
                    final AccountManagerFuture<Bundle> future =
                            EmailServiceUtils.setupAccountManagerAccount(mAppContext, account,
                                    email, calendar, contacts, null);

                    boolean createSuccess = false;
                    try {
                        future.getResult();
                        createSuccess = true;
                    } catch (OperationCanceledException e) {
                        LogUtils.d(LogUtils.TAG, "addAccount was canceled");
                    } catch (IOException e) {
                        LogUtils.d(LogUtils.TAG, "addAccount failed: " + e);
                    } catch (AuthenticatorException e) {
                        LogUtils.d(LogUtils.TAG, "addAccount failed: " + e);
                    }
                    if (!createSuccess) {
                        return false;
                    }
                    // We can move the notification setting to the inbox FolderPreferences
                    // later, once we know what the inbox is
                    new AccountPreferences(mAppContext, account.getEmailAddress())
                            .setDefaultInboxNotificationsEnabled(notificationsEnabled);

                    // Now that AccountManager account creation is complete, clear the
                    // INCOMPLETE flag
                    account.mFlags &= ~Account.FLAGS_INCOMPLETE;
                    AccountSettingsUtils.commitSettings(mAppContext, account);

                    return true;
                }
            };
        }

        @Override
        public void onLoadFinished(Loader<Boolean> loader, Boolean success) {
            if (success == null || !isResumed()) {
                return;
            }
            if (success) {
                mStage = STAGE_REFRESHING_ACCOUNT;
                kickRefreshingAccountLoader();
            } else {
                final Callback callback = (Callback) getActivity();
                mHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        if (!isResumed()) {
                            return;
                        }
                        // Can't do this from within onLoadFinished
                        callback.destroyAccountCreationFragment();
                        callback.showCreateAccountErrorDialog();
                    }
                });
            }
        }

        @Override
        public void onLoaderReset(Loader<Boolean> loader) {}
    }

    private class RefreshAccountCallbacks implements LoaderManager.LoaderCallbacks<Account> {

        @Override
        public Loader<Account> onCreateLoader(int id, Bundle args) {
            final Account account = args.getParcelable(ACCOUNT_TAG);
            return new MailAsyncTaskLoader<Account>(mAppContext) {
                @Override
                protected void onDiscardResult(Account result) {}

                @Override
                public Account loadInBackground() {
                    account.refresh(mAppContext);
                    return account;
                }
            };
        }

        @Override
        public void onLoadFinished(Loader<Account> loader, Account account) {
            if (account == null || !isResumed()) {
                return;
            }

            getArguments().putParcelable(ACCOUNT_TAG, account);

            if ((account.mFlags & Account.FLAGS_SECURITY_HOLD) != 0) {
                final Intent intent = AccountSecurity
                        .actionUpdateSecurityIntent(getActivity(), account.mId, false);
                startActivityForResult(intent, REQUEST_CODE_ACCEPT_POLICIES);
                mStage = STAGE_WAITING_FOR_ACCOUNT_SECURITY;
            } else {
                mStage = STAGE_AFTER_ACCOUNT_SECURITY;
                kickAfterAccountSecurityLoader();
            }
        }

        @Override
        public void onLoaderReset(Loader<Account> loader) {}
    }

    private class AfterAccountSecurityCallbacks
            implements LoaderManager.LoaderCallbacks<Account> {
        @Override
        public Loader<Account> onCreateLoader(int id, Bundle args) {
            final Account account = args.getParcelable(ACCOUNT_TAG);
            return new MailAsyncTaskLoader<Account>(mAppContext) {
                @Override
                protected void onDiscardResult(Account result) {}

                @Override
                public Account loadInBackground() {
                    // Clear the security hold flag now
                    account.mFlags &= ~Account.FLAGS_SECURITY_HOLD;
                    AccountSettingsUtils.commitSettings(mAppContext, account);
                    // Start up services based on new account(s)
                    EmailProvider.setServicesEnabledSync(mAppContext);
                    EmailServiceUtils
                            .startService(mAppContext, account.mHostAuthRecv.mProtocol);
                    return account;
                }
            };
        }

        @Override
        public void onLoadFinished(final Loader<Account> loader, final Account account) {
            // Need to do this from a runnable because this triggers fragment transactions
            mHandler.post(new Runnable() {
                @Override
                public void run() {
                    if (account == null || !isResumed()) {
                        return;
                    }

                    // Move to final setup screen
                    Callback callback = (Callback) getActivity();
                    callback.setAccount(account);
                    callback.onAccountCreationFragmentComplete();

                    // Update the folder list (to get our starting folders, e.g. Inbox)
                    final EmailServiceProxy proxy = EmailServiceUtils
                            .getServiceForAccount(mAppContext, account.mId);
                    try {
                        proxy.updateFolderList(account.mId);
                    } catch (RemoteException e) {
                        // It's all good
                    }

                }
            });
        }

        @Override
        public void onLoaderReset(Loader<Account> loader) {}
    }

    /**
     * This is called after the AccountSecurity activity completes.
     */
    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        mStage = STAGE_AFTER_ACCOUNT_SECURITY;
        // onResume() will be called immediately after this to kick the next loader
    }
}
