/*
 * Copyright (C) 2013 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.google.example.games.tanc;

import android.app.AlertDialog;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.util.Log;
import android.widget.Toast;

import com.google.android.gms.auth.api.signin.GoogleSignIn;
import com.google.android.gms.auth.api.signin.GoogleSignInAccount;
import com.google.android.gms.auth.api.signin.GoogleSignInClient;
import com.google.android.gms.auth.api.signin.GoogleSignInOptions;
import com.google.android.gms.common.api.ApiException;
import com.google.android.gms.games.AchievementsClient;
import com.google.android.gms.games.AnnotatedData;
import com.google.android.gms.games.EventsClient;
import com.google.android.gms.games.Games;
import com.google.android.gms.games.LeaderboardsClient;
import com.google.android.gms.games.Player;
import com.google.android.gms.games.PlayersClient;
import com.google.android.gms.games.event.Event;
import com.google.android.gms.games.event.EventBuffer;
import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.OnFailureListener;
import com.google.android.gms.tasks.OnSuccessListener;
import com.google.android.gms.tasks.Task;

/**
 * Our main activity for the game.
 * <p>
 * IMPORTANT: Before attempting to run this sample, please change
 * the package name to your own package name (not com.android.*) and
 * replace the IDs on res/values/ids.xml by your own IDs (you must
 * create a game in the developer console to get those IDs).
 * <p>
 * This is a very simple game where the user selects "easy mode" or
 * "hard mode" and then the "gameplay" consists of inputting the
 * desired score (0 to 9999). In easy mode, you get the score you
 * request; in hard mode, you get half.
 *
 * @author Bruno Oliveira
 */
