/*
 * Copyright 2010 Google Inc.
 *
 * 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.todoroo.astrid.gtasks.auth;

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

import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AccountManagerCallback;
import android.accounts.AccountManagerFuture;
import android.accounts.AuthenticatorException;
import android.accounts.OperationCanceledException;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;

import com.google.api.client.googleapis.extensions.android2.auth.GoogleAccountManager;

/**
 * AuthManager keeps track of the current auth token for a user. The advantage
 * over just passing around a String is that this class can renew the auth
 * token if necessary, and it will change for all classes using this
 * AuthManager.
 */
public class ModernAuthManager implements AuthManager {
  protected static final int GET_LOGIN_REQUEST = 1;

/** The activity that will handle auth result callbacks. */
  private final Activity activity;

  /** The name of the service to authorize for. */
  private final String service;

  /** The most recently fetched auth token or null if none is available. */
  private String authToken;

  private final AccountManager accountManager;

  private Runnable whenFinished;

  /**
   * AuthManager requires many of the same parameters as
   * {@link com.google.android.googlelogindist.GoogleLoginServiceHelper
   * #getCredentials(Activity, int, Bundle, boolean, String, boolean)}.
   * The activity must have a handler in {@link Activity#onActivityResult} that
   * calls {@link #authResult(int, Intent)} if the request code is the code
   * given here.
   *
   * @param activity An activity with a handler in
   *        {@link Activity#onActivityResult} that calls
   *        {@link #authResult(int, Intent)} when {@literal code} is the request
   *        code
   * @param code The request code to pass to
   *        {@link Activity#onActivityResult} when
   *        {@link #authResult(int, Intent)} should be called
   * @param extras A {@link Bundle} of extras for
   *        {@link com.google.android.googlelogindist.GoogleLoginServiceHelper}
   * @param requireGoogle True if the account must be a Google account
   * @param service The name of the service to authenticate as
   */
  public ModernAuthManager(Activity activity, int code, Bundle extras,
      boolean requireGoogle, String service) {
    this.activity = activity;
    this.service = service;
    this.accountManager = AccountManager.get(activity);
  }

  /**
   * Call this to do the initial login. The user will be asked to login if
   * they haven't already. The {@link Runnable} provided will be executed
   * when the auth token is successfully fetched.
   *
   * @param runnable A {@link Runnable} to execute when the auth token
   *        has been successfully fetched and is available via
   *        {@link #getAuthToken()}
   */
  @SuppressWarnings("nls")
  public void doLogin(final Runnable runnable, Object o) {
    this.whenFinished = runnable;
    if (!(o instanceof Account)) {
      throw new IllegalArgumentException("FroyoAuthManager requires an account.");
    }
    Account account = (Account) o;
    accountManager.getAuthToken(account, service, true,
            new AccountManagerCallback<Bundle>() {
        public void run(AccountManagerFuture<Bundle> future) {
          try {
            Bundle result = future.getResult();

            // AccountManager needs user to grant permission
            if (result.containsKey(AccountManager.KEY_INTENT)) {
              Intent intent = (Intent) result.get(AccountManager.KEY_INTENT);
              clearNewTaskFlag(intent);
              activity.startActivityForResult(intent, GET_LOGIN_REQUEST);
              return;
            }

            authToken = result.getString(
                AccountManager.KEY_AUTHTOKEN);
            Log.e("gtasks-auth", "Got auth token.");
            runWhenFinished();
          } catch (OperationCanceledException e) {
            Log.e("gtasks-auth", "Operation Canceled", e);
          } catch (IOException e) {
            Log.e("gtasks-auth", "IOException", e);
          } catch (AuthenticatorException e) {
            Log.e("gtasks-auth", "Authentication Failed", e);
          }
        }
    }, null /* handler */);
  }

  private static void clearNewTaskFlag(Intent intent) {
    int flags = intent.getFlags();
    flags &= ~Intent.FLAG_ACTIVITY_NEW_TASK;
    intent.setFlags(flags);
  }

  /**
   * The {@link Activity} passed into the constructor should call this
   * function when it gets {@link Activity#onActivityResult} with the request
   * code passed into the constructor. The resultCode and results should
   * come directly from the {@link Activity#onActivityResult} function. This
   * function will return true if an auth token was successfully fetched or
   * the process is not finished.
   *
   * @param resultCode The result code passed in to the {@link Activity}'s
   *        {@link Activity#onActivityResult} function
   * @param results The data passed in to the {@link Activity}'s
   *        {@link Activity#onActivityResult} function
   * @return True if the auth token was fetched or we aren't done fetching
   *         the auth token, or False if there was an error or the request was
   *         canceled
   */
  @SuppressWarnings("nls")
  public boolean authResult(int resultCode, Intent results) {
    if (results != null) {
      authToken = results.getStringExtra(
          AccountManager.KEY_AUTHTOKEN);
      Log.w("google-auth", "authResult: " + authToken);
    } else {
      Log.e("google-auth", "No auth result results!!");
    }
    runWhenFinished();
    return authToken != null;
  }

  /**
   * Returns the current auth token. Response may be null if no valid auth
   * token has been fetched.
   *
   * @return The current auth token or null if no auth token has been
   *         fetched
   */
  public String getAuthToken() {
    return authToken;
  }

  /**
   * Invalidates the existing auth token and request a new one. The
   * {@link Runnable} provided will be executed when the new auth token is
   * successfully fetched.
   *
   * @param runnable A {@link Runnable} to execute when a new auth token
   *        is successfully fetched
   */
  public void invalidateAndRefresh(final Runnable runnable) {
    this.whenFinished = runnable;

    activity.runOnUiThread(new Runnable() {
      public void run() {
        accountManager.invalidateAuthToken("com.google", authToken); //$NON-NLS-1$
        new AccountChooser().chooseAccount(activity,
            new AccountChooser.AccountHandler() {
              @Override
              public void handleAccountSelected(Account account) {
                if (account != null) {
                  doLogin(whenFinished, account);
                } else {
                  runWhenFinished();
                }
              }
            });
      }
    });
  }

  private void runWhenFinished() {
    if (whenFinished != null) {
      (new Thread() {
        @Override
        public void run() {
          whenFinished.run();
        }
      }).start();
    }
  }

    public static String[] getAccounts(Activity activity) {
        GoogleAccountManager accountManager = new GoogleAccountManager(activity);
        Account[] accounts = accountManager.getAccounts();
        ArrayList<String> accountNames = new ArrayList<String>();
        for (Account a : accounts) {
            accountNames.add(a.name);
        }
        return accountNames.toArray(new String[accountNames.size()]);
    }
}
