using System;
using System.IO;
using System.Text;
using System.Security.Cryptography;
using sscs.common;
using sscs.constants;

namespace sscs.crypto
{
	public class CASACrypto
	{
		internal static byte[] Generate16ByteKeyFromString(string TheString)
		{
			byte[] baKey = new byte[16]; //return value
			try
			{
				Random rand = new Random(TheString.GetHashCode());
				rand.NextBytes(baKey);			
			}			
			catch(Exception e)
			{
				CSSSLogger.ExpLog(e.ToString());
				CSSSLogger.DbgLog("Key generation failed");
				baKey = null;
			}
			return baKey;
		}

		internal static bool StoreKeySetUsingMasterPasscode(byte[] key, 
			byte[] IV, byte[] baMasterPasscode, string fileName)
		{
			bool bRet = false;
			try
			{

				//Get an encryptor.
				RijndaelManaged myRijndael = new RijndaelManaged();
				ICryptoTransform encryptor; 
				encryptor = myRijndael.CreateEncryptor(baMasterPasscode, baMasterPasscode);
                                    
				//Encrypt the data to a file            
				FileStream fsEncrypt = new FileStream(fileName, FileMode.Create);

				SHA256 sha = new SHA256Managed();
				byte[] hash = sha.ComputeHash(key);

				fsEncrypt.Write(hash,0,hash.Length);
				fsEncrypt.Flush();

				CryptoStream csEncrypt = new CryptoStream(fsEncrypt, encryptor, CryptoStreamMode.Write);            
                    
				//Write all data to the crypto stream and flush it.
				csEncrypt.Write(key, 0, key.Length);
				csEncrypt.FlushFinalBlock();
				fsEncrypt.Close();
				bRet = true;
			}
			catch(Exception e)
			{
				CSSSLogger.ExpLog(e.ToString());
				CSSSLogger.DbgLog("Unable to store the generated key");
				bRet = false;
			}
			return bRet;
		}    

		internal static byte[] GetKeySetFromFile(byte[] baMasterPasscode, 
			string fileName ) 
		{
			byte[] baSavedKey = null;
			try
			{
				if(!File.Exists(fileName))
				{
					return null;
				}
				
				/* Get a decryptor that uses the same key and IV 
				 * as the encryptor.
				 */

				RijndaelManaged myRijndael = new RijndaelManaged();
				ICryptoTransform decryptor = myRijndael.CreateDecryptor(baMasterPasscode, baMasterPasscode);
				//Now decrypt             
				FileStream fsDecrypt = new FileStream(fileName, FileMode.Open);

				byte[] storedHash = new byte[32];
				fsDecrypt.Read(storedHash,0,storedHash.Length);

				CryptoStream csDecrypt = new CryptoStream(fsDecrypt, decryptor, CryptoStreamMode.Read);            
				baSavedKey = new byte[32];
  
				//Read the data out of the crypto stream.
				csDecrypt.Read(baSavedKey, 0, baSavedKey.Length);
				fsDecrypt.Close();                        

				SHA256 sha = new SHA256Managed();
				byte[] newHash = sha.ComputeHash(baSavedKey);
				for( int i = 0 ; i < 32; i++ )
				{
					if(storedHash[i] != newHash[i])
					{
						CSSSLogger.DbgLog("Hash doesnot match");
						return null;
					}
				}

				return baSavedKey;
			}
			catch(Exception e)
			{
				CSSSLogger.ExpLog(e.ToString());
				CSSSLogger.DbgLog("Unable to get the stored key");
				baSavedKey = null;
			}
			return baSavedKey;
		}

