
using System;
using System.IO;
using System.Text;
using System.Security.Cryptography;

namespace Tomboy
{
	public class InvalidKeyException: Exception
	{
	}

	public class EncryptionManager
	{
		static EncryptionManager instance = null;
		static readonly object lock_ = new object();

		System.Collections.ArrayList key_cache; 

		protected EncryptionManager ()
		{
			key_cache = new System.Collections.ArrayList ();
		}

		public static EncryptionManager Instance
		{
			get
			{
				lock (lock_)
				{
					if (instance == null)
						instance = new EncryptionManager ();
					return instance;
				}
			}
			set {
				lock (lock_)
				{
					instance = value;
				}
			}
		}

		public string HandleEncryptedNote (NoteData data)
		{
			if (data.EncryptionKey != null) 
			{
				try
				{
					return Decrypt (data);
				}
				catch (Exception)
				{
					// We couldn't decrypt the data using the key; it must have
					// changed.  We'll prompt the user instead, or try a cached key.
					data.EncryptionKey = null;
				}
			}

			// Try decrypting note with the keys in the key cache first.
			foreach (byte[] key in key_cache)
			{
				data.EncryptionKey = key;
				try
				{	
					return Decrypt (data);
				}
				catch (Exception)
				{
				}
			}

			// We don't have the key cached; ask the user for it.
			data.EncryptionKey = null;
			data.EncryptionKey = GetDecryptionKey (null);

			if (data.EncryptionKey != null)
			{
				try
				{	
					return Decrypt (data);
				}
				catch (Exception)
				{
				}
			}
			
			// We couldn't decrypt it; throw an exception to our caller.
			throw new DecryptionRequiredException ();
		}

		public byte[] Encrypt (string data, byte[] key)
		{
			Rijndael rijndael = Rijndael.Create ();

			MemoryStream m = new MemoryStream ();
			m.Write(rijndael.IV, 0, 16);

			CryptoStream cs = new CryptoStream (m, 
				rijndael.CreateEncryptor (key, rijndael.IV), CryptoStreamMode.Write);
			StreamWriter sw = new StreamWriter (cs);
			sw.Write(data);
			sw.Close();
			cs.Close();

			return m.ToArray();
		}

		public string Decrypt (NoteData data)
		{
			byte[] enc = data.EncryptedText;
			
			byte[] iv = new byte[16];
			Array.Copy(enc, iv, 16);

			MemoryStream m = new MemoryStream (enc, 16, enc.Length - 16);
			CryptoStream cs = new CryptoStream (m, 
				Rijndael.Create ().CreateDecryptor (data.EncryptionKey, iv),
				CryptoStreamMode.Read);

			StreamReader sr = new StreamReader (cs);
			return sr.ReadToEnd ();
		}

		public byte[] GetEncryptionKey (Gtk.Window parent)
		{
			return HashPassword ("test");
		}

		public byte[] GetDecryptionKey (Gtk.Window parent)
		{
			return HashPassword ("test");
		}
		
		public byte[] HashPassword (string password)
		{
			return new MD5CryptoServiceProvider ().ComputeHash (
				Encoding.ASCII.GetBytes("*Tomboy*PasswordSalt*" + password));
		}

		public void ClearKeyCache ()
		{
			key_cache.Clear ();
		}
	}
}

