/***********************************************************************
 * 
 *  Copyright (C) 2005-2006 Novell, Inc. All Rights Reserved.
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; version 2.1
 *  of the License.
 *
 *  This library 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
 *  Library Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, Novell, Inc.
 * 
 *  To contact Novell about this file by physical or electronic mail, 
 *  you may find current contact information at www.novell.com.
 * 
 ***********************************************************************/

using System;
using System.Collections;
using System.Threading;
using System.IO;
using System.Xml;
using System.Xml.Serialization;
using System.Security.Cryptography;
using System.Text;
using sscs.cache;
using sscs.common;
using sscs.constants;
using sscs.lss;
using sscs.crypto;

namespace sscs.cache
{
    class SecretStore
    {
        internal string secretStoreName; // User name ?
        internal int refCount;
        private uint version;
        private Hashtable tKeyChainList = new Hashtable();
        private Hashtable keyChainList; //= Hashtable.Synchronized(tKeyChainList);
        internal User user;
        private Mutex ssMutex ; //reqd only for refCount
        private int state; // Maintains the state of SS ( keychain
                           // type availability). TODO: Convert to a class.

		private static int STATE_NOT_DEFINED = 0;
		private static int STATE_OK	= 1;
		private static int STATE_LOCKED = 2;

        private LocalStorage lss = null; 
        bool bIsStorePersistent = false; 

        private DateTime createTime;
        public DateTime CreateTime
        {
            get
            {
                return createTime;
            }
            set
            {
                createTime = value;
            }
        }

        ~SecretStore()
        {
           ssMutex.Close();
        }

        internal SecretStore(User ssUser)
        {
            secretStoreName = ssUser.GetUserName();
            version         = 1;
            state           = STATE_NOT_DEFINED;
            user            = ssUser;
            refCount        = 0;
            keyChainList    = Hashtable.Synchronized(tKeyChainList);
            
            ssMutex = new Mutex();  
        }

        internal bool IsStorePersistent()
        {
            return bIsStorePersistent;
        }
	
		public bool StopPersistence()
		{
			if(lss != null && bIsStorePersistent == true)
			{
				lss.StopPersistence();
				lss = null;
				bIsStorePersistent = false;
			}			
			return true;
		}
  
		public bool IsStoreLocked()
		{
			if (state == STATE_LOCKED)
				return true;
			else
				return false;
		}

		public void LockStore()
		{
			state = STATE_LOCKED;
		}

		public bool UnlockStore(string sDesktopPassword, string sMasterPassword)
		{
			if (sDesktopPassword != null)
			{
				// verify Desktop password
				//state = STATE_OK;
				//return true;
			}


			if (sMasterPassword != null)
			{
				// verify MasterPassword
				if (SetMasterPassword(sMasterPassword))
				{
					state = STATE_OK;
					return true;
				}
			}

			return false;
		}
		
