/*
 * The Cryptonit security software suite is developped by IDEALX
 * Cryptonit Team (http://IDEALX.org/ and http://cryptonit.org).
 *
 * Copyright 2003-2006 IDEALX
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License version
 * 2 as published by the Free Software Foundation.
 * 
 * This program 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 this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301, USA. 
 *
 * In addition, as two special exceptions:
 *
 * 1) IDEALX S.A.S gives permission to:
 *  * link the code of portions of his program with the OpenSSL library under
 *    certain conditions described in each source file
 *  * distribute linked combinations including the two, with respect to the
 *    OpenSSL license and with the GPL
 *
 * You must obey the GNU General Public License in all respects for all of the
 * code used other than OpenSSL. If you modify file(s) with this exception,
 * you may extend this exception to your version of the file(s), but you are
 * not obligated to do so. If you do not wish to do so, delete this exception
 * statement from your version, in all files (this very one along with all
 * source files).

 * 2) IDEALX S.A.S acknowledges that portions of his sourcecode uses (by the
 * way of headers inclusion) some work published by 'RSA Security Inc.'. Those
 * portions are "derived from the RSA Security Inc. PKCS #11Cryptographic
 * Token Interface (Cryptoki)" as described in each individual source file.
 */

#include "ConfigDatabase.hh"

#include <ctype.h> // Method toupper
#include <algorithm> //find_first_of

#include <iostream>
#include <fstream>

#include <stdlib.h> // malloc
#include <stdio.h> // snprintf
#include <string.h> // strlen

extern "C" {
    int snprintf (char*, size_t, const char*, ...);
}

namespace Cryptonit
{

    /** Append integer val 'val' to string 'str'.
     *  Returns the result string.
     */
    std::string appendVal( const char* str, long int val ) 
    {
	char* buffer;

	size_t len = strlen(str) + 16 + 1; // 16 stands for long int digits

	buffer = (char*) malloc( len * sizeof(char));

	if( buffer == NULL ) {
#ifdef DEBUG
 	    std::cerr << "Cannot allocate memory for section buffer" << std::endl;
#endif 
	    return std::string("");
	}

	snprintf( buffer, len, "%s%ld", str, val );

	std::string s( buffer );
	free( buffer );

	return s;
    }


    std::string toUpper( const std::string str )
    {
	std::string s( str );
	const std::string mins("abcdefghijklmnopqrstuvwxyz");
	std::string::size_type pos = 0;
	
	while( (pos = s.find_first_of( mins, pos)) != std::string::npos )
	    s[pos] = toupper( s[pos] );

	return s;
    }


    AttributeType getAttributeTypeFromString( const std::string str )
    {
	std::string s = toUpper( str );
	
	if( s == "INTEGER" ) return AttributeInteger;
	if( s == "LONG" ) return AttributeLong;
	if( s == "FLOAT" ) return AttributeFloat;
	if( s == "STRING" ) return AttributeString;
	if( s == "BINARY" ) return AttributeBinary;
	if( s == "BASE64" ) return AttributeBase64;
	else return AttributeString;
    }


