/* iniconfig.cc
****************************************************************************************************************
****************************************************************************************************************

    Copyright (C) 2020, 2021, 2026 Askar Almatov

    This file is part of Trafficmeter.

    Trafficmeter 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, either version 3 of the License, or (at your
    option) any later version.

    Trafficmeter 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 Trafficmeter. If not, see
    <http://www.gnu.org/licenses/>.
*/

#include <cctype>
#include <fstream>

#include "iniconfig.h"

using namespace trafficmeter;

using std::ifstream;
using std::string;
using std::vector;

constexpr const char*   WHITESPACE_CHAR_    = " \t\f\v\r\a";
constexpr const char*   COMMENT_CHAR_       = "#;";
constexpr const char*   DELIMITER_CHAR_     = "=:";

/**************************************************************************************************************/
static string
toLowerCopy_( const string& str )
{
    string  lower;

    lower.reserve( str.capacity() );

    for ( auto c : str )
    {
        lower.push_back( tolower(c) );
    }

    return std::move( lower );
}

/**************************************************************************************************************/
static string&
trim_( string& str )
{
    auto  last = str.find_last_not_of( WHITESPACE_CHAR_ );

    if ( last == string::npos )
    {
        str.clear();
    }
    else
    {
        str.erase( last + 1 ).erase( 0, str.find_first_not_of(WHITESPACE_CHAR_) );
    }

    return str;
}
    
/**************************************************************************************************************/
static string
getLine_( ifstream& input )
{
    string  line;

    getline( input, line );

    auto  comment = line.find_first_of( COMMENT_CHAR_ );

    if ( comment != string::npos )
    {
        line.erase( comment );
    }

    return std::move( trim_(line) );
}

/**************************************************************************************************************/
IniConfig::IniConfig( const string& section )
{
    if ( !section.empty() )
    {
        section_ += "[" + toLowerCopy_(section) + "]";
    }
}
/**************************************************************************************************************/
const string&
IniConfig::getValue( const string& key ) const
{
    static string   zero;
    auto            found( dictionary_.find(toLowerCopy_(key)) );

    return ( found != dictionary_.end() )? found->second : zero;
}

/**************************************************************************************************************/
vector<string>
IniConfig::getList( const string& key ) const
{
    vector<string>      tokens;
    string              value( getValue(key) );
    string              token;
    string::size_type   tokenPos( 0 );

    while ( true )
    {
        string::size_type   tokenEnd = value.find( ',', tokenPos );

        token.append( value, tokenPos, tokenEnd - tokenPos );
        trim_( token );

        if ( !token.empty() )
        {
            tokens.push_back( token );
        }

        if ( tokenEnd == string::npos )
        {
            break;
        }

        tokenPos = tokenEnd + 1;
        token.clear();
    }

    return std::move( tokens );
}

/**************************************************************************************************************/
bool
IniConfig::hasKey( const string& key ) const
{
    return dictionary_.find( toLowerCopy_(key) ) != dictionary_.end();
}

/**************************************************************************************************************/
bool
IniConfig::readFile( const Path& configFile )
{
    ifstream  input( configFile );

    if ( !input )
    {
        return false;
    }

    dictionary_.clear();

    if ( !section_.empty() )
    {
        while ( input.good() && toLowerCopy_(getLine_(input)) != section_ )
        {
        }
    }

    string  key;
    string  value;
    auto    dlm = string::npos;

    while ( input.good() )
    {
        string&&    line = getLine_( input );

        if ( !line.empty() )
        {
            if ( line.front() == '[' )
            {
                break;
            }

            if ( dlm == string::npos )
            {
                dlm = line.find_first_of( DELIMITER_CHAR_ );
                key.append( line, 0, dlm );

                if ( dlm != string::npos )
                {
                    value.append( line, dlm );
                    value.erase( 0, 1 );
                }
            }
            else
            {
                value.append( line );
            }

            if ( !value.empty() && value.back() == '\\' )
            {
                value.pop_back();
                continue;
            }
        }

        trim_( key );
        trim_( value );

        if ( !key.empty() )
        {
            dictionary_.emplace( toLowerCopy_(key), value );
        }

        key.clear();
        value.clear();
        dlm = string::npos;
    }

    return true;
}