		internal bool StartPersistenceByDesktopPasswd(string desktopPasswd)
        {
			CSSSLogger.DbgLog("StartPersistenceByDesktopPasswd - Called");

			// make sure we have a user home directory
			if (GetUserHomeDirectory() == null || GetUserHomeDirectory().Length < 1)
			{				
				CSSSLogger.DbgLog("StartPersistenceByDesktopPasswd - No Home directory yet");
				return false;
			}
			else
			{
				if (!Directory.Exists(GetUserHomeDirectory()))
				{
					CSSSLogger.DbgLog("StartPersistenceByDesktopPasswd - Home directory is not created yet");
					return false;
				}
			}


            try
            {
                byte[] baPasscode;
                /* Persistence could have started because the user
                 * could have set master password.
                 */
                if(lss != null && bIsStorePersistent == true)
                {
                    /* Verify passcode and if validation fails, rewrite 
                     * desktop file.
                     */
                    if(File.Exists(GetPasscodeByDesktopFilePath()))
                    {
                    }
                    else
                    {
                        /* Write the desktop passwd file.
                         */
                    }
                    CSSSLogger.DbgLog(CSSSLogger.GetExecutionPath(this) + " Store is already persistent");
					CSSSLogger.DbgLog("StartPersistenceByDesktopPasswd - Started");
                    return true;
                }
				
            
                if(!File.Exists(GetPasscodeByDesktopFilePath()))
                {										
					if (File.Exists(GetPasscodeByMasterPasswdFilePath()))
					{
						// wait for the user to start the Persistence by entering MP
						return false;					
					}

                    //Else passcode needs to be generated.
                    baPasscode = CASACrypto.GenerateMasterPasscodeUsingString(
                                             desktopPasswd,
                                             GetPasscodeByDesktopFilePath(), 
                                             GetValidationFilePath(),
                                             user.UserIdentifier);

                    if( null == baPasscode )
                        return false;

                    if(!File.Exists(GetKeyFilePath()))
                    {
						GenerateAndStoreEncryptionKey(baPasscode);
						lss = new LocalStorage(this,baPasscode);
						bIsStorePersistent = true;
						return true;
                    }

                }
                baPasscode = CASACrypto.GetMasterPasscodeUsingDesktopPasswd(desktopPasswd, GetPasscodeByDesktopFilePath());
                if(baPasscode != null)
                {
                    if(CASACrypto.ValidatePasscode(baPasscode,GetValidationFilePath()))
                    {
                        lss = new LocalStorage(this,baPasscode);
                        bIsStorePersistent = true;
                        return true;
                    }
                    else
                    {
                        lss = null;
                        bIsStorePersistent = false; //till masterPasswd is verified
                    }
                    return true;
                }
                else
                {
                    CSSSLogger.DbgLog(CSSSLogger.GetExecutionPath(this) + " May be desktop passwd has changed");
                    lss = null;
                    bIsStorePersistent = false;
                    return false;
                }
            }
            catch(Exception e)
            {
                CSSSLogger.ExpLog(e.ToString());
            }
            return false;
        }

		internal bool GenerateAndStoreEncryptionKey(byte[] baPasscode)
		{
			RijndaelManaged myRijndael = new RijndaelManaged();
			byte[] key;
			byte[] IV = new byte[16];
			//Create a new key and initialization vector.
			try 
			{
				myRijndael.GenerateKey();
				key = myRijndael.Key;
			
				CASACrypto.StoreKeySetUsingMasterPasscode(key,IV, 
					baPasscode,
					GetKeyFilePath());
			}
			catch (Exception e)
			{
				return false;
			}
			return true;			
		}


