/*
This file is part of Keepass2Android, Copyright 2013 Philipp Crocoll. 
  Keepass2Android is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.
  Keepass2Android 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.
  You should have received a copy of the GNU General Public License
  along with Keepass2Android.  If not, see .
  */
using System;
using System.Linq;
using Android.App;
using Android.Content;
using Android.OS;
using Android.Views;
using Android.Widget;
using Android.Content.PM;
using KeePassLib.Keys;
using Android.Preferences;
using Android.Provider;
using Android.Runtime;
using Android.Views.InputMethods;
using Google.Android.Material.AppBar;
using Google.Android.Material.Dialog;
using keepass2android;
using KeePassLib;
using KeePassLib.Serialization;
using Toolbar = AndroidX.AppCompat.Widget.Toolbar;
using AndroidX.Core.Content;
using keepass2android.Utils;
namespace keepass2android
{
	[Activity(Label = "@string/app_name", 
		ConfigurationChanges = ConfigChanges.Orientation,
		WindowSoftInputMode = SoftInput.AdjustResize,
		MainLauncher = false,
        Theme = "@style/Kp2aTheme_BlueNoActionBar")]
	public class QuickUnlock : LifecycleAwareActivity, IBiometricAuthCallback
	{
		private IOConnectionInfo _ioc;
		private QuickUnlockBroadcastReceiver _intentReceiver;
		private ActivityDesign _design;
        private IBiometricIdentifier _biometryIdentifier;
		private int _quickUnlockLength;
        private int numFailedAttempts = 0;
        private int maxNumFailedAttempts = int.MaxValue;
        public QuickUnlock()
		{
			_design = new ActivityDesign(this);
		}
		protected override void OnCreate(Bundle bundle)
		{
			_design.ApplyTheme();
			base.OnCreate(bundle);
			
			//use FlagSecure to make sure the last (revealed) character of the password is not visible in recent apps
		    Util.MakeSecureDisplay(this);
			_ioc = App.Kp2a.GetDbForQuickUnlock()?.Ioc;
            if (_ioc == null)
			{
				Finish();
				return;
			}
			SetContentView(Resource.Layout.QuickUnlock);
			var collapsingToolbar = FindViewById(Resource.Id.collapsing_toolbar);
			collapsingToolbar.SetTitle(GetString(Resource.String.QuickUnlock_prefs));
            SetSupportActionBar(FindViewById(Resource.Id.toolbar));
            
            if (App.Kp2a.GetDbForQuickUnlock().KpDatabase.Name != "")
			{
				FindViewById(Resource.Id.filename_label).Visibility = ViewStates.Visible;
				((TextView) FindViewById(Resource.Id.filename_label)).Text = App.Kp2a.GetDbForQuickUnlock().KpDatabase.Name;
			}
			else
			{
				if (
					PreferenceManager.GetDefaultSharedPreferences(this)
					                 .GetBoolean(GetString(Resource.String.RememberRecentFiles_key),
					                             Resources.GetBoolean(Resource.Boolean.RememberRecentFiles_default)))
				{
					((TextView) FindViewById(Resource.Id.filename_label)).Text = App.Kp2a.GetFileStorage(_ioc).GetDisplayName(_ioc);
				}
				else
				{
					((TextView) FindViewById(Resource.Id.filename_label)).Text = "*****";
				}
			}
			TextView txtLabel = (TextView) FindViewById(Resource.Id.QuickUnlock_label);
			_quickUnlockLength = App.Kp2a.QuickUnlockKeyLength;
			bool useUnlockKeyFromDatabase = 
                QuickUnlockFromDatabaseEnabled
				&& FindQuickUnlockEntry() != null;
			
            if (useUnlockKeyFromDatabase || PreferenceManager.GetDefaultSharedPreferences(this)
		        .GetBoolean(GetString(Resource.String.QuickUnlockHideLength_key), false))
		    {
		        txtLabel.Text = GetString(Resource.String.QuickUnlock_label_secure);
            }
		    else
		    {
		        txtLabel.Text = GetString(Resource.String.QuickUnlock_label, new Java.Lang.Object[] { _quickUnlockLength });
            }
			
			EditText pwd = (EditText) FindViewById(Resource.Id.QuickUnlock_password);
			pwd.SetEms(_quickUnlockLength);
			Util.MoveBottomBarButtons(Resource.Id.QuickUnlock_buttonLock, Resource.Id.QuickUnlock_button, Resource.Id.bottom_bar, this);
			Button btnUnlock = (Button) FindViewById(Resource.Id.QuickUnlock_button);
			btnUnlock.Click += (object sender, EventArgs e) =>
				{
					OnUnlock(pwd);
				};
		    
			Button btnLock = (Button) FindViewById(Resource.Id.QuickUnlock_buttonLock);
			btnLock.Text = btnLock.Text.Replace("ß", "ss");
			btnLock.Click += (object sender, EventArgs e) =>
				{
					App.Kp2a.Lock(false);
					Finish();
				};
			pwd.EditorAction += (sender, args) =>
				{
					if ((args.ActionId == ImeAction.Done) || ((args.ActionId == ImeAction.ImeNull) && (args.Event.Action == KeyEventActions.Down)))
						OnUnlock(pwd);
				};
			_intentReceiver = new QuickUnlockBroadcastReceiver(this);
			IntentFilter filter = new IntentFilter();
			filter.AddAction(Intents.DatabaseLocked);
            ContextCompat.RegisterReceiver(this, _intentReceiver, filter, (int)ReceiverFlags.Exported);
            Util.SetNoPersonalizedLearning(FindViewById(Resource.Id.QuickUnlock_password));
            if (bundle != null)
                numFailedAttempts = bundle.GetInt(NumFailedAttemptsKey, 0);
            FindViewById(Resource.Id.QuickUnlock_buttonEnableLock).Click += (object sender, EventArgs e) =>
            {
				Intent intent = new Intent(Settings.ActionSecuritySettings);
                StartActivity(intent);
            };
            FindViewById(Resource.Id.QuickUnlock_buttonCloseDb).Click += (object sender, EventArgs e) =>
            {
                App.Kp2a.Lock(false);
            };
            if (App.Kp2a.ScreenLockWasEnabledWhenOpeningDatabase == false)
            {
				FindViewById(Resource.Id.QuickUnlockForm).Visibility = ViewStates.Gone;
                FindViewById(Resource.Id.QuickUnlockBlocked).Visibility = ViewStates.Visible;
            }
            else
            {
                FindViewById(Resource.Id.QuickUnlockForm).Visibility = ViewStates.Visible;
                FindViewById(Resource.Id.QuickUnlockBlocked).Visibility = ViewStates.Gone;
            }
        }
        private bool QuickUnlockFromDatabaseEnabled =>
            PreferenceManager.GetDefaultSharedPreferences(this)
                .GetBoolean(GetString(Resource.String.QuickUnlockKeyFromDatabase_key), false);
        private static PwEntry FindQuickUnlockEntry()
        {
            return App.Kp2a.GetDbForQuickUnlock()?.KpDatabase?.RootGroup?.Entries.SingleOrDefault(e => e.Strings.GetSafe(PwDefs.TitleField).ReadString() == "QuickUnlock");
        }
        private const string NumFailedAttemptsKey = "FailedAttempts";
        protected override void OnSaveInstanceState(Bundle outState)
        {
            base.OnSaveInstanceState(outState);
            outState.PutInt(NumFailedAttemptsKey, numFailedAttempts);
            
        }
        protected override void OnStart()
		{
			base.OnStart();
			DonateReminder.ShowDonateReminderIfAppropriate(this);
			
		}
		