		internal static void EncryptDataAndWriteToFile(byte[] xmlData, 
			byte[] key, string fileName)
		{
			try
			{
				byte[] IV = new byte[16];
				for(int z = 0 ; z < 16; z++ )
					IV[z] = key[z];

				//Get an encryptor.
				RijndaelManaged myRijndael = new RijndaelManaged();
				ICryptoTransform encryptor = myRijndael.CreateEncryptor(key, IV);
             
				//Encrypt the data to a file            
				FileStream fsEncrypt = new FileStream(fileName, FileMode.Create);
				SHA256 sha = new SHA256Managed();

				byte[] hash = sha.ComputeHash(xmlData);
            
				fsEncrypt.Write(hash,0,hash.Length);           
				fsEncrypt.Flush();
      
				CryptoStream csEncrypt = new CryptoStream(fsEncrypt, encryptor, CryptoStreamMode.Write);            
                    
				//Write all data to the crypto stream and flush it.
				csEncrypt.Write(xmlData, 0, xmlData.Length);
				csEncrypt.FlushFinalBlock();
				fsEncrypt.Close();
			}
			catch(Exception e)
			{
				CSSSLogger.ExpLog(e.ToString());
				CSSSLogger.DbgLog("Encrypting and storing to file failed.");
			}
		}

		internal static byte[] ReadFileAndDecryptData(byte[] key, 
			string fileName)
		{
                        FileStream fsDecrypt = null;
			try
			{
				byte[] IV = new byte[16];
				for(int z = 0 ; z < 16; z++ )
					IV[z] = key[z];

				//Get a decryptor that uses the same key and IV as the encryptor.
				RijndaelManaged myRijndael = new RijndaelManaged();
				ICryptoTransform decryptor = myRijndael.CreateDecryptor(key, IV);

				if(!File.Exists(fileName))
				{
					return null;
				}

				//Now decrypt             
				fsDecrypt = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read);                        
				byte[] storedHash = new byte[32];
				fsDecrypt.Read(storedHash,0,storedHash.Length);

				CryptoStream csDecrypt = new CryptoStream(fsDecrypt, decryptor, CryptoStreamMode.Read);            
				long fileLen = fsDecrypt.Length - 32;
				byte[] fromEncrypt = new byte[fileLen];

				//Read the data out of the crypto stream.
				int bytesRead = csDecrypt.Read(fromEncrypt, 0, fromEncrypt.Length);
				byte[] tmpEncrypt = new byte[bytesRead];
				for(int i = 0 ; i < bytesRead; i++ )
					tmpEncrypt[i] = fromEncrypt[i];

				SHA256 sha = new SHA256Managed();
				byte[] newHash = sha.ComputeHash(tmpEncrypt);

				for( int i = 0 ; i < 32; i++ )
				{
					if(storedHash[i] != newHash[i])
					{
						CSSSLogger.DbgLog("Hash doesnot match");
						return null;
					}
				}
            
				fsDecrypt.Close();
				return tmpEncrypt;
			}
			catch(Exception e)
			{
				Console.WriteLine(e.ToString());
			}
                        if(fsDecrypt != null)
                        {
                            fsDecrypt.Close();
                        }
                        return null;

		}

		/* The methods EncryptData() and DecryptData() would be 
		 * required when we use a database to store secrets.
		 */
 
		/* Encrypts the data with the key and returns the encrypted buffer.
		 */

		internal static byte[] EncryptData(byte[] data, byte[] key) 
		{

			try
			{
				byte[] IV = new byte[16];
				int i = 0;
				for(i = 0 ; i < 16; i++ )
					IV[i] = key[i];

				//Get an encryptor.
				RijndaelManaged myRijndael = new RijndaelManaged();
				ICryptoTransform encryptor = myRijndael.CreateEncryptor(key, IV);
				MemoryStream ms1 = new MemoryStream();
				CryptoStream csEncrypt = new CryptoStream(ms1, encryptor, CryptoStreamMode.Write);            
                    
				//Write all data to the crypto stream and flush it.
				csEncrypt.Write(data, 0, data.Length);
				csEncrypt.FlushFinalBlock();
				return ms1.ToArray();
			}   
			catch(Exception e)
			{
				CSSSLogger.ExpLog(e.ToString());
			}
			return null;
		}

