/**
 * @file
 * $Id$
 * $Revision$
 * $Author$
 * $Date$
 *
 * This file is part of The iWear Framework.
 *
 * The iWear Framework 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 as in version 2 of the License.

 * 
 * The iWear Framework 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
 * The iWear Framework; if not, write to the Free Software Foundation, Inc., 59
 * Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#ifndef __IWEAR_HASH_H
#define __IWEAR_HASH_H

#ifndef __IWEAR_HASHBASE_H
#include <net/hashbase.h>
#endif

#include "net/ssl.h"
#include SSINC(evp.h)

namespace iwear
{

/**
 * The Type of the hash function used with this hash object. It is one of the
 * following which are supported by the OpenSSL implementation.
 */
enum hash_type
{
    md2,		///< Deprecated - unsecure - compatibility only
    md4,		///< Deprecated - unsecure - compatibility only
    md5,		///< Deprecated - unsecure - compatibility only
    sha,		///< Deprecated - unsecure - compatibility only
    sha1,		///< Currently recommended hash function
#ifndef OPENSSL_NO_MDC2
    mdc2,
#endif
    ripemd160,		///< Dont know how secure...
    crc32,
};

template<hash_type ht,class HC>
class Hash : public HashBase
{
private:
protected:
    /**
     * the actual hashcode. Should be one of the typedefd HashCode<>s
     */
    HC hashcode;

    /**
     * returns the openssl digest function structure needed to compute the
     * proper digest requested.
     */
    const EVP_MD * get_digest( void ) const;

    EVP_MD_CTX mdctx;
public:

    /**
     * Constructs an empty, initialized hash object.
     */
    Hash( );
    virtual ~Hash( );

    /**
     * Gets a reference to the actual hashcode. Only usefull if the hash
     * building has finished.
     */
    virtual const HC& getHashcode( void ) const { return hashcode; }

    /**
     * Output a string with a hexadicimal representation of the hashcode. Like
     * the output of md5sum or similar.
     */
    virtual string asHex( void ) const;

    /**
     * Gets the size of the hashcode in bytes.
     */
    virtual size_t getHashsize( void ) const { return hashcode.getHashsize(); }

    /**
     * Gets a pointer to the plain representation of the hashcode as a byte
     * stream.
     */
    virtual const unsigned char * theHash( void ) const;

    /**
     * Resets the Hash to a state, as if it was just created.
     */
    virtual int Reset( void );

    /**
     * updates some data for the hash code, can be used e.g. to feed a big file
     * in small pieces.
     */
    virtual int Update( const string& str );
    virtual int Update( const void* data, unsigned long len );

    /**
     * Initializes, updates and finalizes the hash code from a single string.
     */
    virtual int From( const string& str );
    virtual int From( const unsigned char* d, unsigned long n );

    /**
     * If a hashcode has to be constructed out of smaller pieces, it has to be
     * finalized with this function.
     */
    virtual int Final( void );

};


template<hash_type ht, class HC>
iwear::uid hash_string_to_uid( const std::string& s )
{
    Hash<ht,HC> h;
    h.From(s);
    return h.getHashcode( );
}

template<hash_type ht, class HC>
inline int Hash<ht,HC>::Final( void )
{
    unsigned int len = hashcode.getHashsize();
    if( !EVP_DigestFinal_ex(&mdctx,hashcode.code(),&len)) return 0;
    return Reset();
}

template<hash_type ht, class HC>
inline int Hash<ht,HC>::From( const unsigned char* d, unsigned long n )
{
    if ( !Update(d,n) ) return 0; 
    return Final();
}

template<hash_type ht, class HC>
inline int Hash<ht,HC>::From( const string& str )
{
    return From(reinterpret_cast<const unsigned char *>(str.c_str()),str.length());
}

template<hash_type ht, class HC>
inline int Hash<ht,HC>::Update( const void* data, unsigned long len )/*{{{*/
{
    return EVP_DigestUpdate(&mdctx,data,len);
}/*}}}*/

template<hash_type ht, class HC>
inline int Hash<ht,HC>::Update( const string& str )/*{{{*/
{
    return Update(str.c_str(),str.length());
}/*}}}*/

template<hash_type ht, class HC>
inline int Hash<ht,HC>::Reset( void )/*{{{*/
{
    if ( !EVP_MD_CTX_cleanup(&mdctx) ) return 0;
    return EVP_DigestInit(&mdctx, get_digest());
}/*}}}*/

template<hash_type ht, class HC>
inline Hash<ht,HC>::Hash( )/*{{{*/
{
    if ( hashcode.getHashsize() != static_cast<size_t>(EVP_MD_size(get_digest())) )
    {
//	cout << "got " << hashcode.getHashsize() << " wanted " << static_cast<size_t>(EVP_MD_size(get_digest()))
//	    << endl;
	THROW( iwlogic_error,("Invalid HashCode size for requested digest"));
    }
    EVP_DigestInit(&mdctx, get_digest());
}/*}}}*/

template<hash_type ht, class HC>
inline Hash<ht,HC>::~Hash( )/*{{{*/
{
     EVP_MD_CTX_cleanup(&mdctx);
}/*}}}*/

template<hash_type ht, class HC>
inline string Hash<ht,HC>::asHex( void ) const/*{{{*/
{
    return hashcode.asHex();
}/*}}}*/

template<hash_type ht, class HC>
inline const unsigned char * Hash<ht,HC>::theHash( void ) const/*{{{*/
{
    return hashcode.code();
}/*}}}*/

template<hash_type ht, class HC>
inline const EVP_MD * Hash<ht,HC>::get_digest( void ) const/*{{{*/
{
    switch(ht)
    {
	case md2:
	    return EVP_md2();
	case md4:
	    return EVP_md4();
	case md5:
	    return EVP_md5();
	case sha:
	    return EVP_sha();
	case sha1:
	    return EVP_sha1();
#ifndef OPENSSL_NO_MDC2
	case mdc2:
	    return EVP_mdc2();
#endif
	case ripemd160:
	    return EVP_ripemd160();
	default:
	    THROW( invalidargument_error,("No Such Digest", ht));
    };
}/*}}}*/

typedef Hash<md2,MD2HashCode> MD2Hash;
typedef Hash<md4,MD4HashCode> MD4Hash;
typedef Hash<md5,MD5HashCode> MD5Hash;
typedef Hash<sha,SHAHashCode> SHAHash;
typedef Hash<sha1,SHA1HashCode> SHA1Hash;
#ifndef OPENSSL_NO_MDC2
typedef Hash<mdc2,MDC2HashCode> MDC2Hash;
#endif
typedef Hash<ripemd160,RipeMD160HashCode> RipeMD160Hash;

}

#endif