        internal bool SetMasterPassword(string mPasswdFromIDK)
        {
            try
            {
                char[] trimChars = {'\0'};
                string mPasswd = mPasswdFromIDK.TrimEnd(trimChars);
                bool isVerifyOperation = false;
                string mPasswdFileName = GetPasscodeByMasterPasswdFilePath();
                byte[] baPasscode;
                if(File.Exists(mPasswdFileName))
                    isVerifyOperation = true; //else it is a set operation.

                string desktopPasswd = GetDesktopPasswd();
            
                if(isVerifyOperation == false)
                {
                    /* Here the master password file needs to be generated.
                     */
                    if(desktopPasswd != null)
                    {
                        baPasscode = CASACrypto.GetMasterPasscodeUsingDesktopPasswd(desktopPasswd, GetPasscodeByDesktopFilePath());
                        if(CASACrypto.ValidatePasscode(baPasscode,GetValidationFilePath()))
                        {
                            CASACrypto.EncryptAndStoreMasterPasscodeUsingString(
                                                                 baPasscode,
                                                                 mPasswd,
                                           GetPasscodeByMasterPasswdFilePath());
                            return true;
                        }
                        else
                        {
                            //Probably desktop passwd has changed.
                            //But as even master passwd is being set only now,
                            //the persistent store is lost.

                            baPasscode = CASACrypto.GenerateMasterPasscodeUsingString(mPasswd,GetPasscodeByMasterPasswdFilePath(),GetValidationFilePath(), user.UserIdentifier);
                            if(baPasscode != null)
                            {
                                CASACrypto.EncryptAndStoreMasterPasscodeUsingString(baPasscode,mPasswd,GetPasscodeByMasterPasswdFilePath());
                                CASACrypto.EncryptAndStoreMasterPasscodeUsingString(baPasscode,desktopPasswd,GetPasscodeByDesktopFilePath());
                                if(File.Exists(GetPersistenceFilePath()))
                                {
                                    File.Delete(GetPersistenceFilePath());
                                    CSSSLogger.DbgLog("Removing the persistent storeas its meaningless now.");
                                }
                                if( bIsStorePersistent == false )
                                { 
                                    lss = new LocalStorage(this,baPasscode);
                                    bIsStorePersistent = true;
                                }
                                return true;
                            }
                            else
                            {
                                return false;
                            }
                        }
                        //return true;
                    }//if a valid desktop Passwd is present - if ends here
                    else
                    {
                        /* If desktop passwd is not there and user sets
                         * master password.
                         */
                        if(File.Exists(GetPersistenceFilePath()))
                        {
                            File.Delete(GetPersistenceFilePath());
                            CSSSLogger.DbgLog("Removing the persistent storeas its meaningless now. - Desktop passwd is not there and Master password is being set");
                        }
                        if(File.Exists((GetPasscodeByDesktopFilePath())))
                        {
                            File.Delete((GetPasscodeByDesktopFilePath()));
                            CSSSLogger.DbgLog("Removing the persistent storeas its meaningless now. - Desktop passwd is not there and Master password is being set");
                        }
                

                        baPasscode = CASACrypto.GenerateMasterPasscodeUsingString(mPasswd,GetPasscodeByMasterPasswdFilePath(),GetValidationFilePath(), user.UserIdentifier);
                        if(baPasscode != null)
                        { 
							if(!File.Exists(GetKeyFilePath()))
							{
								GenerateAndStoreEncryptionKey(baPasscode);
							}

                            CASACrypto.EncryptAndStoreMasterPasscodeUsingString(baPasscode,mPasswd,GetPasscodeByMasterPasswdFilePath());
                            if( bIsStorePersistent == false )
                            {
                                lss = new LocalStorage(this,baPasscode);
                                bIsStorePersistent = true;
                            }
                            return true;
                        }
                        return false;
                    }
                }//end of isVerifyOperation == false
                else
                {
                    /* Verify the master password. If verified, and if
                     * persistence has not started, start it.
                     */

                    //Get the passcode from master passwd file and validate.
                    //If validation succeeds,start persistence.
                    if(desktopPasswd == null)
                    {
                        baPasscode = CASACrypto.DecryptMasterPasscodeUsingString(mPasswd, GetPasscodeByMasterPasswdFilePath());
                        if(CASACrypto.ValidatePasscode(baPasscode,GetValidationFilePath()))
                        {
                            if(bIsStorePersistent == false)
                            {
                                lss = new LocalStorage(this,baPasscode);
                                bIsStorePersistent = true;
                            }
                            return true;
                        }
                        else
                        {    
                            return false;
                        }
                    }
                    else
                    {  //There are 2 cases - either desktop passwd has changed
                       //or it hasnt.
                        baPasscode = CASACrypto.GetMasterPasscodeUsingMasterPasswd(mPasswd, GetPasscodeByMasterPasswdFilePath());
                        if(CASACrypto.ValidatePasscode(baPasscode,GetValidationFilePath()))
                        {
                            RewriteDesktopPasswdFile(baPasscode,desktopPasswd);
                            if(bIsStorePersistent == false)
                            {
                                lss = new LocalStorage(this,baPasscode);
                                bIsStorePersistent = true;
                            }
                            return true;
                        }
                        else
                        {
                            return false;
                        }
                    }                          
                }
            }
            catch(Exception e)
            {
                CSSSLogger.ExpLog(e.ToString());
            }
            return false;
        }//End of SetMasterPassword

        internal bool RewriteDesktopPasswdFile(byte[] baPasscode, string desktopPasswd)
        {
            try
            {
                CASACrypto.EncryptAndStoreMasterPasscodeUsingString(baPasscode, desktopPasswd, GetPasscodeByDesktopFilePath());
                CSSSLogger.DbgLog("Re-encryted passcode with desktop passwd");
            }
            catch(Exception e)
            {
                CSSSLogger.ExpLog(e.ToString());
            }
            return true;
        }

