/*
 * Copyright 2019 ACINQ SAS
 *
 * 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 fr.acinq.eclair.wallet.activities;

import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import androidx.databinding.DataBindingUtil;
import android.os.Bundle;
import android.os.Handler;
import android.preference.PreferenceManager;
import androidx.fragment.app.Fragment;
import androidx.viewpager.widget.ViewPager;
import android.view.HapticFeedbackConstants;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.view.inputmethod.InputMethodManager;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import fr.acinq.bitcoin.MnemonicCode;
import fr.acinq.eclair.wallet.R;
import fr.acinq.eclair.wallet.adapters.SimplePagerAdapter;
import fr.acinq.eclair.wallet.databinding.ActivityCreateSeedBinding;
import fr.acinq.eclair.wallet.fragments.initwallet.WalletCheckWordsFragment;
import fr.acinq.eclair.wallet.fragments.initwallet.WalletCreateSeedFragment;
import fr.acinq.eclair.wallet.fragments.initwallet.WalletEncryptFragment;
import fr.acinq.eclair.wallet.fragments.initwallet.WalletPassphraseConfirmFragment;
import fr.acinq.eclair.wallet.fragments.initwallet.WalletPassphraseFragment;
import fr.acinq.eclair.wallet.utils.Constants;
import fr.acinq.eclair.wallet.utils.EncryptedSeed;
import fr.acinq.eclair.wallet.utils.WalletUtils;
import scala.collection.JavaConverters;

public class CreateSeedActivity extends EclairActivity implements EclairActivity.EncryptSeedCallback {

  final List<Integer> recoveryPositions = Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11);
  private final Logger log = LoggerFactory.getLogger(CreateSeedActivity.class);
  private List<String> mMnemonics = null;
  private String mPassphrase = "";
  private boolean mMnemonicsVerified = false;
  private WalletCreateSeedFragment mWalletCreateSeedFragment;
  private WalletCheckWordsFragment mWalletCheckWordsFragment;
  private WalletPassphraseFragment mWalletPassphraseFragment;
  private WalletPassphraseConfirmFragment mWalletPassphraseConfirmFragment;
  private WalletEncryptFragment mWalletEncryptFragment;
  private ActivityCreateSeedBinding mBinding;
  private Animation mErrorAnimation;
  final Handler verifHandler = new Handler();

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    mBinding = DataBindingUtil.setContentView(this, R.layout.activity_create_seed);

    try {
      mMnemonics = JavaConverters.seqAsJavaListConverter(MnemonicCode.toMnemonics(
        fr.acinq.eclair.package$.MODULE$.randomBytes(16),
        MnemonicCode.englishWordlist())).asJava();

      final Bundle args = new Bundle();
      args.putStringArrayList(WalletCreateSeedFragment.BUNDLE_ARG_MNEMONICS, new ArrayList<>(mMnemonics));
      final List<Fragment> fragments = new ArrayList<>();
      mWalletCreateSeedFragment = new WalletCreateSeedFragment();
      mWalletCreateSeedFragment.setArguments(args);
      mWalletCheckWordsFragment = new WalletCheckWordsFragment();
      mWalletPassphraseFragment = new WalletPassphraseFragment();
      mWalletPassphraseConfirmFragment = new WalletPassphraseConfirmFragment();
      mWalletEncryptFragment = new WalletEncryptFragment();
      fragments.add(mWalletCreateSeedFragment);
      fragments.add(mWalletCheckWordsFragment);
      fragments.add(mWalletPassphraseFragment);
      fragments.add(mWalletPassphraseConfirmFragment);
      fragments.add(mWalletEncryptFragment);
      final SimplePagerAdapter pagerAdapter = new SimplePagerAdapter(getSupportFragmentManager(), fragments);
      mBinding.viewPager.setAdapter(pagerAdapter);
      mBinding.viewPager.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
        @Override
        public void onPageSelected(int position) {
          super.onPageSelected(position);
          mBinding.setCreationStep(position);
        }
      });
      goToInit(null);

    } catch (Exception e) {
      mMnemonics = null;
    }
  }

  @Override
  protected void onStart() {
    super.onStart();
    mErrorAnimation = AnimationUtils.loadAnimation(this, R.anim.shake);
  }

  protected void onResume() {
    super.onResume();
    if (mMnemonics == null) {
      log.error("mnemonics is null");
      mBinding.error.setText(R.string.createwallet_error_seed_generation);
      mBinding.setCreationStep(Constants.SEED_SPAWN_ERROR);
      new Handler().postDelayed(() -> goToStartup(), 1500);
    }
  }

  @Override
  public void onBackPressed() {
    switch (mBinding.viewPager.getCurrentItem()) {
      case 0: // init -> finish + startup page
        goToStartup();
        break;
      case 1: // check -> init
        goToInit(null);
        break;
      case 2: // passphrase -> init
        goToInit(null);
        break;
      case 3: // confirm passphrase -> passphrase
        goToPassphraseStep(null);
        break;
      case 4: // encrypting -> passphrase
        goToPassphraseStep(null);
        break;
      default:
        break;
    }
  }

  public void goToInit(final View view) {
    mBinding.viewPager.setCurrentItem(0);
  }

  public void goToVerificationStep(final View view) {
    verifHandler.removeCallbacksAndMessages(null);
    if (mWalletCheckWordsFragment != null && mWalletCheckWordsFragment.mBinding != null) {
      mWalletCheckWordsFragment.mBinding.verificationError.setVisibility(View.GONE);
      mWalletCheckWordsFragment.mBinding.inputGrid.clearAnimation();
      if (mMnemonicsVerified) {
        goToPassphraseStep(null);
      } else {
        setUpCheckWords();
        mBinding.viewPager.setCurrentItem(1);
      }
    } else {
      goToInit(view);
    }
  }

  private void setUpCheckWords() {
    try {
      Collections.shuffle(recoveryPositions);
      final List<Integer> pos = getFirst3Positions();
      mWalletCheckWordsFragment.mBinding.checkQuestion.setText(getString(R.string.createwallet_check_instructions, pos.get(0) + 1, pos.get(1) + 1, pos.get(2) + 1));
      mWalletCheckWordsFragment.mBinding.checkInput1Hint.setText(getString(R.string.createwallet_check_input_hint, pos.get(0) + 1));
      mWalletCheckWordsFragment.mBinding.checkInput1.setText("");
      mWalletCheckWordsFragment.mBinding.checkInput2Hint.setText(getString(R.string.createwallet_check_input_hint, pos.get(1) + 1));
      mWalletCheckWordsFragment.mBinding.checkInput2.setText("");
      mWalletCheckWordsFragment.mBinding.checkInput3Hint.setText(getString(R.string.createwallet_check_input_hint, pos.get(2) + 1));
      mWalletCheckWordsFragment.mBinding.checkInput3.setText("");
    } catch (Exception e) {
      goToInit(null);
    }
  }

  /**
   * Check that the user has correctly backed up the mMnemonics.
   */
  public void verifyUserBackup(View view) {
    verifHandler.removeCallbacksAndMessages(null);
    if (mWalletCheckWordsFragment != null && mWalletCheckWordsFragment.mBinding != null) {
      mWalletCheckWordsFragment.mBinding.inputGrid.clearAnimation();
      mWalletCheckWordsFragment.mBinding.verificationError.setVisibility(View.GONE);
    }
    view.clearFocus();
    final InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
    if (imm != null) {
      imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
    }
    try {
      final String userWord1 = mWalletCheckWordsFragment.mBinding.checkInput1.getText().toString().trim();
      final String userWord2 = mWalletCheckWordsFragment.mBinding.checkInput2.getText().toString().trim();
      final String userWord3 = mWalletCheckWordsFragment.mBinding.checkInput3.getText().toString().trim();
      final List<Integer> pos = getFirst3Positions();
      if (checkWordRecoveryPhrase(pos.get(0), userWord1)
        && checkWordRecoveryPhrase(pos.get(1), userWord2)
        && checkWordRecoveryPhrase(pos.get(2), userWord3)) {
        mMnemonicsVerified = true;
        goToPassphraseStep(view);
      } else {
        throw new RuntimeException();
      }
    } catch (Exception e) {
      if (mWalletCheckWordsFragment != null && mWalletCheckWordsFragment.mBinding != null) {
        mWalletCheckWordsFragment.mBinding.inputGrid.startAnimation(mErrorAnimation);
        mWalletCheckWordsFragment.mBinding.verificationError.setVisibility(View.VISIBLE);
      }
      verifHandler.postDelayed(() -> {
        setUpCheckWords();
        if (mWalletCheckWordsFragment != null && mWalletCheckWordsFragment.mBinding != null) {
          mWalletCheckWordsFragment.mBinding.verificationError.setVisibility(View.GONE);
        }
      }, 2000);
      view.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY, HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING);
      mMnemonicsVerified = false;
    }
  }

  public void goToPassphraseStep(final View view) {
    mBinding.viewPager.setCurrentItem(2);
  }

  public void goToPassphraseConfirmStep(final View view) {
    if (mWalletPassphraseFragment != null && mWalletPassphraseFragment.mBinding != null) {
      mWalletPassphraseFragment.mBinding.passphraseError.setVisibility(View.GONE);
    }
    if (mWalletPassphraseConfirmFragment != null && mWalletPassphraseConfirmFragment.mBinding != null) {
      mWalletPassphraseConfirmFragment.mBinding.passphraseConfirmInput.setText("");
    }
    final InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
    if (imm != null && view != null && view.getWindowToken() != null) {
      imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
    }
    if (mWalletPassphraseFragment != null && mWalletPassphraseFragment.mBinding != null) {
      if ("".equals(mWalletPassphraseFragment.mBinding.passphraseInput.getText().toString())) {
        goToEncryptionStep(null);
      } else {
        mBinding.viewPager.setCurrentItem(3);
      }
    }
  }

  public void goToEncryptionStep(final View view) {
    final InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
    if (imm != null && view != null && view.getWindowToken() != null) {
      imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
    }
    try {
      final String passphrase = mWalletPassphraseFragment.mBinding.passphraseInput.getText().toString();
      final String passphraseConfirm = mWalletPassphraseConfirmFragment.mBinding.passphraseConfirmInput.getText().toString();
      if (passphrase.equals(passphraseConfirm)) {
        mBinding.viewPager.setCurrentItem(4);
        mPassphrase = passphrase;
      } else {
        goToPassphraseStep(view);
        if (mWalletPassphraseFragment != null && mWalletPassphraseFragment.mBinding != null) {
          mWalletPassphraseFragment.mBinding.passphraseError.setVisibility(View.VISIBLE);
          mWalletPassphraseFragment.mBinding.passphraseInput.startAnimation(mErrorAnimation);
        }
      }
    } catch (Exception e) {
      goToInit(view);
    }
  }

  public void encryptSeed(final View view) {
    if (mWalletEncryptFragment != null && mWalletEncryptFragment.mBinding != null) {
      mWalletEncryptFragment.mBinding.encryptionError.setVisibility(View.GONE);
    }
    mBinding.setCreationStep(Constants.SEED_SPAWN_ENCRYPTION);
    new Thread() {
      @Override
      public void run() {
        try {
          if (mMnemonics == null) {
            // reference was lost -- cannot continue
            throw new RuntimeException(getString(R.string.createwallet_invalid_seed));
          }
          final String passphrase = mPassphrase;
          final File datadir = new File(getFilesDir(), Constants.ECLAIR_DATADIR);
          final byte[] seed = WalletUtils.encodeMnemonics(EncryptedSeed.SEED_FILE_VERSION_2, mMnemonics, passphrase);
          runOnUiThread(() -> encryptWallet(CreateSeedActivity.this, false, datadir, seed, EncryptedSeed.SEED_FILE_VERSION_2));
        } catch (Exception e) {
          runOnUiThread(() -> {
            mBinding.error.setText(getString(R.string.createwallet_error_write_seed, e.getLocalizedMessage()));
            mBinding.setCreationStep(Constants.SEED_SPAWN_ERROR);
            new Handler().postDelayed(() -> goToStartup(), 1500);
          });
        }
      }
    }.start();
  }

  public void cancel(View view) {
    goToStartup();
  }

  private List<Integer> getFirst3Positions() {
    final List<Integer> first3Positions = recoveryPositions.subList(0, 3);
    Collections.sort(first3Positions);
    return first3Positions;
  }

  /**
   * Checks if the word belongs to the recovery phrase and is at the right position.
   *
   * @param position position of the word in the recovery phrase
   * @param word     word entered by the user
   * @return false if the check fails
   */
  private boolean checkWordRecoveryPhrase(final int position, final String word) {
    return mMnemonics.get(position).equalsIgnoreCase(word);
  }

  @Override
  public void onEncryptSeedFailure(final String message) {
    if (mWalletEncryptFragment != null && mWalletEncryptFragment.mBinding != null) {
      mWalletEncryptFragment.mBinding.encryptionError.setVisibility(View.VISIBLE);
      mWalletEncryptFragment.mBinding.encryptionError.setText(message);
    }
    mBinding.setCreationStep(4);
    goToEncryptionStep(null);
  }

  private void goToStartup() {
    Intent startup = new Intent(this, StartupActivity.class);
    startup.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
    startup.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    startActivity(startup);
    finish();
  }

  @Override
  public void onEncryptSeedSuccess() {
    mBinding.setCreationStep(Constants.SEED_SPAWN_COMPLETE);
    final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getBaseContext());
    prefs.edit().putInt(Constants.SETTING_WALLET_ORIGIN, Constants.WALLET_ORIGIN_FROM_SCRATCH).apply();
    new Handler().postDelayed(this::goToStartup, 1700);
  }
}