		/* Decrypts the buffer(encrypted) with the key and returns the
		 * decrypted data.
		 */      

		internal static byte[] DecryptData(byte[] buffer, byte[] key)
		{
			try
			{
				byte[] IV = new byte[16];
				for(int i = 0 ; i < 16; i++ )
					IV[i] = key[i];
				//Get a decryptor that uses the same key and IV as the encryptor.
				RijndaelManaged myRijndael = new RijndaelManaged();
				ICryptoTransform decryptor = myRijndael.CreateDecryptor(key, IV);
				MemoryStream ms1 = new MemoryStream(buffer);           
				CryptoStream csDecrypt = new CryptoStream(ms1, decryptor, CryptoStreamMode.Read);            
				byte[] fromEncrypt = new byte[buffer.Length];
				//Read the data out of the crypto stream.
				csDecrypt.Read(fromEncrypt, 0, fromEncrypt.Length);
				return fromEncrypt;
			}
			catch(Exception e)
			{
				CSSSLogger.ExpLog(e.ToString());
			}
			return null;            
		}

		/* This method checks if we can get the master passcode by
		 * decrypting the passwds file ( where we store all possible
		 * passwds cross-encrypted. 
		 * 
		 * TBD : As we are storing master passcode and the keys in 2
		 * different files, we need to take care of cases when 1 of the files
		 * is deleted.
		 */

		internal static bool CheckIfMasterPasscodeIsAvailable(string desktopPasswd, string fileName)
		{
			return (File.Exists(fileName));
		}

		internal static byte[] GetMasterPasscode(string desktopPasswd, string fileName)
		{
			byte[] mp = DecryptMasterPasscodeUsingString(desktopPasswd, fileName);
			return mp;
		}

		/* TBD - There must be a way, where we establish the integrity of
		 * the files where we store the keys and master passcode. 
		 * Use a marker ?
		 */

		// Used to save the MasterPasscode encrypted with Desktop login, etc
		internal static void EncryptAndStoreMasterPasscodeUsingString(
			byte[] baMasterPasscode, 
			string passwd,
			string fileName)
		{
			try
			{
				if(File.Exists(fileName))
					File.Delete(fileName);
				byte[] baKey = Generate16ByteKeyFromString(passwd);
				

				//Get an encryptor.
				RijndaelManaged myRijndael = new RijndaelManaged();
				ICryptoTransform encryptor;
				encryptor = myRijndael.CreateEncryptor(baKey, baKey);
  
				//Encrypt the data to a file
				FileStream fsEncrypt = new FileStream(fileName,FileMode.Create);
				CryptoStream csEncrypt = new CryptoStream(fsEncrypt, encryptor, 
					CryptoStreamMode.Write);

				//Write all data to the crypto stream and flush it.
				
				csEncrypt.Write(baMasterPasscode, 0, baMasterPasscode.Length);
				csEncrypt.FlushFinalBlock();
				fsEncrypt.Close();
			}
			catch(Exception e)
			{
				CSSSLogger.ExpLog(e.ToString());
			}
		}
		public static byte[] DecryptMasterPasscodeUsingString(string passwd,
			string fileName)
		{
			try
			{
				byte[] baKey = Generate16ByteKeyFromString(passwd);

				/* Get a decryptor that uses the same key and 
				 * IV as the encryptor. 
				 */
				RijndaelManaged myRijndael = new RijndaelManaged();
				ICryptoTransform decryptor = myRijndael.CreateDecryptor(baKey,
					baKey);
				//Now decrypt
				FileStream fsDecrypt = new FileStream(fileName, FileMode.Open);
				CryptoStream csDecrypt = new CryptoStream(fsDecrypt, decryptor,
					CryptoStreamMode.Read);
				byte[] baSavedMasterPasscode = new byte[16];

				//Read the data out of the crypto stream.
				csDecrypt.Read(baSavedMasterPasscode, 0, 16);
				fsDecrypt.Close();

				return baSavedMasterPasscode;				
			}
			catch(Exception e)
			{
				CSSSLogger.ExpLog(e.ToString());
				CSSSLogger.DbgLog("Unable to decrypt master passode");
			}
			return null;
		}

