/*************************************************
* Algorithm Lookup Table Source File             *
* (C) 1999-2004 The Botan Project                *
*************************************************/

#include <botan/lookup.h>
#include <botan/look_add.h>
#include <botan/mutex.h>
#include <map>

namespace Botan {

namespace {

/*************************************************
* Name to algorithm mapping tables               *
*************************************************/
std::map<std::string, BlockCipher*> bc_map;
std::map<std::string, StreamCipher*> sc_map;
std::map<std::string, HashFunction*> hf_map;
std::map<std::string, MessageAuthenticationCode*> mac_map;
std::map<std::string, S2K*> s2k_map;
std::map<std::string, BlockCipherModePaddingMethod*> bc_pad_map;

/*************************************************
* Alias to canonical name mapping table          *
*************************************************/
std::map<std::string, std::string> alias_map;

/*************************************************
* Mutexes controlling access to the tables       *
*************************************************/
Mutex* bc_map_lock = 0;
Mutex* sc_map_lock = 0;
Mutex* hf_map_lock = 0;
Mutex* mac_map_lock = 0;
Mutex* s2k_map_lock = 0;
Mutex* bc_pad_map_lock = 0;
Mutex* alias_map_lock = 0;

}

/*************************************************
* External functions to find things for us       *
*************************************************/
extern BlockCipher*               try_to_get_bc(const std::string&);
extern StreamCipher*              try_to_get_sc(const std::string&);
extern HashFunction*              try_to_get_hash(const std::string&);
extern MessageAuthenticationCode* try_to_get_mac(const std::string&);
extern S2K*                       try_to_get_s2k(const std::string&);

/*************************************************
* Retrieve a block cipher                        *
*************************************************/
const BlockCipher* retrieve_block_cipher(const std::string& name)
   {
   BlockCipher* retval = 0;
   bc_map_lock->lock();
   std::map<std::string, BlockCipher*>::const_iterator algo;
   algo = bc_map.find(deref_alias(name));
   if(algo != bc_map.end())
      retval = algo->second;
   bc_map_lock->unlock();
   if(!retval)
      {
      retval = try_to_get_bc(deref_alias(name));
      add_algorithm(retval);
      }
   return retval;
   }

/*************************************************
* Retrieve a stream cipher                       *
*************************************************/
const StreamCipher* retrieve_stream_cipher(const std::string& name)
   {
   StreamCipher* retval = 0;
   sc_map_lock->lock();
   std::map<std::string, StreamCipher*>::const_iterator algo;
   algo = sc_map.find(deref_alias(name));
   if(algo != sc_map.end())
      retval = algo->second;
   sc_map_lock->unlock();
   if(!retval)
      {
      retval = try_to_get_sc(deref_alias(name));
      add_algorithm(retval);
      }
   return retval;
   }

/*************************************************
* Retrieve a hash function                       *
*************************************************/
const HashFunction* retrieve_hash(const std::string& name)
   {
   HashFunction* retval = 0;
   hf_map_lock->lock();
   std::map<std::string, HashFunction*>::const_iterator algo;
   algo = hf_map.find(deref_alias(name));
   if(algo != hf_map.end())
      retval = algo->second;
   hf_map_lock->unlock();
   if(!retval)
      {
      retval = try_to_get_hash(deref_alias(name));
      add_algorithm(retval);
      }
   return retval;
   }

/*************************************************
* Retrieve a MAC                                 *
*************************************************/
const MessageAuthenticationCode* retrieve_mac(const std::string& name)
   {
   MessageAuthenticationCode* retval = 0;
   mac_map_lock->lock();
   std::map<std::string, MessageAuthenticationCode*>::const_iterator algo;
   algo = mac_map.find(deref_alias(name));
   if(algo != mac_map.end())
      retval = algo->second;
   mac_map_lock->unlock();
   if(!retval)
      {
      retval = try_to_get_mac(deref_alias(name));
      add_algorithm(retval);
      }
   return retval;
   }

/*************************************************
* Retrieve a S2K algorithm                       *
*************************************************/
const S2K* retrieve_s2k(const std::string& name)
   {
   S2K* retval = 0;
   s2k_map_lock->lock();
   std::map<std::string, S2K*>::const_iterator algo;
   algo = s2k_map.find(deref_alias(name));
   if(algo != s2k_map.end())
      retval = algo->second;
   s2k_map_lock->unlock();
   if(!retval)
      {
      retval = try_to_get_s2k(deref_alias(name));
      add_algorithm(retval);
      }
   return retval;
   }

/*************************************************
* Retrieve a block cipher padding method         *
*************************************************/
const BlockCipherModePaddingMethod* retrieve_bc_pad(const std::string& name)
   {
   BlockCipherModePaddingMethod* retval = 0;
   bc_pad_map_lock->lock();
   std::map<std::string, BlockCipherModePaddingMethod*>::const_iterator algo;
   algo = bc_pad_map.find(deref_alias(name));
   if(algo != bc_pad_map.end())
      retval = algo->second;
   bc_pad_map_lock->unlock();
   return retval;
   }

/*************************************************
* Add a block cipher to the lookup table         *
*************************************************/
void add_algorithm(BlockCipher* algo)
   {
   if(!algo) return;
   bc_map_lock->lock();
   if(bc_map.find(algo->name()) != bc_map.end())
      delete bc_map[algo->name()];
   bc_map[algo->name()] = algo;
   bc_map_lock->unlock();
   }

/*************************************************
* Add a stream cipher to the lookup table        *
*************************************************/
void add_algorithm(StreamCipher* algo)
   {
   if(!algo) return;
   sc_map_lock->lock();
   if(sc_map.find(algo->name()) != sc_map.end())
      delete sc_map[algo->name()];
   sc_map[algo->name()] = algo;
   sc_map_lock->unlock();
   }

/*************************************************
* Add a hash function to the lookup table        *
*************************************************/
void add_algorithm(HashFunction* algo)
   {
   if(!algo) return;
   hf_map_lock->lock();
   if(hf_map.find(algo->name()) != hf_map.end())
      delete hf_map[algo->name()];
   hf_map[algo->name()] = algo;
   hf_map_lock->unlock();
   }

/*************************************************
* Add a MAC to the lookup table                  *
*************************************************/
void add_algorithm(MessageAuthenticationCode* algo)
   {
   if(!algo) return;
   mac_map_lock->lock();
   if(mac_map.find(algo->name()) != mac_map.end())
      delete mac_map[algo->name()];
   mac_map[algo->name()] = algo;
   mac_map_lock->unlock();
   }

/*************************************************
* Add a S2K algorithm to the lookup table        *
*************************************************/
void add_algorithm(S2K* algo)
   {
   if(!algo) return;
   s2k_map_lock->lock();
   if(s2k_map.find(algo->name()) != s2k_map.end())
      delete s2k_map[algo->name()];
   s2k_map[algo->name()] = algo;
   s2k_map_lock->unlock();
   }

/*************************************************
* Add a padding method to the lookup table       *
*************************************************/
void add_algorithm(BlockCipherModePaddingMethod* algo)
   {
   if(!algo) return;
   bc_pad_map_lock->lock();
   if(bc_pad_map.find(algo->name()) != bc_pad_map.end())
      delete bc_pad_map[algo->name()];
   bc_pad_map[algo->name()] = algo;
   bc_pad_map_lock->unlock();
   }

/*************************************************
* Add an alias for an algorithm                  *
*************************************************/
void add_alias(const std::string& alias, const std::string& official_name)
   {
   if(alias == "" || official_name == "")
      return;

   Mutex_Holder lock(alias_map_lock);

   if(alias_map.find(alias) != alias_map.end())
      {
      if(deref_alias(alias_map[alias]) != deref_alias(official_name))
         throw Invalid_Argument("add_alias: The alias " + alias +
                                " already exists");
      return;
      }

   alias_map[alias] = official_name;
   }

/*************************************************
* Dereference an alias                           *
*************************************************/
std::string deref_alias(const std::string& name)
   {
   std::map<std::string, std::string>::const_iterator realname;
   realname = alias_map.find(name);
   if(realname == alias_map.end())
      return name;
   return deref_alias(realname->second);
   }

/*************************************************
* Handle startup for the lookup tables           *
*************************************************/
void init_lookup_tables()
   {
   bc_map_lock = get_mutex();
   sc_map_lock = get_mutex();
   hf_map_lock = get_mutex();
   mac_map_lock = get_mutex();
   s2k_map_lock = get_mutex();
   bc_pad_map_lock = get_mutex();
   alias_map_lock = get_mutex();
   }

/*************************************************
* Destroy the lookup tables                      *
*************************************************/
void destroy_lookup_tables()
   {
   std::map<std::string, BlockCipher*>::iterator bc_iter;
   for(bc_iter = bc_map.begin(); bc_iter != bc_map.end(); bc_iter++)
      delete bc_iter->second;

   std::map<std::string, StreamCipher*>::iterator sc_iter;
   for(sc_iter = sc_map.begin(); sc_iter != sc_map.end(); sc_iter++)
      delete sc_iter->second;

   std::map<std::string, HashFunction*>::iterator hf_iter;
   for(hf_iter = hf_map.begin(); hf_iter != hf_map.end(); hf_iter++)
      delete hf_iter->second;

   std::map<std::string, MessageAuthenticationCode*>::iterator mac_iter;
   for(mac_iter = mac_map.begin(); mac_iter != mac_map.end(); mac_iter++)
      delete mac_iter->second;

   std::map<std::string, S2K*>::iterator s2k_iter;
   for(s2k_iter = s2k_map.begin(); s2k_iter != s2k_map.end(); s2k_iter++)
      delete s2k_iter->second;

   std::map<std::string, BlockCipherModePaddingMethod*>::iterator pad_iter;
   for(pad_iter = bc_pad_map.begin(); pad_iter != bc_pad_map.end(); pad_iter++)
      delete pad_iter->second;

   bc_map.clear();
   sc_map.clear();
   hf_map.clear();
   mac_map.clear();
   s2k_map.clear();
   bc_pad_map.clear();
   alias_map.clear();

   delete bc_map_lock;
   delete sc_map_lock;
   delete hf_map_lock;
   delete mac_map_lock;
   delete s2k_map_lock;
   delete bc_pad_map_lock;
   delete alias_map_lock;

   bc_map_lock = 0;
   sc_map_lock = 0;
   hf_map_lock = 0;
   mac_map_lock = 0;
   s2k_map_lock = 0;
   bc_pad_map_lock = 0;
   alias_map_lock = 0;
   }

}