        internal byte[] GetPasscodeFromOldDesktopPasswd(string oldDesktopPasswd)
        {
            try
            {
                byte[] baPasscode = CASACrypto.GetMasterPasscodeUsingDesktopPasswd(oldDesktopPasswd, GetPasscodeByDesktopFilePath());
                if(CASACrypto.ValidatePasscode(baPasscode,GetValidationFilePath()))
                {
                    return baPasscode;
                }
            }
            catch(Exception e)
            {
                CSSSLogger.ExpLog(e.ToString());
            }
            return null;
        } 

        /* This method would be called, when the user is setting his
         * master passcode for the first time.
         */

        internal bool SetMasterPasscode(string sMasterPasscode)
        {
            return true;    
#if false
            bool bRet = false;
            try
            {
                if(!CASACrypto.CheckIfMasterPasscodeIsAvailable(desktopPasswd, GetPasswdFilePath()))
                {
                    RijndaelManaged myRijndael = new RijndaelManaged();
                    byte[] key;
                    byte[] IV = new byte[16];
                    //Create a new key and initialization vector.
                    myRijndael.GenerateKey();
                    key = myRijndael.Key;
                    CASACrypto.StoreKeySetUsingMasterPasscode(key,IV,sMasterPasscode,GetKeyFilePath());
                    //Store the master passcode encrypted with the desktopPasswd
                    CASACrypto.EncryptAndStoreMasterPasscodeUsingString(sMasterPasscode, desktopPasswd, GetPasswdFilePath());
                    lss = new LocalStorage(this,sMasterPasscode);
                    bIsStorePersistent = true;
                    bRet = true;
                }
                else
                {
                    //Console.WriteLine("Master passcode is already set");
                }
            }
            catch(Exception e)
            {
                CSSSLogger.ExpLog(e.ToString());
            }
            return bRet;
#endif
        }

        internal void IncrRefCount()
        {
            try
            {
                ssMutex.WaitOne();
                refCount++;
                ssMutex.ReleaseMutex();
                CSSSLogger.DbgLog(CSSSLogger.GetExecutionPath(this) + " : RefCount = " + refCount);
            }
            catch(Exception e)
            {
                CSSSLogger.ExpLog(e.ToString());
                throw e;
            }
        }

        internal void DecrRefCount()
        {
            try
            {
                ssMutex.WaitOne();
                refCount--;
                ssMutex.ReleaseMutex();
                CSSSLogger.DbgLog(CSSSLogger.GetExecutionPath(this) + " : RefCount = " + refCount);
            }
            catch(Exception e)
            {
                CSSSLogger.ExpLog(e.ToString());
                throw e;
            }

        }

        internal bool AddKeyChain(KeyChain keychain)
        {
            try
            {
                keychain.CreatedTime = DateTime.Now;
                keyChainList.Add(keychain.GetKey(),keychain);
            }
            catch(Exception e)
            {
                 CSSSLogger.DbgLog(e.ToString());
                 throw e;
            }

            CSSSLogger.DbgLog(CSSSLogger.GetExecutionPath(this) + " - Succefully added Keychain = "+ keychain.GetKey() + " length = "+ (keychain.GetKey()).Length);

            return true;
        }
        internal bool RemoveKeyChain(string id)
        {
            keyChainList.Remove(id);
            return true;
        }

		internal KeyChain GetKeyChainDefault()
		{	
			return GetKeyChain("SSCS_SESSION_KEY_CHAIN_ID\0");
		}

        internal KeyChain GetKeyChain(string id)
        {
            if(keyChainList.ContainsKey(id))
            {
               CSSSLogger.DbgLog("In " + CSSSLogger.GetExecutionPath(this) + " Keychain already exists.");
               KeyChain kc = (KeyChain)(keyChainList[id]);
               kc.AccessedTime = DateTime.Now;
               return kc;
            }  
            else
            {
               CSSSLogger.DbgLog("In " + CSSSLogger.GetExecutionPath(this) + " Keychain doesnot exist.Returning null.");
                throw new KeyChainDoesNotExistException(id);
            }
        }
		
		internal bool CheckIfKeyChainExists(string id)
        {
            if(keyChainList.ContainsKey(id))
                return true;
            else
                return false;
        }


		internal void UpdatePersistentStore()
		{
			if (lss != null)
				lss.PersistStoreWithDelay();
		}