		public void OnBiometricError(string message)
		{
			Kp2aLog.Log("fingerprint error: " + message);
			var btn = FindViewById(Resource.Id.fingerprintbtn);
			btn.SetImageResource(Resource.Drawable.ic_fingerprint_error);
			btn.PostDelayed(() =>
			{
				btn.SetImageResource(Resource.Drawable.baseline_fingerprint_24);
				
			}, 1300);
			App.Kp2a.ShowMessage(this, message,  MessageSeverity.Error);
		}
        
        public void OnBiometricAttemptFailed(string message)
        {
            numFailedAttempts++;
            if (numFailedAttempts >= maxNumFailedAttempts)
            {
                FindViewById(Resource.Id.fingerprintbtn).Visibility = ViewStates.Gone;
                _biometryIdentifier.StopListening();
            }
        }
        public void OnBiometricAuthSucceeded()
		{
			Kp2aLog.Log("OnFingerprintAuthSucceeded");
			_biometryIdentifier.StopListening();
			var btn = FindViewById(Resource.Id.fingerprintbtn);
			btn.SetImageResource(Resource.Drawable.ic_fingerprint_success);
			EditText pwd = (EditText)FindViewById(Resource.Id.QuickUnlock_password);
			pwd.Text = ExpectedPasswordPart;
			
			btn.PostDelayed(() =>
            {
				UnlockAndSyncAndClose();
			}, 500);
		}
		private bool InitFingerprintUnlock()
		{
			Kp2aLog.Log("InitFingerprintUnlock");
			if (_biometryIdentifier != null)
			{
				Kp2aLog.Log("Already listening for fingerprint!");
				return true;
			}
			var btn = FindViewById(Resource.Id.fingerprintbtn);
			try
			{
				FingerprintUnlockMode um;
				Enum.TryParse(PreferenceManager.GetDefaultSharedPreferences(this).GetString(App.Kp2a.GetDbForQuickUnlock().CurrentFingerprintModePrefKey, ""), out um);
				btn.Visibility = (um != FingerprintUnlockMode.Disabled) ? ViewStates.Visible : ViewStates.Gone;
				if (um == FingerprintUnlockMode.Disabled)
				{
					_biometryIdentifier = null;
					return false;
				}
                if (um == FingerprintUnlockMode.QuickUnlock && Util.GetCloseDatabaseAfterFailedBiometricQuickUnlock(this))
                {
                    maxNumFailedAttempts = 3;
                }
                BiometricModule fpModule = new BiometricModule(this);
				Kp2aLog.Log("fpModule.IsHardwareAvailable=" + fpModule.IsHardwareAvailable);
				if (fpModule.IsHardwareAvailable) //see FingerprintSetupActivity
					_biometryIdentifier = new BiometricDecryption(fpModule, App.Kp2a.GetDbForQuickUnlock().CurrentFingerprintPrefKey, this,
						App.Kp2a.GetDbForQuickUnlock().CurrentFingerprintPrefKey);
				
				
			    if (_biometryIdentifier == null)
			    {
			        FindViewById(Resource.Id.fingerprintbtn).Visibility = ViewStates.Gone;
			        return false;
                }
                
				if (_biometryIdentifier.Init())
				{
					Kp2aLog.Log("successfully initialized fingerprint.");
					btn.SetImageResource(Resource.Drawable.baseline_fingerprint_24);
					_biometryIdentifier.StartListening(this);
					return true;
				}
				else
				{
					Kp2aLog.Log("failed to initialize fingerprint.");
					HandleFingerprintKeyInvalidated();
				}
			}
			catch (Exception e)
			{
				Kp2aLog.Log("Error initializing Fingerprint Unlock: " + e);
				btn.SetImageResource(Resource.Drawable.ic_fingerprint_error);
				btn.Tag = "Error initializing Fingerprint Unlock: " + e;
				_biometryIdentifier = null;
			}
			return false;
		}
		private void HandleFingerprintKeyInvalidated()
		{
			var btn = FindViewById(Resource.Id.fingerprintbtn);
//key invalidated permanently
			btn.SetImageResource(Resource.Drawable.ic_fingerprint_error);
		    btn.Tag = GetString(Resource.String.fingerprint_unlock_failed) + " " + GetString(Resource.String.fingerprint_reenable2);
            _biometryIdentifier = null;
		}
	    private void OnUnlock(EditText pwd)
		{
			var expectedPasswordPart = ExpectedPasswordPart;
			if (pwd.Text == expectedPasswordPart)
            {
                UnlockAndSyncAndClose();
            }
			else
			{
				Kp2aLog.Log("QuickUnlock not successful!");
				App.Kp2a.Lock(false);
				App.Kp2a.ShowMessage(this, GetString(Resource.String.QuickUnlock_fail),  MessageSeverity.Error);
                Finish();
			}
			
		}
        private void UnlockAndSyncAndClose()
        {
            App.Kp2a.UnlockDatabase();
			if (PreferenceManager.GetDefaultSharedPreferences(this)
									 .GetBoolean(GetString(Resource.String.SyncAfterQuickUnlock_key), false))
			{
				new SyncUtil(this).SynchronizeDatabase(Finish);
			}
			else
				Finish();
			
            
        }
        private string ExpectedPasswordPart
		{
			get
            {
                if (QuickUnlockFromDatabaseEnabled)
                {
                    var quickUnlockEntry = FindQuickUnlockEntry();
                    if (quickUnlockEntry != null)
                    {
                        return quickUnlockEntry.Strings.ReadSafe(PwDefs.PasswordField).ToString();
                    }
				}
                
				KcpPassword kcpPassword = (KcpPassword) App.Kp2a.GetDbForQuickUnlock().KpDatabase.MasterKey.GetUserKey(typeof (KcpPassword));
				String password = kcpPassword.Password.ReadString();
			    var passwordStringInfo = new System.Globalization.StringInfo(password);
			    int passwordLength = passwordStringInfo.LengthInTextElements;
                
                String expectedPasswordPart = passwordStringInfo.SubstringByTextElements(Math.Max(0, passwordLength - _quickUnlockLength),
                    Math.Min(passwordLength, _quickUnlockLength));
				return expectedPasswordPart;
			}
		}
		private void OnLockDatabase()
		{
			CheckIfUnloaded();
		}
		protected override void OnResume()
		{
			base.OnResume();
			_design.ReapplyTheme();
            App.Kp2a.MessagePresenter = new ChainedSnackbarPresenter(FindViewById(Resource.Id.main_content));
            CheckIfUnloaded();
            InitFingerprintUnlock();
            bool showKeyboard = true;
			EditText pwd = (EditText)FindViewById(Resource.Id.QuickUnlock_password);
			pwd.PostDelayed(() =>
            {
                pwd.RequestFocus();
				InputMethodManager keyboard = (InputMethodManager)GetSystemService(Context.InputMethodService);
				if (showKeyboard)
					keyboard.ShowSoftInput(pwd, ShowFlags.Implicit);
				else
					keyboard.HideSoftInputFromWindow(pwd.WindowToken, HideSoftInputFlags.ImplicitOnly);
			}, 50);
            var btn = FindViewById(Resource.Id.fingerprintbtn);
            btn.Click += (sender, args) =>
            {
                if ((_biometryIdentifier != null) && ((_biometryIdentifier.HasUserInterface)|| string.IsNullOrEmpty((string)btn.Tag) ))
                {
                    _biometryIdentifier.StartListening(this);
                }
                else
                {
                    MaterialAlertDialogBuilder b = new MaterialAlertDialogBuilder(this);
                    b.SetTitle(Resource.String.fingerprint_prefs);
                    b.SetMessage(btn.Tag.ToString());
                    b.SetPositiveButton(Android.Resource.String.Ok, (o, eventArgs) => ((Dialog)o).Dismiss());
                    if (_biometryIdentifier != null)
                    {
                        b.SetNegativeButton(Resource.String.disable_sensor, (senderAlert, alertArgs) =>
                        {
                            btn.SetImageResource(Resource.Drawable.ic_fingerprint_error);
                            _biometryIdentifier?.StopListening();
                            _biometryIdentifier = null;
                        });
                    }
                    else
                    {
                        b.SetNegativeButton(Resource.String.enable_sensor, (senderAlert, alertArgs) =>
                        {
                            InitFingerprintUnlock();
                        });
                    }
                    b.Show();
                }
                
            };
            
            
        }
		
		protected override void OnPause()
		{
            App.Kp2a.MessagePresenter = new NonePresenter();
            if (_biometryIdentifier != null)
			{
				Kp2aLog.Log("FP: Stop listening");
				_biometryIdentifier.StopListening();
            }
			base.OnPause();
		}
		protected override void OnDestroy()
		{
			base.OnDestroy();
			try
			{
				UnregisterReceiver(_intentReceiver);
			}
			catch (Exception e)
			{
				Kp2aLog.LogUnexpectedError(e);
			}
			
		}
		private void CheckIfUnloaded()
		{
			if (App.Kp2a.OpenDatabases.Any() == false)
			{
				Finish();
			}
		}
		public override void OnBackPressed()
		{
			SetResult(KeePass.ExitClose);
			base.OnBackPressed();
		}
		private class QuickUnlockBroadcastReceiver : BroadcastReceiver
		{
			readonly QuickUnlock _activity;
			public QuickUnlockBroadcastReceiver(QuickUnlock activity)
			{
				_activity = activity;
			}
			public override void OnReceive(Context context, Intent intent)
			{
				switch (intent.Action)
				{
					case Intents.DatabaseLocked:
						_activity.OnLockDatabase();
						break;
				}
			}
		}
	}
}