    const std::string getStringFromAttributeType( const AttributeType t )
    {
	if( t == AttributeInteger ) return "INTEGER";
	if( t == AttributeLong ) return "LONG";
	if( t == AttributeFloat ) return "FLOAT";
	if( t == AttributeString ) return "STRING";
	if( t == AttributeBinary ) return "BINARY";
	if( t == AttributeBase64 ) return "BASE64";
	else return "STRING";
    }


CONF* ConfigDatabase::initOpenSSLNCONF( const std::string filename, const std::string method )
{

    if( current_conf != NULL && filename != "" )
	return NULL;


    if( method == "WIN32" )
	current_conf = NCONF_new( NCONF_WIN32() );
    else
	current_conf = NCONF_new( NCONF_default() );


    if( !NCONF_load(current_conf, filename.c_str(), &current_error_line) ) {
	if( current_error_line == 0 ) {
#ifdef DEBUG
 	    std::cerr << "Error opening configuration file " << filename << std::endl;
#endif 
	    current_conf = NULL;
	    return NULL;
	}
	else {
#ifdef DEBUG
 	    std::cerr << "Error in " << filename << "  on line " << current_error_line << std::endl;
 	    std::cerr << "Errors parsing configuration file" << std::endl;
#endif 
	    return NULL;
	}
    }
    return current_conf;
}



bool ConfigDatabase::addField( const std::string section_name, 
					     const std::string entry_name )
{
    char* field_name;

    // Get field name, section: entryX_fieldX (ENTRY_BASE + I + "_" + FIELD_BASE + J )
    if( !(field_name = NCONF_get_string(current_conf, section_name.c_str(), FIELD_NAME)) ) {
#ifdef DEBUG
 	std::cerr << "Error finding \"" << FIELD_NAME << "\" in [" << section_name << "]" << std::endl;
 	//std::cerr << "Error finding field #" << i << " name." << std::endl;
#endif 
	return false;
    }

    
    long int nbvalues = 0;

    // Get the total number of values, section: entryX_fieldX
    if( !(current_error_line = NCONF_get_number_e(current_conf, section_name.c_str(), NB_VALUE, &nbvalues)) ) {
#ifdef DEBUG
 	std::cerr << "Error finding \"" << NB_VALUE << "\" in [" << section_name << "]" << std::endl;
 	std::cerr << "Error finding total number of values." << std::endl;
#endif 
	return false;
    }


    // Scanning for all values contained into the field section
    for( long int i = 1 ; i <= nbvalues ; i++ ) {

	// Retrieving value type, section: entryX_fieldX
	const std::string value_type_name = appendVal( VALUE_TYPE_BASE, i );
	char* value_type;
	
	if( !( value_type = NCONF_get_string(current_conf, section_name.c_str(), value_type_name.c_str())) ) {
#ifdef DEBUG
 	    std::cerr << "Warning, can't find \"" << value_type_name << "\" in [" << section_name << "]" << std::endl;
 	    std::cerr << "Assuming type STRING for value #" << i << " in " << section_name << std::endl;
#endif 
	}


	// Retrieving value data, section: entryX_fieldX
	char* value_data;
	const std::string value_data_name = appendVal( VALUE_BASE, i );

	if( !(value_data = NCONF_get_string(current_conf, section_name.c_str(), value_data_name.c_str())) ) {
#ifdef DEBUG
 	    std::cerr << "Error finding \"" << value_data_name << "\" in [" << section_name << "]" << std::endl;
 	    std::cerr << "Error finding data for \"" << value_data_name << "\" in " << section_name << std::endl;
#endif 
	    return false;
	}


	
	// Appends the value in the internal hash map, creating if necessary
	// the new entry and the new attribute.
	if( ! append( entry_name, field_name, value_data, 
		      (value_type != NULL ? getAttributeTypeFromString(std::string(value_type)) 
		       : getAttributeTypeFromString(std::string("")) ) ) )
	    return false;
	
    }

    return true;
}


bool ConfigDatabase::addEntry( const std::string section_name, const std::string entry_name )
{
    long int nbfield = 0;

    // Get the total number of fields, section: entryX (ENTRY_BASE + I)
    if( !(current_error_line = NCONF_get_number_e(current_conf, section_name.c_str(), NB_FIELD, &nbfield)) ) {
#ifdef DEBUG
 	std::cerr << "Error finding \"" << NB_FIELD << "\" in [" << section_name << "]" << std::endl;
 	std::cerr << "Error finding total number of fields." << std::endl;
#endif 
	return false;
    }

    // Scanning for all field contained into the entry section
    for( long int i = 1 ; i <= nbfield ; i++ ) {
 	const std::string field = appendVal( FIELD_BASE, i);
	    
 	if( addField( section_name + ENTRY_FIELD_SEPARATOR + field , entry_name ) == false ) {
#ifdef DEBUG
 	    std::cerr << "Error occured while adding field \"" << field << "\"." << std::endl;
#endif 
	    return false;
	}
    }
    
    return true;
}


ConfigDatabase::ConfigDatabase()
{
    current_conf = NULL;
    current_error_line = 0;
}
 

ConfigDatabase::ConfigDatabase(const std::string uri)
{
    current_error_line = 0;
    current_conf = NULL;

    // Strip the "config://" part if presents.
    std::string params( uri );
    unsigned int i = params.find("config://");
    if( i != std::string::npos)
	params.erase(i, strlen("config://") );

    current_filename = params;
}


ConfigDatabase::~ConfigDatabase()
{
    if( current_conf )
	NCONF_free( current_conf );
    clear();
}

/*
static void dump_value(CONF_VALUE *a, BIO* out)
{
    if( a->name )
 	std::cout << a->section << " / " << a->name << "=" << a->value << std::endl;
}
static IMPLEMENT_LHASH_DOALL_ARG_FN(dump_value, CONF_VALUE*, BIO* )
static int custom_dump( const CONF* conf, BIO* out )
{
    lh_doall_arg(conf->data, LHASH_DOALL_ARG_FN(dump_value), out);
    return 1;
}
*/


bool ConfigDatabase::read( const std::string params[] )
{
    std::string filename = params[0];

    if( filename == "" ) filename = current_filename;

    if( filename == "" && current_filename == "" )
	return false;
    

    if( initOpenSSLNCONF( filename, params[1] ) != NULL ) {

	/* current_conf->meth->dump = custom_dump; */

	// Retrieving the total number of entries, section: GLOBAL
	long int nbentry = 0;

	if( !(current_error_line = NCONF_get_number_e(current_conf, NULL, NB_ENTRY, &nbentry) ) )  {
#ifdef DEBUG
 	    std::cerr << "Error finding \"" << NB_ENTRY << "\" in global section." << std::endl;
 	    std::cerr << "Error finding total number of entries." << std::endl;
#endif 
	    return false;
	}

	// Iterating in all entries.
	for( long int i = 1 ; i <= nbentry ; i++ ) {
	    char* entry_name;
	    const std::string entry = appendVal( ENTRY_BASE, i);


	    // Get entry name, section: GLOBAL
	    if( !(entry_name = NCONF_get_string(current_conf, entry.c_str(), ENTRY_NAME)) ) {
#ifdef DEBUG
 		std::cerr << "Error finding \"" << ENTRY_NAME << "\" in [" << entry << "]." << std::endl;
 		std::cerr << "Error finding entry #" << i << " name." << std::endl;
#endif 
		return false;
	    }

	    
	    if( addEntry( entry, entry_name ) == false ) {
#ifdef DEBUG
 		std::cerr << "Error occured while adding entry \"" << entry << "\"." << std::endl;
#endif 
		return false;
	    }
	}

    return true;

    }
    else return false;
}


bool ConfigDatabase::commit( const std::string params[] )
{
    std::ofstream* out;

    if( params[0] != "" ) {
	out = new std::ofstream ( params[0].c_str(), std::ios::trunc | std::ios::binary );
    } else {
	out = new std::ofstream ( current_filename.c_str(), std::ios::trunc | std::ios::binary );
    }

    *out << NB_ENTRY << "=" << DirectoryService::getNbEntry() << std::endl;;
    *out << std::endl;

    DirectoryService::iterator entry = DirectoryService::begin();
    long int i = 1;

    while( entry != DirectoryService::end() ) {

	*out << std::endl;
	*out << std::endl;

	*out << "[" << ENTRY_BASE << i  << "]" << std::endl;
	*out << ENTRY_NAME << "=" << entry.first() << std::endl;
	*out << NB_FIELD << "=" << entry.second()->getNbAttribute() << std::endl;



	Entry::iterator attribute = entry.second()->begin();
	long int j = 1;

	while( attribute != entry.second()->end() ) {
 	    *out << std::endl;
	    
	    *out << "[" << ENTRY_BASE << i << ENTRY_FIELD_SEPARATOR << FIELD_BASE << j << "]" << std::endl;
	    *out << FIELD_NAME << "=" << attribute.first() << std::endl;
	    *out << NB_FIELD << "=" << attribute.second()->getNbValues() << std::endl;


	    Attribute::iterator value = attribute.second()->begin();
	    long int k = 1;
	    while( value != attribute.second()->end() ) {
		*out << VALUE_BASE << k << "=" << value << std::endl;
		*out << VALUE_TYPE_BASE << k << "=" << getStringFromAttributeType(attribute.second()->getType()) << std::endl;

		value++;
		k++;
	    }

	    attribute++;
	    j++;
	}

	entry++;
	i++;
    }

    out->close();
    delete out;

    return true;
}

} // Namespace