		internal static byte[] GetMasterPasscodeUsingMasterPasswd(
			string mPasswd,
			string fileName)
		{
			byte[] baMasterPasscode;
			try
			{
				if(File.Exists(fileName))
				{
					/* Decrypt the passcode from the file using master passwd.
					 * and return the decrypted passcode.
					 */
					baMasterPasscode = DecryptMasterPasscodeUsingString(mPasswd,
						fileName);   
					return baMasterPasscode;
				}
				else
					return null;
			}
			catch(Exception e)
			{
				CSSSLogger.ExpLog(e.ToString());
				CSSSLogger.DbgLog("Failed to get master passcode from master password.");
			}
			return null;
		}

		internal static byte[] GetMasterPasscodeUsingDesktopPasswd(
			string desktopPasswd,
			string fileName)
		{
			byte[] passcode;
			try
			{
				if(File.Exists(fileName))
				{
					/* Decrypt the passcode from the file using desktop passwd.
					 * and return the decrypted passcode.
					 */
					passcode = DecryptMasterPasscodeUsingString(desktopPasswd,
						fileName);   
					return passcode;
                
				}
				else
					return null;
			}
			catch(Exception e)
			{
				CSSSLogger.ExpLog(e.ToString());
				CSSSLogger.DbgLog("Failed to get master passcode using desktop passwd");
			}
			return null;
		}
		//internal static string GenerateMasterPasscodeUsingDesktopPasswd(
		internal static byte[] GenerateMasterPasscodeUsingString(
			string desktopPasswd,
			string fileName,
			string validationFile,
			UserIdentifier userId
			)
		{
			try
			{
				/* Generate passcode using desktop passwd and store
				 * it in the passwd file encrypted with the desktop passwd.
				 * Encrypt a well-known with the passcode and store it.
				 * Return the generated passcode.
				 */
				Random random = new Random(desktopPasswd.GetHashCode());
				int randNum = random.Next();
				string randStr = randNum.ToString() + userId.GetUID().ToString();
				byte[] baPasscode = Generate16ByteKeyFromString(randStr);
		
				EncryptAndStoreMasterPasscodeUsingString(baPasscode,
					desktopPasswd,
					fileName);                                              
				EncryptDataAndWriteToFile(
					Encoding.Default.GetBytes(
					ConstStrings.MICASA_VALIDATION_STRING),
					baPasscode,
					validationFile);
				return baPasscode;         
			}
			catch(Exception e)
			{
				CSSSLogger.ExpLog(e.ToString());
				CSSSLogger.DbgLog("Generation of master passcode failed.");
			}
			return null;
		}

		public static bool ValidatePasscode(byte[] baPasscode, string fileName)
		{
			/* Here we decrpyt a well known string, throw exception 
			 * if not successful
			 * A well-known string is encrpyted by the Passcode and saved
			 */

			if ((baPasscode == null) || baPasscode.Length < 1 )
				return false;

			try
			{				
				byte[] baString = ReadFileAndDecryptData(baPasscode, fileName);
				string sString = Encoding.Default.GetString(baString);
				char[] trimChars = {'\0'};
				sString = sString.TrimEnd(trimChars);
				if( ConstStrings.MICASA_VALIDATION_STRING.Equals(sString))
				{
					return true;
				}
				else
				{
					return false;
				}
			}
			catch(Exception e)
			{
				CSSSLogger.ExpLog(e.ToString());
				CSSSLogger.DbgLog("Validation of passcode failed.");
			}
			return false;
		}
        
	}
}