/**
   author: stephan@wanderinghorse.net
   based on code by Rusty Ballinger (bozo@users.sourceforge.net)
   license: GPL
*/
#include "EPropertyList.h"
#include <stdlib.h>
#include <stdio.h>
#include <iostream>
#include <fstream>

#include "EKeyValueParser.h"
#include "EStringTokenizer.h"

EPropertyList::TrueMap EPropertyList::trues;
EPropertyList::CharBoolMap EPropertyList::commentChars;

#define NUMBER_BUFFER_SIZE 128
#define INIT_NUMBER_BUFFER if( ! this->number_buffer ) {this->number_buffer = (char *)malloc( NUMBER_BUFFER_SIZE );}
#define FREE_NUMBER_BUFFER     if( this->number_buffer ) free( this->number_buffer );

ostream &
operator<<(ostream &os, const EPropertyList &obj)
{
    os << obj.toString();
    return os;
}
std::string &
operator<<(std::string &os, const EPropertyList &obj)
{
    os += obj.toString();
    return os;
}

std::string
EPropertyList::operator[] ( const string & key ) const
{ return this->getString( key ); }


EPropertyList::EPropertyList( const std::string &name )
{
  init( name );
  return ;
}

EPropertyList::~EPropertyList()
{
        if( this->saveAtDestruction() )
        {
                this->save();
        }
        FREE_NUMBER_BUFFER;
}
void
EPropertyList::init( std::string fn )
{
    this->number_buffer = 0;
    this->name = fn;
    this->m_saveAtDtor = false;
    static bool inited = false;
    if( !inited && ( inited = true) )
    {
        trues["true"] = true; trues["TRUE"] = true; trues["True"] = true;
        trues["yes"] = true; trues["YES"] = true; trues["Yes"] = true;
        trues["y"] = true; trues["Y"] = true; trues["t"] = true; trues["T"] = true;
        trues["1"] = true;
		// Don't set false values in 'trues'. We will check for true based on whether
		// trues.find() finds something, not it's actually value. I do this because
		// I remember the problems Rusty had in doing something similar in his ConfigFile
		// object, and I don't wanna have to repeat his workaround.
        commentChars[';'] = true;
        commentChars['#'] = true;
    }
}

const std::string &
EPropertyList::getName() const
{
  return this->name;
}

void
EPropertyList::setName( const std::string &name )
{
  this->name = name;
}

bool
EPropertyList::load( std::string fn, bool blanksToFalse )
{
    if( fn.empty() )
    {
        if( name.empty() ) return false;
        else fn = name;
    }
    else
    {
        name = fn;
    }
    //CERR << "loading [" << name << "]" << endl;
    ifstream ifs( name.c_str() );
    if( !ifs ) return false;
    bool b = this->load( ifs, blanksToFalse );
    ifs.close();
    return b;
}

bool
EPropertyList::load( std::istream &istr, bool blanksToFalse )
{
    std::string key;
    std::string val;
    std::string str;
    EKeyValueParser kvp;
    bool parsed = false;
    static int buffsize = 4 * 1024;
    char *buffer = (char *) malloc( buffsize );
    while( !istr.eof() )
    {
        istr.getline( buffer, buffsize, '\n' );
	str = string(buffer);
        commentIt = commentChars.find( str[0] );
        if( commentIt != commentChars.end() )
	{
	    order.push_back( str ); // add comment line to our list
	    continue;
	}
        // todo: add support for \-terminated multi-lines.
        parsed = kvp.parse( str );
        if( ! parsed ) continue;
        key = kvp.key();
        val = kvp.value();
        if( val.empty() < 1 && blanksToFalse ) set( std::string(key), std::string("false") );
        else set( key, val );
    }
    free( buffer );
    return true;
}


int
EPropertyList::count() const
{
        int c = 0;
        EPropertyList::const_iterator beg = this->begin();
        EPropertyList::const_iterator theend = this->end();
        while( beg != theend ) { ++c; ++beg; }
        return c;
}

bool
EPropertyList::save( std::string fn  )
{
    if( fn.length() > 0 )
    {
        name = fn;
    }
    if( name.length() == 0 ) return false;
    //LIBE_DEBUG << "EPropertyList::save(" << fn << ")" << endl;
    // todo: add optional support for the zip stream?
    ofstream of( name.c_str() );
    if( !of ) return false;
    of << toString() << endl;
    of.close();
    return true;
}

void
EPropertyList::set( const std::string &key, bool val ) { setBool( key, val ); }
void
EPropertyList::setBool( const std::string &key, bool val )
{
    std::string s = val ? (const char *)"true" : (const char *)"false";
    set( key, s );
    return;
}

bool
EPropertyList::getBool( const std::string &key, bool defaultVal ) const
{
  std::string s = this->getString( key, defaultVal?"true":"false" );
  return EPropertyList::boolVal( s );
}

// static 
bool
EPropertyList::boolVal( const std::string &key )
{
    static bool inited = false;
    if( !inited && (inited=true) )
    {
	EPropertyList foo; // initialize trues map. lame, eh?
    }
    TrueMap::const_iterator ti = trues.find( key );
    return ( ! (ti == trues.end()) );
}

