#ifndef ITERHASH_H
#define ITERHASH_H

#include "cryptlib.h"
#include "misc.h"

/* The following classes are explicitly instantiated in iterhash.cpp

    IteratedHash<word32>
    IteratedHash<word64>    // #ifdef WORD64_AVAILABLE
*/

template <class T> class IteratedHash : public virtual HashModule
{
public:
    IteratedHash(unsigned int blockSize, unsigned int digestSize);
    ~IteratedHash();
    void Update(const byte *input, unsigned int length);

    typedef T HashWordType;

protected:
    void PadLastBlock(unsigned int lastBlockSize, byte padFirst=0x80);
    virtual void Init() =0;
    virtual void HashBlock(const T *input) =0;

    unsigned int blockSize;
    word32 countLo, countHi;    // 64-bit bit count
    SecBlock<T> data;           // Data buffer
    SecBlock<T> digest;         // Message digest
};

template <class T> IteratedHash<T>::IteratedHash(unsigned int blockSize, unsigned int digestSize)
    : blockSize(blockSize), data(blockSize/sizeof(T)), digest(digestSize/sizeof(T))
{
}

template <class T> IteratedHash<T>::~IteratedHash()
{
}

template <class T> void IteratedHash<T>::Update(const byte *input, unsigned int len)
{
    word32 tmp = countLo;
    if ((countLo = tmp + ((word32)len << 3)) < tmp)
        countHi++;             // Carry from low to high
    countHi += len >> 29;

    assert((blockSize & (blockSize-1)) == 0);   // blockSize is a power of 2
    unsigned int num = (unsigned int)(tmp >> 3) & (blockSize-1);

    if (num != 0)
    {
        if ((num+len) >= blockSize)
        {
            memcpy((byte *)data.ptr+num, input, blockSize-num);
            HashBlock(data);
            input += (blockSize-num);
            len-=(blockSize - num);
            num=0;
            // drop through and do the rest
        }
        else
        {
            memcpy((byte *)data.ptr+num, input, len);
            return;
        }
    }

    // we now can process the input data in blocks of blockSize
    // chars and save the leftovers to this->data.
    if (len >= blockSize)
    {
        if ((ptr_size_type)input % sizeof(T))   // test for alignment
            do
            {   // copy input first if it's not aligned correctly
                memcpy(data, input, blockSize);
                HashBlock(data);
                input+=blockSize;
                len-=blockSize;
            } while (len >= blockSize);
        else
            do
            {
                HashBlock((T *)input);
                input+=blockSize;
                len-=blockSize;
            } while (len >= blockSize);
    }

    memcpy(data, input, len);
}

template <class T> void IteratedHash<T>::PadLastBlock(unsigned int lastBlockSize, byte padFirst)
{
    unsigned int num = (unsigned int)(countLo >> 3) & (blockSize-1);
    assert(num < blockSize);
    ((byte *)data.ptr)[num++]=padFirst;
    if (num <= lastBlockSize)
        memset((byte *)data.ptr+num, 0, lastBlockSize-num);
    else
    {
        memset((byte *)data.ptr+num, 0, blockSize-num);
        HashBlock(data);
        memset(data, 0, lastBlockSize);
    }
}

// provide empty definitions to avoid instantiation warnings
//template <class T> void IteratedHash<T>::Init() {}
//template <class T> void IteratedHash<T>::HashBlock(const T *input) {}

#endif