        /* This function would need to do any storage/cleanup required
         * before removing a user session.
         */
        internal bool CommitStore()
        {
            if(lss != null)
                lss.PersistStore();
            return true;
        }

        internal IEnumerator GetKeyChainEnumerator()
        {
           //TBD
           // Return an Enumerator class which has all secrets in this keychain
           return keyChainList.GetEnumerator();
        }
        internal void DumpSecretstore()
        {
            lock(keyChainList.SyncRoot)
            {
            IDictionaryEnumerator iter = (IDictionaryEnumerator)GetKeyChainEnumerator();
            while( iter.MoveNext() )   
            {
                int i = 0;
                KeyChain kc = (KeyChain)iter.Value;
                CSSSLogger.DbgLog("\nKeychain id = " + kc.GetKey());
                CSSSLogger.DbgLog("Secret List is ");
                IDictionaryEnumerator secIter = (IDictionaryEnumerator)(kc.GetAllSecrets());
                while(secIter.MoveNext())
                {
                    Secret secret = (Secret)secIter.Value;
                    CSSSLogger.DbgLog("Secret " + i.ToString() + " id = " + secret.GetKey() + " value = " + secret.GetValue() );
                    IDictionaryEnumerator etor = (IDictionaryEnumerator) secret.GetKeyValueEnumerator();
                    while(etor.MoveNext())
                    {
                        KeyValue kv = (KeyValue)etor.Value;
                        CSSSLogger.DbgLog("Key = " + kv.Key +" Value = " + kv.GetValue());
                    }
                    i++;    
                }
            }
            }
        }

        internal int GetSecretStoreState() 
        {
            return state;
        }
        internal int GetNumKeyChains()
        {
            return keyChainList.Count;
        }

        internal bool SetSecretStoreState(int stateToSet)
        {
           //BrainShare Special Only - Only Session keychains state 1
           
           state = STATE_OK;
           return true; 
        }

		internal bool ChangeMasterPassword(string sCurrentPWD, string sNewPWD)
		{
			string sMasterFilePath = GetPasscodeByMasterPasswdFilePath();
			byte[] baPasscode = CASACrypto.GetMasterPasscodeUsingMasterPasswd(sCurrentPWD, sMasterFilePath);
			if (baPasscode != null)
			{
				CASACrypto.EncryptAndStoreMasterPasscodeUsingString(baPasscode, sNewPWD, sMasterFilePath);
				return true;
			}
			return false;
		}

        internal string GetDesktopPasswd()
        { 
            try
            {
                string keyChainId = ConstStrings.SSCS_SESSION_KEY_CHAIN_ID + "\0";
                KeyChain keyChain = GetKeyChain(keyChainId);
                Secret secret = keyChain.GetSecret(ConstStrings.MICASA_DESKTOP_PASSWD);
                string passwd = secret.GetKeyValue(ConstStrings.MICASA_DESKTOP_PASSWD_KEYNAME).GetValue();
                return passwd;
            }
            catch(Exception e)
            {
                CSSSLogger.ExpLog(e.ToString());
            }
            return null;
        }

        internal string GetUserHomeDirectory()
        {
            return user.GetUserHomeDir();
        }

        internal string GetKeyFilePath()
        {
            string homeDir = GetUserHomeDirectory();
            return homeDir + ConstStrings.MICASA_KEY_FILE;
        }
        internal string GetPasscodeByDesktopFilePath()
        {
            string homeDir = GetUserHomeDirectory();
            return homeDir + ConstStrings.MICASA_PASSCODE_BY_DESKTOP_FILE;
        }

        internal string GetPasscodeByMasterPasswdFilePath()
        {
            string homeDir = GetUserHomeDirectory();
            return homeDir + ConstStrings.MICASA_PASSCODE_BY_MASTERPASSWD_FILE;
        }

        internal string GetPersistenceFilePath()
        {
            string homeDir = GetUserHomeDirectory();
            return homeDir + ConstStrings.MICASA_PERSISTENCE_FILE;
        }
        internal string GetValidationFilePath()
        {
            string homeDir = GetUserHomeDirectory();
            return homeDir + ConstStrings.MICASA_VALIDATION_FILE;
        }
    }
}