/*****************************************************************************
 * Author:   Valient Gough <vgough@pobox.com>
 *
 *****************************************************************************
 * Copyright (c) 2002-2004, Valient Gough
 *
 * This library is free software; you can distribute it and/or modify it under
 * the terms of the GNU General Public License (GPL), as published by the Free
 * Software Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * 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 GPL in the file COPYING for more
 * details.
 *
 */

#include "Cipher.h"
#include "Interface.h"
#include "Range.h"

#include <map>
#include <list>
#include <string>
#include <iostream>

using namespace std;
using namespace rel;

struct CipherAlg
{
    bool hidden;
    Cipher::CipherConstructor constructor;
    string description;
    Interface iface;
    Range keyLength;
    Range blockSize;
};

typedef multimap< string, CipherAlg> CipherMap_t;
static CipherMap_t *gCipherMap = NULL;

std::list<Cipher::CipherAlgorithm> 
Cipher::GetAlgorithmList( bool includeHidden )
{
    list<CipherAlgorithm> result;
    
    if(!gCipherMap)
	return result;

    CipherMap_t::const_iterator it;
    CipherMap_t::const_iterator mapEnd = gCipherMap->end();
    for(it = gCipherMap->begin(); it != mapEnd; ++it)
    {
	if(includeHidden || !it->second.hidden)
	{
	    CipherAlgorithm tmp;
	    tmp.name = it->first;
	    tmp.description = it->second.description;
	    tmp.iface = it->second.iface;
	    tmp.keyLength = it->second.keyLength;
	    tmp.blockSize = it->second.blockSize;

	    result.push_back( tmp );
	}
    }

    return result;
}

bool Cipher::Register(const char *name, const char *description,
	const Interface &iface, CipherConstructor fn, bool hidden)
{
    Range keyLength(-1,-1,1);
    Range blockSize(-1,-1,1);
    return Cipher::Register( name, description, iface, 
	    keyLength, blockSize, fn, hidden );
}

bool Cipher::Register(const char *name, const char *description,
	const Interface &iface, const Range &keyLength, 
	const Range &blockSize, 
	CipherConstructor fn, bool hidden)
{
    if(!gCipherMap)
	gCipherMap = new CipherMap_t;

    CipherAlg ca;
    ca.hidden = hidden;
    ca.constructor = fn;
    ca.description = description;
    ca.iface = iface;
    ca.keyLength = keyLength;
    ca.blockSize = blockSize;

    gCipherMap->insert( make_pair(string(name), ca) );
    return true;
}

Ptr<Cipher> Cipher::New(const string &name, int keyLen)
{
    Ptr<Cipher> result;

    if(gCipherMap)
    {
	CipherMap_t::const_iterator it = gCipherMap->find( name );
	if(it != gCipherMap->end())
	{
	    CipherConstructor fn = it->second.constructor;
	    // use current interface..
	    result = (*fn)( it->second.iface, keyLen );
	}
    }

    return result;
}

Ptr<Cipher> Cipher::New( const Interface &iface, int keyLen )
{
    Ptr<Cipher> result;
    if(gCipherMap)
    {
	CipherMap_t::const_iterator it;
	CipherMap_t::const_iterator mapEnd = gCipherMap->end();

	for(it = gCipherMap->begin(); it != mapEnd; ++it)
	{
	    // TODO: we should look for the newest implementation..
	    if( it->second.iface.implements( iface ) )
	    {
		CipherConstructor fn = it->second.constructor;
		// pass in requested interface..
		result = (*fn)( iface, keyLen );

		// if we're not going to compare the options, then just stop
		// now..
		break;
	    }
	}
    }

    return result;
}

Cipher::Cipher()
{
}

Cipher::~Cipher()
{
}

unsigned int Cipher::MAC_32( const unsigned char *src, int len, 
	const CipherKey &key, uint64_t *chainedIV ) const
{
    uint64_t mac64 = MAC_64( src, len, key, chainedIV );

    unsigned int mac32 = ((mac64 >> 32) & 0xffffffff) ^ (mac64 & 0xffffffff);

    return mac32;
}

unsigned int Cipher::MAC_16( const unsigned char *src, int len, 
	const CipherKey &key, uint64_t *chainedIV ) const
{
    uint64_t mac64 = MAC_64( src, len, key, chainedIV );

    unsigned int mac32 = ((mac64 >> 32) & 0xffffffff) ^ (mac64 & 0xffffffff);
    unsigned int mac16 = ((mac32 >> 16) & 0xffff) ^ (mac32 & 0xffff);

    return mac16;
}

bool Cipher::nameEncode( unsigned char *data, int len,
	uint64_t iv64, const CipherKey &key ) const
{
    return streamEncode( data, len, iv64, key );
}

bool Cipher::nameDecode( unsigned char *data, int len,
	uint64_t iv64, const CipherKey &key ) const
{
    return streamDecode( data, len, iv64, key );
}
