/*
  This file was modified my Philipp Crocoll, 2013. Based on: 
  
  OtpKeyProv Plugin
  Copyright (C) 2011-2012 Dominik Reichl <dominik.reichl@t-online.de>

  This program 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 2 of the License, or
  (at your option) any later version.

  This program 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 this program; if not, write to the Free Software
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*/

using System;
using System.Collections.Generic;
using KeePassLib.Keys;
using KeePassLib.Serialization;
using KeePassLib.Utility;
using keepass2android;
using keepass2android.Io;

namespace OtpKeyProv
{
	public sealed class OathHotpKeyProv
		/*removed base class KeyProvider because "synchronous" interface is not suitable on Android*/
	{
		public const string AuxFileExt = ".otp.xml";
		private const string ProvType = "OATH HOTP / RFC 4226";
		private const string ProvVersion = "2.0"; // File version, not OtpKeyProv version

		public static string Name
		{
			get { return "One-Time Passwords (OATH HOTP)"; }
		}


		public const string ShortProductName = "OtpKeyProv";
		public const string ProductName = "OtpKeyProv KeePass Plugin";


		

		/*
		private static byte[] Open(KeyProviderQueryContext ctx, OtpInfo otpInfo)
		{
			if(otpInfo.Type != ProvType)
			{
				MessageService.ShowWarning("Unknown OTP generator type!");
				return null;
			}

			OtpKeyPromptForm dlg = new OtpKeyPromptForm();
			dlg.InitEx(otpInfo, ctx);
			if(UIUtil.ShowDialogAndDestroy(dlg) != DialogResult.OK)
				return null;

			if(!CreateAuxFile(otpInfo, ctx)) return null;
			return otpInfo.Secret;
		}
		 * */

		/// <summary>
		/// Sets the "Secret" field in otpInfo based on the list of entered OTPs (lOtps) or the entered secret itself which is in format fmt
		/// </summary>
		/// based on the code in OtpKeyPromptForm.cs
		public void SetSecret(OtpInfo otpInfo, List<string> lOtps, string secret, OtpDataFmt? fmt)
		{
			byte[] pbSecret = EncodingUtil.ParseKey(secret,
			                                        (fmt.HasValue ? fmt.Value : OtpDataFmt.Hex));
			if (pbSecret != null)
			{
				otpInfo.Secret = pbSecret;
				return;
			}

			if (!string.IsNullOrEmpty(otpInfo.EncryptedSecret)) // < v2.0
			{
				byte[] pbKey32 = OtpUtil.KeyFromOtps(lOtps.ToArray(), 0,
				                                     lOtps.Count, Convert.FromBase64String(
					                                     otpInfo.TransformationKey), otpInfo.TransformationRounds);
				if (pbKey32 == null) throw new InvalidOperationException();

				pbSecret = OtpUtil.DecryptData(otpInfo.EncryptedSecret,
				                               pbKey32, Convert.FromBase64String(otpInfo.EncryptionIV));
				if (pbSecret == null) throw new InvalidOperationException();

				otpInfo.Secret = pbSecret;
				otpInfo.Counter += (ulong) otpInfo.OtpsRequired;
			}
			else // >= v2.0, supporting look-ahead
			{
				bool bSuccess = false;
				for (int i = 0; i < otpInfo.EncryptedSecrets.Count; ++i)
				{
					OtpEncryptedData d = otpInfo.EncryptedSecrets[i];
					pbSecret = OtpUtil.DecryptSecret(d, lOtps.ToArray(), 0,
					                                 lOtps.Count);
					if (pbSecret != null)
					{
						otpInfo.Secret = pbSecret;
						otpInfo.Counter += ((ulong) otpInfo.OtpsRequired +
						                    (ulong) i);
						bSuccess = true;
						break;
					}
				}
				if (!bSuccess) throw new InvalidOperationException();
			}
		}


		public static bool CreateAuxFile(OtpInfo otpInfo,
			KeyProviderQueryContext ctx, IOConnectionInfo auxFileIoc)
		{
			otpInfo.Type = ProvType;
			otpInfo.Version = ProvVersion;
			otpInfo.Generator = ProductName;

			otpInfo.EncryptSecret();

			if(!OtpInfo.Save(auxFileIoc, otpInfo))
			{
				MessageService.ShowWarning("Failed to save auxiliary OTP info file:",
					auxFileIoc.GetDisplayName());
				return false;
			}

			return true;
		}

		public static void CreateOtpSecret(List<string> lOtps, OtpInfo otpInfo)
		{
			
			byte[] pbSecret;
			if (!string.IsNullOrEmpty(otpInfo.EncryptedSecret)) // < v2.0
			{
				byte[] pbKey32 = OtpUtil.KeyFromOtps(lOtps.ToArray(), 0,
				                                     lOtps.Count, Convert.FromBase64String(
					                                     otpInfo.TransformationKey), otpInfo.TransformationRounds);
				if (pbKey32 == null) throw new InvalidOperationException();

				pbSecret = OtpUtil.DecryptData(otpInfo.EncryptedSecret,
				                               pbKey32, Convert.FromBase64String(otpInfo.EncryptionIV));
				if (pbSecret == null) throw new InvalidOperationException();

				otpInfo.Secret = pbSecret;
				otpInfo.Counter += otpInfo.OtpsRequired;
			}
			else // >= v2.0, supporting look-ahead
			{
				bool bSuccess = false;
				for (int i = 0; i < otpInfo.EncryptedSecrets.Count; ++i)
				{
					OtpEncryptedData d = otpInfo.EncryptedSecrets[i];
					pbSecret = OtpUtil.DecryptSecret(d, lOtps.ToArray(), 0,
					                                 lOtps.Count);
					if (pbSecret != null)
					{
						otpInfo.Secret = pbSecret;
						otpInfo.Counter += ((ulong) otpInfo.OtpsRequired +
						                     (ulong) i);
						bSuccess = true;
						break;
					}
				}
				if (!bSuccess) throw new InvalidOperationException();
			}
		
		}
	}
}