public class MainActivity extends FragmentActivity implements
    MainMenuFragment.Listener,
    GameplayFragment.Callback,
    WinFragment.Listener {

  // Fragments
  private MainMenuFragment mMainMenuFragment;
  private GameplayFragment mGameplayFragment;
  private WinFragment mWinFragment;

  // Client used to sign in with Google APIs
  private GoogleSignInClient mGoogleSignInClient;

  // Client variables
  private AchievementsClient mAchievementsClient;
  private LeaderboardsClient mLeaderboardsClient;
  private EventsClient mEventsClient;
  private PlayersClient mPlayersClient;

  // request codes we use when invoking an external activity
  private static final int RC_UNUSED = 5001;
  private static final int RC_SIGN_IN = 9001;

  // tag for debug logging
  private static final String TAG = "TanC";

  // playing on hard mode?
  private boolean mHardMode = false;

  // achievements and scores we're pending to push to the cloud
  // (waiting for the user to sign in, for instance)
  private final AccomplishmentsOutbox mOutbox = new AccomplishmentsOutbox();

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

    setContentView(R.layout.activity_main);

    // Create the client used to sign in to Google services.
    mGoogleSignInClient = GoogleSignIn.getClient(this,
        new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_GAMES_SIGN_IN).build());

    // Create the fragments used by the UI.
    mMainMenuFragment = new MainMenuFragment();
    mGameplayFragment = new GameplayFragment();
    mWinFragment = new WinFragment();

    // Set the listeners and callbacks of fragment events.
    mMainMenuFragment.setListener(this);
    mGameplayFragment.setCallback(this);
    mWinFragment.setListener(this);

    // Add initial Main Menu fragment.
    // IMPORTANT: if this Activity supported rotation, we'd have to be
    // more careful about adding the fragment, since the fragment would
    // already be there after rotation and trying to add it again would
    // result in overlapping fragments. But since we don't support rotation,
    // we don't deal with that for code simplicity.
    getSupportFragmentManager().beginTransaction().add(R.id.fragment_container,
        mMainMenuFragment).commit();

    checkPlaceholderIds();
  }

  // Check the sample to ensure all placeholder ids are are updated with real-world values.
  // This is strictly for the purpose of the samples; you don't need this in a production
  // application.
  private void checkPlaceholderIds() {
    StringBuilder problems = new StringBuilder();

    if (getPackageName().startsWith("com.google.")) {
      problems.append("- Package name start with com.google.*\n");
    }

    for (Integer id : new Integer[]{
        R.string.app_id,
        R.string.achievement_prime,
        R.string.achievement_really_bored,
        R.string.achievement_bored,
        R.string.achievement_humble,
        R.string.achievement_arrogant,
        R.string.achievement_leet,
        R.string.leaderboard_easy,
        R.string.leaderboard_hard,
        R.string.event_start,
        R.string.event_number_chosen,}) {

      String value = getString(id);

      if (value.startsWith("YOUR_")) {
        // needs replacing
        problems.append("- Placeholders(YOUR_*) in ids.xml need updating\n");
        break;
      }
    }

    if (problems.length() > 0) {
      problems.insert(0, "The following problems were found:\n\n");

      problems.append("\nThese problems may prevent the app from working properly.");
      problems.append("\n\nSee the TODO window in Android Studio for more information");
      (new AlertDialog.Builder(this)).setMessage(problems.toString())
          .setNeutralButton(android.R.string.ok, null).create().show();
    }
  }

  private void loadAndPrintEvents() {

    final MainActivity mainActivity = this;

    mEventsClient.load(true)
        .addOnSuccessListener(new OnSuccessListener<AnnotatedData<EventBuffer>>() {
          @Override
          public void onSuccess(AnnotatedData<EventBuffer> eventBufferAnnotatedData) {
            EventBuffer eventBuffer = eventBufferAnnotatedData.get();

            int count = 0;
            if (eventBuffer != null) {
              count = eventBuffer.getCount();
            }

            Log.i(TAG, "number of events: " + count);

            for (int i = 0; i < count; i++) {
              Event event = eventBuffer.get(i);
              Log.i(TAG, "event: "
                  + event.getName()
                  + " -> "
                  + event.getValue());
            }
          }
        })
        .addOnFailureListener(new OnFailureListener() {
          @Override
          public void onFailure(@NonNull Exception e) {
            handleException(e, getString(R.string.achievements_exception));
          }
        });
  }

  // Switch UI to the given fragment
  private void switchToFragment(Fragment newFrag) {
    getSupportFragmentManager().beginTransaction().replace(R.id.fragment_container, newFrag)
        .commit();
  }

  private boolean isSignedIn() {
    return GoogleSignIn.getLastSignedInAccount(this) != null;
  }

  private void signInSilently() {
    Log.d(TAG, "signInSilently()");

    mGoogleSignInClient.silentSignIn().addOnCompleteListener(this,
        new OnCompleteListener<GoogleSignInAccount>() {
          @Override
          public void onComplete(@NonNull Task<GoogleSignInAccount> task) {
            if (task.isSuccessful()) {
              Log.d(TAG, "signInSilently(): success");
              onConnected(task.getResult());
            } else {
              Log.d(TAG, "signInSilently(): failure", task.getException());
              onDisconnected();
            }
          }
        });
  }

  private void startSignInIntent() {
    startActivityForResult(mGoogleSignInClient.getSignInIntent(), RC_SIGN_IN);
  }

  @Override
  protected void onResume() {
    super.onResume();
    Log.d(TAG, "onResume()");

    // Since the state of the signed in user can change when the activity is not active
    // it is recommended to try and sign in silently from when the app resumes.
    signInSilently();
  }

  private void signOut() {
    Log.d(TAG, "signOut()");

    if (!isSignedIn()) {
      Log.w(TAG, "signOut() called, but was not signed in!");
      return;
    }

    mGoogleSignInClient.signOut().addOnCompleteListener(this,
        new OnCompleteListener<Void>() {
          @Override
          public void onComplete(@NonNull Task<Void> task) {
            boolean successful = task.isSuccessful();
            Log.d(TAG, "signOut(): " + (successful ? "success" : "failed"));

            onDisconnected();
          }
        });
  }

  @Override
  public void onStartGameRequested(boolean hardMode) {
    startGame(hardMode);
  }

  @Override
  public void onShowAchievementsRequested() {
    mAchievementsClient.getAchievementsIntent()
        .addOnSuccessListener(new OnSuccessListener<Intent>() {
          @Override
          public void onSuccess(Intent intent) {
            startActivityForResult(intent, RC_UNUSED);
          }
        })
        .addOnFailureListener(new OnFailureListener() {
          @Override
          public void onFailure(@NonNull Exception e) {
            handleException(e, getString(R.string.achievements_exception));
          }
        });
  }

  @Override
  public void onShowLeaderboardsRequested() {
    mLeaderboardsClient.getAllLeaderboardsIntent()
        .addOnSuccessListener(new OnSuccessListener<Intent>() {
          @Override
          public void onSuccess(Intent intent) {
            startActivityForResult(intent, RC_UNUSED);
          }
        })
        .addOnFailureListener(new OnFailureListener() {
          @Override
          public void onFailure(@NonNull Exception e) {
            handleException(e, getString(R.string.leaderboards_exception));
          }
        });
  }

  private void handleException(Exception e, String details) {
    int status = 0;

    if (e instanceof ApiException) {
      ApiException apiException = (ApiException) e;
      status = apiException.getStatusCode();
    }

    String message = getString(R.string.status_exception_error, details, status, e);

    new AlertDialog.Builder(MainActivity.this)
        .setMessage(message)
        .setNeutralButton(android.R.string.ok, null)
        .show();
  }

  /**
   * Start gameplay. This means updating some status variables and switching
   * to the "gameplay" screen (the screen where the user types the score they want).
   *
   * @param hardMode whether to start gameplay in "hard mode".
   */
  private void startGame(boolean hardMode) {
    mHardMode = hardMode;
    switchToFragment(mGameplayFragment);
    mEventsClient.increment(getString(R.string.event_start), 1);
  }

  @Override
  public void onEnteredScore(int requestedScore) {
    // Compute final score (in easy mode, it's the requested score; in hard mode, it's half)
    int finalScore = mHardMode ? requestedScore / 2 : requestedScore;

    mWinFragment.setScore(finalScore);
    mWinFragment.setExplanation(mHardMode ? getString(R.string.hard_mode_explanation) :
        getString(R.string.easy_mode_explanation));

    // check for achievements
    checkForAchievements(requestedScore, finalScore);

    // update leaderboards
    updateLeaderboards(finalScore);

    // push those accomplishments to the cloud, if signed in
    pushAccomplishments();

    // switch to the exciting "you won" screen
    switchToFragment(mWinFragment);

    mEventsClient.increment(getString(R.string.event_number_chosen), 1);
  }

  // Checks if n is prime. We don't consider 0 and 1 to be prime.
  // This is not an implementation we are mathematically proud of, but it gets the job done.
  private boolean isPrime(int n) {
    int i;
    if (n == 0 || n == 1) {
      return false;
    }
    for (i = 2; i <= n / 2; i++) {
      if (n % i == 0) {
        return false;
      }
    }
    return true;
  }

  /**
   * Check for achievements and unlock the appropriate ones.
   *
   * @param requestedScore the score the user requested.
   * @param finalScore     the score the user got.
   */
  private void checkForAchievements(int requestedScore, int finalScore) {
    // Check if each condition is met; if so, unlock the corresponding
    // achievement.
    if (isPrime(finalScore)) {
      mOutbox.mPrimeAchievement = true;
      achievementToast(getString(R.string.achievement_prime_toast_text));
    }
    if (requestedScore == 9999) {
      mOutbox.mArrogantAchievement = true;
      achievementToast(getString(R.string.achievement_arrogant_toast_text));
    }
    if (requestedScore == 0) {
      mOutbox.mHumbleAchievement = true;
      achievementToast(getString(R.string.achievement_humble_toast_text));
    }
    if (finalScore == 1337) {
      mOutbox.mLeetAchievement = true;
      achievementToast(getString(R.string.achievement_leet_toast_text));
    }
    mOutbox.mBoredSteps++;
  }

  private void achievementToast(String achievement) {
    // Only show toast if not signed in. If signed in, the standard Google Play
    // toasts will appear, so we don't need to show our own.
    if (!isSignedIn()) {
      Toast.makeText(this, getString(R.string.achievement) + ": " + achievement,
          Toast.LENGTH_LONG).show();
    }
  }

  private void pushAccomplishments() {
    if (!isSignedIn()) {
      // can't push to the cloud, try again later
      return;
    }
    if (mOutbox.mPrimeAchievement) {
      mAchievementsClient.unlock(getString(R.string.achievement_prime));
      mOutbox.mPrimeAchievement = false;
    }
    if (mOutbox.mArrogantAchievement) {
      mAchievementsClient.unlock(getString(R.string.achievement_arrogant));
      mOutbox.mArrogantAchievement = false;
    }
    if (mOutbox.mHumbleAchievement) {
      mAchievementsClient.unlock(getString(R.string.achievement_humble));
      mOutbox.mHumbleAchievement = false;
    }
    if (mOutbox.mLeetAchievement) {
      mAchievementsClient.unlock(getString(R.string.achievement_leet));
      mOutbox.mLeetAchievement = false;
    }
    if (mOutbox.mBoredSteps > 0) {
      mAchievementsClient.increment(getString(R.string.achievement_really_bored),
          mOutbox.mBoredSteps);
      mAchievementsClient.increment(getString(R.string.achievement_bored),
          mOutbox.mBoredSteps);
      mOutbox.mBoredSteps = 0;
    }
    if (mOutbox.mEasyModeScore >= 0) {
      mLeaderboardsClient.submitScore(getString(R.string.leaderboard_easy),
          mOutbox.mEasyModeScore);
      mOutbox.mEasyModeScore = -1;
    }
    if (mOutbox.mHardModeScore >= 0) {
      mLeaderboardsClient.submitScore(getString(R.string.leaderboard_hard),
          mOutbox.mHardModeScore);
      mOutbox.mHardModeScore = -1;
    }
  }

  /**
   * Update leaderboards with the user's score.
   *
   * @param finalScore The score the user got.
   */
  private void updateLeaderboards(int finalScore) {
    if (mHardMode && mOutbox.mHardModeScore < finalScore) {
      mOutbox.mHardModeScore = finalScore;
    } else if (!mHardMode && mOutbox.mEasyModeScore < finalScore) {
      mOutbox.mEasyModeScore = finalScore;
    }
  }

  @Override
  public void onWinScreenDismissed() {
    switchToFragment(mMainMenuFragment);
  }

  @Override
  protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
    super.onActivityResult(requestCode, resultCode, intent);
    if (requestCode == RC_SIGN_IN) {
      Task<GoogleSignInAccount> task =
          GoogleSignIn.getSignedInAccountFromIntent(intent);

      try {
        GoogleSignInAccount account = task.getResult(ApiException.class);
        onConnected(account);
      } catch (ApiException apiException) {
        String message = apiException.getMessage();
        if (message == null || message.isEmpty()) {
          message = getString(R.string.signin_other_error);
        }

        onDisconnected();

        new AlertDialog.Builder(this)
            .setMessage(message)
            .setNeutralButton(android.R.string.ok, null)
            .show();
      }
    }
  }

  private void onConnected(GoogleSignInAccount googleSignInAccount) {
    Log.d(TAG, "onConnected(): connected to Google APIs");

    mAchievementsClient = Games.getAchievementsClient(this, googleSignInAccount);
    mLeaderboardsClient = Games.getLeaderboardsClient(this, googleSignInAccount);
    mEventsClient = Games.getEventsClient(this, googleSignInAccount);
    mPlayersClient = Games.getPlayersClient(this, googleSignInAccount);

    // Show sign-out button on main menu
    mMainMenuFragment.setShowSignInButton(false);

    // Show "you are signed in" message on win screen, with no sign in button.
    mWinFragment.setShowSignInButton(false);

    // Set the greeting appropriately on main menu
    mPlayersClient.getCurrentPlayer()
        .addOnCompleteListener(new OnCompleteListener<Player>() {
          @Override
          public void onComplete(@NonNull Task<Player> task) {
            String displayName;
            if (task.isSuccessful()) {
              displayName = task.getResult().getDisplayName();
            } else {
              Exception e = task.getException();
              handleException(e, getString(R.string.players_exception));
              displayName = "???";
            }
            mMainMenuFragment.setGreeting("Hello, " + displayName);
          }
        });


    // if we have accomplishments to push, push them
    if (!mOutbox.isEmpty()) {
      pushAccomplishments();
      Toast.makeText(this, getString(R.string.your_progress_will_be_uploaded),
          Toast.LENGTH_LONG).show();
    }

    loadAndPrintEvents();
  }

  private void onDisconnected() {
    Log.d(TAG, "onDisconnected()");

    mAchievementsClient = null;
    mLeaderboardsClient = null;
    mPlayersClient = null;

    // Show sign-in button on main menu
    mMainMenuFragment.setShowSignInButton(true);

    // Show sign-in button on win screen
    mWinFragment.setShowSignInButton(true);

    mMainMenuFragment.setGreeting(getString(R.string.signed_out_greeting));
  }

  @Override
  public void onSignInButtonClicked() {
    startSignInIntent();
  }

  @Override
  public void onSignOutButtonClicked() {
    signOut();
  }

  private class AccomplishmentsOutbox {
    boolean mPrimeAchievement = false;
    boolean mHumbleAchievement = false;
    boolean mLeetAchievement = false;
    boolean mArrogantAchievement = false;
    int mBoredSteps = 0;
    int mEasyModeScore = -1;
    int mHardModeScore = -1;

    boolean isEmpty() {
      return !mPrimeAchievement && !mHumbleAchievement && !mLeetAchievement &&
          !mArrogantAchievement && mBoredSteps == 0 && mEasyModeScore < 0 &&
          mHardModeScore < 0;
    }

  }
}