int
EPropertyList::getInt( const std::string &key, int defaultVal ) const
{
    INIT_NUMBER_BUFFER;
    sprintf( this->number_buffer, "%d", defaultVal );
    std::string s = getString( key, std::string(this->number_buffer) );
    return atoi( s.c_str() );
}

void
EPropertyList::set( const std::string &key, int val ) { setInt( key, val ); }
void
EPropertyList::setInt( const std::string &key, int val )
{
    INIT_NUMBER_BUFFER;
    sprintf( this->number_buffer, "%d", val );
    set( key, std::string(this->number_buffer) );

    return;
}

std::string
EPropertyList::getString( const std::string &key, const std::string &defaultVal ) const
{
// DO NOT call LIBE_{DEBUG,VERBOSE} from here!
    EPropertyList::const_iterator citer = map.find( key );
    if(citer != map.end()) return (*citer).second;
    return defaultVal;
}

void
EPropertyList::set( const std::string &key, std::string val ) { setString( key, val ); }
void
EPropertyList::setString( const std::string &key, std::string val )
{
    if( key.length() < 1 ) return;
	bool b = isSet( key );
    map[key] = val;
    if( !b ) order.push_back( key );
    return;
}

double
EPropertyList::getDouble( const std::string &key, double defaultVal ) const
{
    INIT_NUMBER_BUFFER;
    sprintf( this->number_buffer, "%f", defaultVal );
    std::string s = getString( key, std::string(this->number_buffer) );
    double d = strtod( s.c_str(), NULL );
    return d;
}

void
EPropertyList::set( const std::string &key, double val ) { setDouble( key, val ); }
void
EPropertyList::setDouble( const std::string &key, double val )
{
    INIT_NUMBER_BUFFER;
    sprintf( this->number_buffer, "%f", val );
    set( key, std::string(this->number_buffer) );
    return;
}

bool
EPropertyList::isSet( const std::string &key ) const
{
    EPropertyList::iterator iter;
    iter = ((EPropertyList *)this)->map.find( key );
    return( iter == map.end() ? false : true );
}
bool
EPropertyList::unset( const std::string &key )
{
    EPropertyList::iterator iter;
    iter = map.find( key );
    if( iter == map.end() ) return false;
    map.erase( iter );
    string_list::iterator oit = this->order.begin();
    string_list::iterator oend = this->order.end();
    while( oit != oend )
    {
            if( (*oit) != key ) { ++oit; continue; }
            this->order.erase( oit );
            break;
    }
    return true;
}
bool
EPropertyList::clear()
{
    if( map.empty() ) return false;
    map.erase( map.begin(),map.end() );
    return true;
}


EPropertyList::StringStringMap &
EPropertyList::getMap()
{
    return map;
}

EPropertyList::iterator
EPropertyList::begin()
{ return this->map.begin(); }

EPropertyList::const_iterator
EPropertyList::begin() const
{ return this->map.begin(); }

EPropertyList::iterator
EPropertyList::end()
{ return this->map.end(); }

EPropertyList::const_iterator
EPropertyList::end() const
{ return this->map.end(); }


EPropertyList::iterator
EPropertyList::find( const std::string &key )
{
    return map.find( key );
}


bool
EPropertyList::setIfNot( const std::string &key, bool val )
{
    bool isset = isSet( key );
    if( !isset ) set( key, val );
    return !isset;
}

bool
EPropertyList::setIfNot( const std::string &key, int val )
{
    bool isset = isSet( key );
    if( !isset ) set( key, val );
    return !isset;
}

bool
EPropertyList::setIfNot( const std::string &key, std::string val )
{
    bool isset = isSet( key );
    if( !isset ) set( key, val );
    return !isset;
}

bool
EPropertyList::setIfNot( const std::string &key, double val )
{
    bool isset = isSet( key );
    if( !isset ) set( key, val );
    return !isset;
}


int
EPropertyList::merge( EPropertyList &src, EPropertyList &dest )
{
    if( &src == &dest || src.count() == 0 )
    {
            //CERR << "merge() source and destinaion EPropertyList objects are the same! Ignoring!" << endl;
	return -1;
    }

    // todo: add += operator which works the same.
    EPropertyList::iterator iter = src.begin();
    std::string key;
    std::string val;
    int count = 0;
    while( iter != src.end() )
    {
	++count;
	key = (*iter).first;
	dest.setString( key, src.getString( key, (*iter).second ) );
	++iter;
    }
    return count;
}

std::string
EPropertyList::toString() const
{
    string_list &theorder = ((EPropertyList *)this)->order; // got that?
    StringStringMap &themap = ((EPropertyList *)this)->map;
    // i can justify the above casts because we're not manipulating any useful structure,
    // even though we are adjusting cursors and such.
    std::string str;
    EPropertyList::iterator iter;
    iter = ((EPropertyList *)this)->map.begin();
    std::string key;
    std::string pre;
    string_list::const_iterator orderIt;
    orderIt = theorder.begin();
    while( orderIt != theorder.end() )
    {
        //CERR << "EPropertyList::toString(): " << (*orderIt) << endl;
        iter = themap.find( (*orderIt) );
        if( iter != themap.end() )
        {
            str += (*iter).first;
            str += "=";
            str += this->getString( (*iter).first, (*iter).second );
        }
	else
	{
	    str += (*orderIt);
	}
	str += '\n';
        ++orderIt;
    }
    return str;
}




