/***********************************************************************************

    Copyright (C) 2007-2020 Ahmet Öztürk (aoz_2@yahoo.com)

    Parts of this file are loosely based on an example gcrypt program
    on http://punkroy.drque.net/

    This file is part of Lifeograph.

    Lifeograph 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.

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

***********************************************************************************/


#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <iomanip>

#include "helpers.hpp"
#include "strings.hpp"


namespace HELPERS
{

Error::Error( const Ustring& error_message )
{
    print_error( error_message );
}

#if LIFEOGRAPH_DEBUG_BUILD
unsigned Console::DEBUG_LINE_I( 0 );
#endif

// DATE ============================================================================================
std::string Date::s_format_order = "YMD";
char        Date::s_format_separator = '.';

Date::Date( const std::string& str_date )
{
    if( parse_string( &m_date, str_date ) != OK )
        m_date = NOT_SET;
}

// TODO: later...
//Date::Date( time_t t )
//{
//    struct tm* timeinfo = localtime( &t );
//    m_date = make_date( timeinfo->tm_year + 1900,
//                        timeinfo->tm_mon +1,
//                        timeinfo->tm_mday );
//    timeinfo->tm_hour = 0;
//    timeinfo->tm_min = 0;
//    timeinfo->tm_sec = 0;
//}

Glib::Date::Month
Date::get_month_glib() const
{
    switch( ( m_date & FILTER_MONTH ) >> 15 )
    {
        case 1:
            return Glib::Date::JANUARY;
        case 2:
            return Glib::Date::FEBRUARY;
        case 3:
            return Glib::Date::MARCH;
        case 4:
            return Glib::Date::APRIL;
        case 5:
            return Glib::Date::MAY;
        case 6:
            return Glib::Date::JUNE;
        case 7:
            return Glib::Date::JULY;
        case 8:
            return Glib::Date::AUGUST;
        case 9:
            return Glib::Date::SEPTEMBER;
        case 10:
            return Glib::Date::OCTOBER;
        case 11:
            return Glib::Date::NOVEMBER;
        case 12:
            return Glib::Date::DECEMBER;
        default:
            return Glib::Date::BAD_MONTH;
    }
}

Result
Date::parse_string( date_t* date, const Ustring& str_date )
{
    char c_cur;
    unsigned int num[ 4 ] = { 0, 0, 0, 0 };  // fourth int is for trailing spaces
    int i( 0 );

    for( unsigned j = 0; j < str_date.size(); j++ )
    {
        c_cur = str_date[ j ];
        switch( c_cur )
        {
            case '0': case '1': case '2': case '3': case '4':
            case '5': case '6': case '7': case '8': case '9':
                if( i > 2 )
                    return INVALID;
                num[ i ] *= 10;
                num[ i ] += ( c_cur - '0' );
                break;
            case ' ':
                if( num[ i ] > 0 )
                    i++;
                break;
            case '.':
            case '-':
            case '/':
                if( num[ i ] == 0 || i == 2 )
                    return INVALID;
                else
                    i++;
                break;
            default:
                return INVALID;
        }
    }

    if( num[ 2 ] ) // temporal
    {
        unsigned int year( 0 );
        unsigned int month( 0 );
        unsigned int day( 0 );

        if( num[ 0 ] > 31 && num[ 1 ] <= 12 && num[ 2 ] <= 31 ) // YMD
        {
            year = num[ 0 ];
            month = num[ 1 ];
            day = num[ 2 ];
        }
        else
        {
            if( num[ 0 ] <= 12 && num[ 1 ] <= 12 ) // both DMY and MDY possible
            {
                if( s_format_order[ 0 ] == 'M' )
                {
                    month = num[ 0 ];
                    day = num[ 1 ];
                }
                else
                {
                    day = num[ 0 ];
                    month = num[ 1 ];
                }
            }
            else if( num[ 0 ] <= 31 && num[ 1 ] <= 12 ) // DMY
            {
                month = num[ 1 ];
                day = num[ 0 ];
            }
            else if( num[ 0 ] <= 12 && num[ 1 ] <= 31 ) // MDY
            {
                month = num[ 1 ];
                day = num[ 0 ];
            }
            else
                return INVALID;

            year = num[ 2 ];

            if( year < 100 )
                year += ( year < 30 ? 2000 : 1900 );
        }

        if( year < YEAR_MIN || year > YEAR_MAX )
            return OUT_OF_RANGE;

        Date date_tmp( year, month, day );
        if( ! date_tmp.is_valid() ) // checks days in month
            return INVALID;

        if( date )  // pass NULL when just used for checking
            *date = date_tmp.m_date;
    }
// there is no failsafe way of determining if a string is temporal or ordinal.
// so support for ordinals is disabled for now.
//    else if( num[ 1 ] )   // ordinal
//    {
//        if( num[ 0 ] > ORDER_MAX || num[ 1 ] > ORDER_MAX )
//            return OUT_OF_RANGE;
//
//        if( date )  // pass NULL when just used for checking
//            *date = make_ordinal( true, num[ 0 ], num[ 1 ], num[ 2 ] );
//    }
    else
        return INVALID;

    return OK;
}

Ustring
Date::format_string( const date_t d, const std::string& format, const char separator )
{
    std::string result;

    if( d & FLAG_ORDINAL )
    {
        result += std::to_string( get_order_1st( d ) + 1 );
        if( get_order_2nd( d ) )
            result += ( "." + std::to_string( get_order_2nd( d ) ) );
        if( get_order_3rd( d ) )
            result += ( "." + std::to_string( get_order_3rd( d ) ) );
    }
    else
    {
        auto get_YMD = [ &d ]( char c ) -> unsigned int
            {
                switch( c )
                {
                    case 'Y':
                        return get_year( d );
                    case 'M':
                        return get_month( d );
                    case 'D':
                    default: // no error checking for now
                        return get_day( d );
                }
            };

        for( unsigned int i = 0; ; i++ )
        {
            const auto&& ymd{ get_YMD( format[ i ] ) };
            if( ymd < 10 ) result += '0';
            result += std::to_string( ymd );
            if( i == format.size() - 1 ) break;
            result += separator;
        }
    }
    return result;
}

Ustring
Date::format_string_dt( const time_t time )
{
    struct tm* timeinfo = localtime( &time );
    std::string result;

    result += format_string( make_from_ctime( timeinfo ) );
    result += ", ";
    if( timeinfo->tm_hour < 10 ) result += '0';
    result += std::to_string( timeinfo->tm_hour );
    result += ':';
    if( timeinfo->tm_min < 10 ) result += '0';
    result += std::to_string( timeinfo->tm_min );

    return result;
}

// date only
Ustring
Date::format_string_d( const time_t time )
{
    struct tm* timeinfo = localtime( &time );
    return format_string( make_from_ctime( timeinfo ) );
}

std::string
Date::get_year_str() const
{
    return std::to_string( get_year() );
}

Ustring
Date::get_month_str() const
{
    using namespace LIFEO;
    switch( get_month() )
    {
        case  1: return STR_DATE/SI::JANUARY;
        case  2: return STR_DATE/SI::FEBRUARY;
        case  3: return STR_DATE/SI::MARCH;
        case  4: return STR_DATE/SI::APRIL;
        case  5: return STR_DATE/SI::MAY;
        case  6: return STR_DATE/SI::JUNE;
        case  7: return STR_DATE/SI::JULY;
        case  8: return STR_DATE/SI::AUGUST;
        case  9: return STR_DATE/SI::SEPTEMBER;
        case 10: return STR_DATE/SI::OCTOBER;
        case 11: return STR_DATE/SI::NOVEMBER;
        default: return STR_DATE/SI::DECEMBER;
    }
}

unsigned int
Date::get_weekday( const date_t& date )
{
    // from wikipedia: http://en.wikipedia.org/wiki/Calculating_the_day_of_the_week
    const unsigned int year = get_year( date );
    const unsigned int century = ( year - ( year % 100 ) ) / 100;
    int c = 2 * ( 3 - ( century % 4 ) );
    int y = year % 100;
    y = y + floor( y / 4 );

    static const int t_m[] = { 0, 3, 3, 6, 1, 4, 6, 2, 5, 0, 3, 5 };

    int m = get_month( date ) - 1;
    int d = ( c + y + t_m[ m ] + get_day( date ) );

    if( m < 2 && is_leap_year( date ) )  // leap year!
        d += 6;

    return( d % 7 );
}

unsigned int
Date::get_yearday( const date_t& date )
{
    int result{ 0 };
    date_t d_m{ make( get_year( date ), 1, 1, 0 ) };

    for( unsigned int i = 1; i < 12; i++ )
    {
        if( i < get_month( date ) )
            result += get_days_in_month( d_m );
        else
            break;

        forward_months( d_m, 1 );
    }

    result += get_day( date );

    return result;
}

Ustring
Date::get_weekday_str() const
{
    struct tm ti;
    ti.tm_wday = get_weekday();

#ifdef _WIN32
    wchar_t buf[ 32 ];
    wcsftime( buf, 32, L"%A", &ti );
    return Ustring( convert_utf16_to_8( buf ) );
#else
    char buf[ 32 ];
    strftime( buf, 32, "%A", &ti );
    return Ustring( buf );
#endif
}

unsigned int
Date::get_days_in_month( const date_t d )
{
    switch( get_month( d ) )
    {
        case 4: case 6: case 9: case 11:
            return 30;
        case 2:
            return is_leap_year( d ) ? 29 : 28;
    }

    return 31;
}

time_t
Date::get_ctime( const date_t d )
{
    time_t t;
    time( &t );
    struct tm* timeinfo = localtime( &t );
    timeinfo->tm_year = get_year( d ) - 1900;
    timeinfo->tm_mon = get_month( d ) - 1;
    timeinfo->tm_mday = get_day( d );
    timeinfo->tm_hour = 0;
    timeinfo->tm_min = 0;
    timeinfo->tm_sec = 0;

    return( mktime( timeinfo ) );
}

void
Date::forward_months( date_t& d, unsigned int months )
{
    months += get_month( d );
    d &= FILTER_YEAR;   // isolate year
    const auto mod_months{ months % 12 };
    if( mod_months == 0 )
    {
        d += make_year( ( months / 12 ) - 1 );
        d |= 0x60000;  // make month 12
    }
    else
    {
        d += make_year( months / 12 );
        d |= make_month( mod_months );
    }

    d |= make_day( 1 );
}

void
Date::backward_months( date_t& d, unsigned int months )
{
    const int m_diff{ int( months ) - int( get_month( d ) ) };
    d &= FILTER_YEAR;   // isolate year
    if( m_diff < 0 )
        d |= make_month( -m_diff );
    else
    {
        d -= make_year( ( m_diff / 12 ) + 1 );
        d |= make_month( 12 - ( m_diff % 12 ) );
    }

    d |= make_day( get_days_in_month( d ) );
}

void
Date::backward_to_week_start( date_t& date )
{
    const auto wd{ get_weekday( date ) };
    if( wd > 0 )
        backward_days( date, wd );
}

void
Date::forward_days( date_t& date, unsigned int n_days )
{
    const auto day_new = get_day( date ) + n_days;
    const auto days_in_month{ get_days_in_month( date ) };

    if( day_new > days_in_month )
    {
        forward_months( date, 1 );    // sets day to 1, hence -1 below
        if( day_new > days_in_month + 1 )
            forward_days( date, day_new - days_in_month - 1 );
    }
    else
        set_day( date, day_new );
}

void
Date::backward_days( date_t& date, unsigned int n_days )
{
    const int day_new = get_day( date ) - n_days;

    if( day_new <= 0 )
    {
        backward_months( date, 1 );    // sets day to the last day of previous month
        if( day_new < 0 )
            backward_days( date, -day_new );
    }
    else
        set_day( date, day_new );
}

unsigned int
Date::calculate_days_between( const Date& date2 ) const
{
    // TODO: create own implementation
    Glib::Date gdate_begin( get_glib() );
    Glib::Date gdate_end( date2.get_glib() );
    return( gdate_begin.days_between( gdate_end ) );
}

unsigned int
Date::calculate_weeks_between( date_t date1, date_t date2 )
{
    unsigned int dist{ 0 };
    date_t date_former{ Date::get_pure( date1 < date2 ? date1 : date2 ) };
    date_t date_latter{ Date::get_pure( date1 < date2 ? date2 : date1 ) };

    backward_to_week_start( date_former );
    backward_to_week_start( date_latter );

    while( date_former < date_latter )
    {
        forward_days( date_former, 7 );
        dist++;
    }

    return dist;
}

unsigned int
Date::calculate_months_between( date_t date1, date_t date2 )
{
    int dist{ 12 * int( get_year( date2 ) - get_year( date1 ) ) };
    dist += ( get_month( date2 ) - get_month( date1 ) );

    return( dist < 0 ? -dist : dist );
}

int
Date::calculate_months_between_neg( date_t date1, date_t date2 )
{
    int dist{ 12 * int( get_year( date2 ) - get_year( date1 ) ) };
    dist += ( get_month( date2 ) - get_month( date1 ) );

    return dist;
}

// COLOR OPERATIONS ================================================================================
Color
contrast2( const Color& bg, const Color& c1, const Color& c2 )
{
    const float dist1{ get_color_diff( bg, c1 ) };
    const float dist2{ get_color_diff( bg, c2 ) };

    if( dist1 > dist2 )
        return c1;
    else
        return c2;
}

Color
midtone( const Color& c1, const Color& c2 )
{
    Color midtone;
    midtone.set_red_u( ( c1.get_red_u() + c2.get_red_u() ) / 2.0 );
    midtone.set_green_u( ( c1.get_green_u() + c2.get_green_u() ) / 2.0 );
    midtone.set_blue_u( ( c1.get_blue_u() + c2.get_blue_u() ) / 2.0 );
    midtone.set_alpha( 1.0 );
    return midtone;
}

Color
midtone( const Color& c1, const Color& c2, float ratio )
{
    Color midtone;
    midtone.set_red_u( ( c1.get_red_u() * ( 1.0 - ratio ) ) + ( c2.get_red_u() * ratio ) );
    midtone.set_green_u( ( c1.get_green_u() * ( 1.0 - ratio ) ) + ( c2.get_green_u() * ratio ) );
    midtone.set_blue_u( ( c1.get_blue_u() * ( 1.0 - ratio ) ) + ( c2.get_blue_u() * ratio ) );
    midtone.set_alpha( 1.0 );
    return midtone;
}

// FILE OPERATIONS =================================================================================
#ifdef _WIN32
std::string
get_exec_path()
{
    char lpFilename[ 1024 ];
    GetModuleFileName( NULL, lpFilename, 1024 );
    return( Glib::path_get_dirname( convert_locale_to_utf8( lpFilename ) ) );
}
#endif

std::ios::pos_type
get_file_size( std::ifstream& file )
{
   std::ios::pos_type size;
   const std::ios::pos_type currentPosition = file.tellg();

   file.seekg( 0, std::ios_base::end );
   size = file.tellg();
   file.seekg( currentPosition );

   return size;
}

bool
copy_file( const std::string& source_path, const std::string& target_path_root, bool f_unique )
{
    try
    {
        Glib::RefPtr< Gio::File > file_src = Gio::File::create_for_path( source_path );

        if( file_src )
        {
            std::string target_path( target_path_root );
            int index( 0 );

            while( f_unique && access( PATH( target_path ).c_str(), F_OK ) == 0 )
            {
                target_path = target_path_root;
                target_path += ( " (" + std::to_string( ++index ) + ")" );
            }

            Glib::RefPtr< Gio::File > file_dest = Gio::File::create_for_path( target_path );
            file_src->copy( file_dest, f_unique ? Gio::FILE_COPY_NONE : Gio::FILE_COPY_OVERWRITE );
        }
    }
    catch( ... )
    {
        return false;
    }
    return true;
}

bool
copy_file_suffix( const std::string& source_path, const std::string& suffix1, int suffix2,
                  bool flag_unique )
{
    try
    {
        Glib::RefPtr< Gio::File > file_src = Gio::File::create_for_path( source_path );

        if( file_src )
        {
            const std::string target_path_root( source_path + suffix1 );
            std::string target_path( target_path_root );
            if( suffix2 >= 0 )
                target_path += std::to_string( suffix2 );
            int index( 0 );

            while( flag_unique && access( PATH( target_path ).c_str(), F_OK ) == 0 )
            {
                target_path = target_path_root;
                if( suffix2 >= 0 )
                    target_path += std::to_string( suffix2 );
                target_path += ( " (" + std::to_string( ++index ) + ")" );
            }

            Glib::RefPtr< Gio::File > file_dest = Gio::File::create_for_path( target_path );
            file_src->copy( file_dest );
        }
    }
    catch( ... )
    {
        return false;
    }
    return true;
}

bool
is_dir( const std::string& path )
{
    struct stat s;

    if( stat( PATH( path ).c_str(), &s ) == 0 )
    {
        if( s.st_mode & S_IFDIR )
            return true;
        else if( s.st_mode & S_IFREG ) // file
            return false;
        else // something else
            return false;
    }
    else
        throw Error( "Stat failed" );
}

void
get_all_files_in_path( const std::string& path, SetStrings& list )
{
    Gio::init();
    try
    {
        Glib::RefPtr< Gio::File > directory = Gio::File::create_for_path( path );
        if( directory )
        {
            Glib::RefPtr< Gio::FileEnumerator > enumerator = directory->enumerate_children();
            if( enumerator )
            {
                Glib::RefPtr< Gio::FileInfo > file_info = enumerator->next_file();
                while( file_info )
                {
                    if( file_info->get_file_type() != Gio::FILE_TYPE_DIRECTORY )
                    {
                        list.insert( Glib::build_filename( path, file_info->get_name() ) );
                    }
                    file_info = enumerator->next_file();
                }
            }
        }
    }
    catch( const Glib::Exception& ex )
    {
        print_error( ex.what() );
    }
}

#ifdef _WIN32
String
convert_filename_to_win32_locale( const String& fn )
{
    char* buf = g_win32_locale_filename_from_utf8( fn.c_str() );
    std::string path{ buf };
    g_free( buf );
    return path;
}
#endif

// TEXT OPERATIONS =================================================================================
std::string
STR::format_percentage( double percentage )
{
    std::stringstream str;
    str << std::fixed << std::setprecision( 1 ) << percentage * 100 << '%';
    // TODO add locale support

    return str.str();
}

std::string
STR::format_number( double number )
{
    char result[ 32 ];
    sprintf( result, "%f", number );

    const auto pos_point{ strcspn( result, ".," ) };
    const auto size{ strlen( result ) };

    if( pos_point == size )
        return result;

    std::string str; // decimals separator
    int         decimal_cnt{ 0 };

    for( auto p = pos_point; p > 0; p-- )
    {
        if( p != pos_point && pos_point > 4 && ( ( pos_point - p ) % 3 ) == 0 )
            str.insert( str.begin(), ' ' ); // thousands separator for 10 000 or bigger

        str.insert( str.begin(), result[ p - 1 ] );
    }

    for( auto p = size - 1; p > pos_point; p-- )
    {
        if( decimal_cnt > 0 || result[ p ] != '0' )
            str.insert( str.size() - decimal_cnt++, 1, result[ p ] );
    }
    if( decimal_cnt > 0 )
        str.insert( str.size() - decimal_cnt, 1, '.' );

    return str;
}

bool
STR::ends_with ( const std::string& str, const std::string& end )
{
    if( str.length() > end.length() )
    {
        return( str.compare( str.length() - end.length(), end.length(), end ) == 0 );
    }
    else
    {
        return false;
    }
}

bool
STR::get_line( const std::string& source, std::string::size_type& o, std::string& line )
{
    if( source.empty() || o >= source.size() )
        return false;

    std::string::size_type o_end{ source.find( '\n', o ) };

    if( o_end == std::string::npos )
    {
        line = source.substr( o );
        o = source.size();
    }
    else
    {
        line = source.substr( o, o_end - o );
        o = o_end + 1;
    }

    return true;
}

int
STR::replace( std::string& txt, const String& s_search, const String& s_replace )
{
    int occurrences{ 0 };

    for( auto pos = txt.find( s_search );
         pos != std::string::npos;
         pos = txt.find( s_search, pos + s_replace.size() ) )
    {
        txt.replace( pos, s_search.size(), s_replace );
        occurrences++;
    }

    return occurrences;
}

String
STR::replace_spaces( const String& txt )
{
    std::string output;
    char ch;

    for( unsigned int pos = 0 ; pos < txt.size(); pos++ )
    {
        ch = txt[ pos ];
        if( ch == ' ' || ch == '\t' )
            output += '_';
        else
            output += ch;
    }

    return output;
}

unsigned long
STR::get_ul( const std::string& line, int& i )
{
    unsigned long result{ 0 };

    for( ; i < int( line.size() ) && int ( line[ i ] ) >= '0' && int ( line[ i ] ) <= '9'; i++ )
        result = ( result * 10 ) + int ( line[ i ] ) - '0';

    return result;
}

double
STR::get_d( const std::string& text )
{
    //NOTE: this implementation may be a little bit more forgiving than good for health
    double value{ 0.0 };
    //char lf{ '=' }; // =, \, #, $(unit)
    int    divider{ 0 };
    bool   negative{ false };
    Wchar  c;

    for( Ustring::size_type i = 0; i < text.size(); i++ )
    {
        c = text[ i ];
        switch( c )
        {
            case ',':
            case '.':
                if( !divider ) // note that if divider
                    divider = 1;
                break;
            case '-':
                negative = true;
                break;
            case '0': case '1': case '2': case '3': case '4':
            case '5': case '6': case '7': case '8': case '9':
                value *= 10;
                value += ( c - '0' );
                if( divider )
                    divider *= 10;
                break;
            default:
                break;
        }
    }

    if( divider > 1 )
        value /= divider;
    if( negative )
        value *= -1;

    return value;
}

double
STR::get_d( const std::string& line, int& i )
{
    double value{ 0.0 };
    int    divider{ 0 };
    bool   negative{ false };
    bool   f_continue{ true };
    Wchar  c;

    for( ; i < int( line.size() ) && f_continue; i++ )
    {
        c = line[ i ];
        switch( c )
        {
            case ',':
            case '.':
                if( !divider ) // note that if divider
                    divider = 1;
                break;
            case '-':
                negative = true;
                break;
            case '0': case '1': case '2': case '3': case '4':
            case '5': case '6': case '7': case '8': case '9':
                value *= 10;
                value += ( c - '0' );
                if( divider )
                    divider *= 10;
                break;
            default:
                i--;
                f_continue = false; // end loop
                break;
        }
    }

    if( divider > 1 )
        value /= divider;
    if( negative )
        value *= -1;

    return value;
}

Ustring
STR::lowercase( const Ustring& str )
{
    // fixes the Glib problem with the Turkish capital i (İ)
    Ustring result;
    for( Ustring::size_type i = 0; i < str.length(); i++ )
    {
        if( str.at( i ) == L'İ' )
            result += 'i';
        else
            result += char_lower( str.at( i ) );
    }

    return result;
}

gunichar
char_lower( gunichar c )
{
    return Glib::Unicode::tolower( c );
}

bool
is_char_alpha( gunichar c )
{
    return Glib::Unicode::isalpha( c );
}

std::string
get_env_lang()
{
    static std::string s_lang_env( "" );
    if( s_lang_env.empty() )
    {
        std::string lang = Glib::getenv( "LANG" );
        if( lang.empty() || lang == "C" || lang == "c" )
            s_lang_env = "en";
        else
            s_lang_env = lang;
    }
    return s_lang_env;
}

#ifdef _WIN32

wchar_t*
convert_utf8_to_16( const Ustring& str8 )
{
    wchar_t* str16 = new wchar_t[ str8.size() + 1 ];
    MultiByteToWideChar( CP_UTF8, 0, str8.c_str(), str8.size() + 1, str16, str8.size() + 1 );

    return str16;
}

char*
convert_utf16_to_8( const wchar_t* str16 )
{
    char* str8{ nullptr };
    int size = WideCharToMultiByte( CP_UTF8, 0, str16, -1, str8, 0, NULL, NULL );
    str8 = new char[ size ];
    WideCharToMultiByte( CP_UTF8, 0, str16, -1, str8, size, NULL, NULL );

    return str8;
}

#endif

// ENCRYPTION ======================================================================================
bool
Cipher::init()
{
    // http://www.gnupg.org/documentation/manuals/gcrypt/Initializing-the-library.html

    // initialize subsystems:
    if( ! gcry_check_version( NULL ) )  // TODO: check version
    {
        print_error( "Libgcrypt version mismatch" );
        return false;
    }

    // disable secure memory
    gcry_control( GCRYCTL_DISABLE_SECMEM, 0 );

    // MAYBE LATER:
    /*
    // suppress warnings
    gcry_control( GCRYCTL_SUSPEND_SECMEM_WARN );

    // allocate a pool of 16k secure memory. this makes the secure memory...
    // ...available and also drops privileges where needed
    gcry_control( GCRYCTL_INIT_SECMEM, 16384, 0 );

    // resume warnings
    gcry_control( GCRYCTL_RESUME_SECMEM_WARN );
    */

    // tell Libgcrypt that initialization has completed
    gcry_control( GCRYCTL_INITIALIZATION_FINISHED, 0 );

    return true;
}

void
Cipher::create_iv( unsigned char** iv )
{
    // (Allocate memory for and fill with strong random data)
    *iv = ( unsigned char* ) gcry_random_bytes( Cipher::cIV_SIZE, GCRY_STRONG_RANDOM );

    if( ! *iv )
        throw Error( "Unable to create IV" );
}

void
Cipher::expand_key( const char* passphrase,
                    const unsigned char* salt,
                    unsigned char** key )
{
    gcry_md_hd_t hash;
    gcry_error_t error = 0;
    int hashdigestsize;
    unsigned char* hashresult;

    // OPEN MESSAGE DIGEST ALGORITHM
    error = gcry_md_open( &hash, cHASH_ALGORITHM, 0 );
    if( error )
        throw Error( "Unable to open message digest algorithm: %s" ); //, gpg_strerror( Error ) );

    // RETRIVE DIGEST SIZE
    hashdigestsize = gcry_md_get_algo_dlen( cHASH_ALGORITHM );

    // ADD SALT TO HASH
    gcry_md_write( hash, salt, cSALT_SIZE );

    // ADD PASSPHRASE TO HASH
    gcry_md_write( hash , passphrase , strlen( passphrase ) );

    // FETCH DIGEST (THE EXPANDED KEY)
    hashresult = gcry_md_read( hash , cHASH_ALGORITHM );

    if( ! hashresult )
    {
        gcry_md_close( hash );
        throw Error( "Unable to finalize key" );
    }

    // ALLOCATE MEMORY FOR KEY
    // can't use the 'HashResult' because those resources are freed after the
    // hash is closed
    *key = new unsigned char[ cKEY_SIZE ];
    if( ! key )
    {
        gcry_md_close( hash );
        throw Error( "Unable to allocate memory for key" );
    }

    // DIGEST SIZE SMALLER THEN KEY SIZE?
    if( hashdigestsize < cKEY_SIZE )
    {
        // PAD KEY WITH '0' AT THE END
        memset( *key , 0 , cKEY_SIZE );

        // COPY EVERYTHING WE HAVE
        memcpy( *key , hashresult , hashdigestsize );
    }
    else
        // COPY ALL THE BYTES WE'RE USING
        memcpy( *key , hashresult , hashdigestsize );

    // FINISHED WITH HASH
    gcry_md_close( hash );
}

// create new expanded key
void
Cipher::create_new_key( char const * passphrase,
                        unsigned char** salt,
                        unsigned char** key )
{
    // ALLOCATE MEMORY FOR AND FILL WITH STRONG RANDOM DATA
    *salt = ( unsigned char* ) gcry_random_bytes( cSALT_SIZE, GCRY_STRONG_RANDOM );

    if( ! *salt )
        throw Error( "Unable to create salt value" );

    expand_key( passphrase, *salt, key );
}

void
Cipher::encrypt_buffer ( unsigned char* buffer,
                         size_t& size,
                         const unsigned char* key,
                         const unsigned char* iv )
{
    gcry_cipher_hd_t    cipher;
    gcry_error_t        error = 0;

    error = gcry_cipher_open( &cipher, cCIPHER_ALGORITHM, cCIPHER_MODE, 0 );

    if( error )
        throw Error( "unable to initialize cipher: " ); // + gpg_strerror( Error ) );

    // GET KEY LENGTH
    int cipherKeyLength = gcry_cipher_get_algo_keylen( cCIPHER_ALGORITHM );
    if( ! cipherKeyLength )
        throw Error( "gcry_cipher_get_algo_keylen failed" );

    // SET KEY
    error = gcry_cipher_setkey( cipher, key, cipherKeyLength );
    if( error )
    {
        gcry_cipher_close( cipher );
        throw Error( "Cipher key setup failed: %s" ); //, gpg_strerror( Error ) );
    }

    // SET INITILIZING VECTOR (IV)
    error = gcry_cipher_setiv( cipher, iv, cIV_SIZE );
    if( error )
    {
        gcry_cipher_close( cipher );
        throw Error( "Unable to setup cipher IV: %s" );// , gpg_strerror( Error ) );
    }

    // ENCRYPT BUFFER TO SELF
    error = gcry_cipher_encrypt( cipher, buffer, size, NULL, 0 );

    if( error )
    {
        gcry_cipher_close( cipher );
        throw Error( "Encrption failed: %s" ); // , gpg_strerror( Error ) );
    }

    gcry_cipher_close( cipher );
}

void
Cipher::decrypt_buffer ( unsigned char* buffer,
                         size_t size,
                         const unsigned char* key,
                         const unsigned char* iv )
{
    gcry_cipher_hd_t cipher;
    gcry_error_t error = 0;

    error = gcry_cipher_open( &cipher, cCIPHER_ALGORITHM, cCIPHER_MODE, 0 );

    if( error )
        throw Error( "Unable to initialize cipher: " ); // + gpg_strerror( Error ) );

    // GET KEY LENGTH
    int cipherKeyLength = gcry_cipher_get_algo_keylen( cCIPHER_ALGORITHM );
    if( ! cipherKeyLength )
        throw Error( "gcry_cipher_get_algo_keylen failed" );

    // SET KEY
    error = gcry_cipher_setkey( cipher, key, cipherKeyLength );
    if( error )
    {
        gcry_cipher_close( cipher );
        throw Error( "Cipher key setup failed: %s" ); //, gpg_strerror( Error ) );
    }

    // SET IV
    error = gcry_cipher_setiv( cipher, iv, cIV_SIZE );
    if( error )
    {
        gcry_cipher_close( cipher );
        throw Error( "Unable to setup cipher IV: %s" );// , gpg_strerror( Error ) );
    }

    // DECRYPT BUFFER TO SELF
    error = gcry_cipher_decrypt( cipher, buffer, size, NULL, 0 );

    if( error )
    {
        gcry_cipher_close( cipher );
        throw Error( "Encryption failed: %s" ); // , gpg_strerror( Error ) );
    }

    gcry_cipher_close( cipher );
}

// MARKED UP MENU ITEM =============================================================================
Gtk::MenuItem*
create_menuitem_markup( const Glib::ustring& str_markup,
                        const Glib::SignalProxy0< void >::SlotType& handler )
{
    // thanks to GNote for showing the way
    Gtk::MenuItem* menuitem = Gtk::manage( new Gtk::MenuItem( str_markup ) );
    Gtk::Label* label = dynamic_cast< Gtk::Label* >( menuitem->get_child() );
    if( label )
        label->set_use_markup( true );
    menuitem->signal_activate().connect( handler );
    return menuitem;
}

// QUICK CSS SETTER ================================================================================
bool set_widget_css( Gtk::Widget* w, const Ustring& css )
{
    Glib::RefPtr< Gtk::CssProvider > css_prov = Gtk::CssProvider::create();
    if( css_prov->load_from_data( css ) )
        w->get_style_context()->add_provider( css_prov, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION );
    else
        return false;

    return true;
}

// ENTRYCLEAR ======================================================================================
EntryClear::EntryClear()
:   Gtk::Entry(), m_pressed( false )
{
    signal_icon_press().connect( sigc::mem_fun( this, &EntryClear::handle_icon_press ) );
    signal_icon_release().connect( sigc::mem_fun( this, &EntryClear::handle_icon_release ) );
}

EntryClear::EntryClear( BaseObjectType* cobject, const Glib::RefPtr<Gtk::Builder>& )
:   Gtk::Entry( cobject ), m_pressed( false )
{
    signal_icon_press().connect( sigc::mem_fun( this, &EntryClear::handle_icon_press ) );
    signal_icon_release().connect( sigc::mem_fun( this, &EntryClear::handle_icon_release ) );
}

void
EntryClear::handle_icon_press( Gtk::EntryIconPosition pos, const GdkEventButton* event )
{
    if( pos == Gtk::ENTRY_ICON_SECONDARY && event->button == 1 )
        m_pressed = true;
}

void
EntryClear::handle_icon_release( Gtk::EntryIconPosition pos, const GdkEventButton* event )
{
    if( pos == Gtk::ENTRY_ICON_SECONDARY && event->button == 1 )
    {
        if( m_pressed )
            set_text( "" );
        m_pressed = false;
    }
}

void
EntryClear::on_changed()
{
    if( get_text().empty() )
    {
        // Fall-back to C API as gtkmm has problems with this:
        unset_icon( Gtk::ENTRY_ICON_SECONDARY );
        set_icon_activatable( false, Gtk::ENTRY_ICON_SECONDARY );
    }
    else
    {
        try // may not work!
        {
            set_icon_from_icon_name( "edit-clear-symbolic", Gtk::ENTRY_ICON_SECONDARY );
        }
        catch( ... )
        {
            set_icon_from_icon_name( "edit-clear", Gtk::ENTRY_ICON_SECONDARY );
        }
        set_icon_activatable( true, Gtk::ENTRY_ICON_SECONDARY );
    }

    Gtk::Entry::on_changed();
}

bool
EntryClear::on_key_release_event( GdkEventKey* event )
{
    if( event->keyval == GDK_KEY_Escape )
        set_text( "" );

    return Gtk::Entry::on_key_release_event( event );
}

// DIALOGEVENT =====================================================================================
// the rest of the DialogEvent needs to be defined within the user application
void
DialogEvent::handle_logout()
{
    hide();
}

// FRAME (for printing) ============================================================================
Gtk::Frame*
create_frame( const Glib::ustring& str_label, Gtk::Widget& content )
{
    Gtk::Frame* frame = Gtk::manage( new Gtk::Frame );
    Gtk::Label* label = Gtk::manage( new Gtk::Label );

    Gtk::Alignment* alignment = Gtk::manage( new Gtk::Alignment );

    label->set_markup( Glib::ustring::compose( "<b>%1</b>", str_label ) );
    frame->set_shadow_type( Gtk::SHADOW_NONE );
    frame->set_label_widget( *label );
    alignment->set_padding( 0, 0, 12, 0 );
    alignment->add( content );
    frame->add( *alignment );

    return frame;
}

// TREEVIEW ========================================================================================
bool is_treepath_less( const Gtk::TreePath& p, const Gtk::TreePath& pb )
{
    for( unsigned int i = 0; i < p.size(); i++ )
    {
        if( i >= pb.size() )
            return false;

        if( p[i] < pb[ i ] )
            return true;

        if( p[i] > pb[ i ] )
            return false;
    }

    return true; // if pb has more members apart from the shared ones, it is more
}

bool is_treepath_more( const Gtk::TreePath& p, const Gtk::TreePath& pe )
{
    for( unsigned int i = 0; i < p.size(); i++ )
    {
        if( i >= pe.size() )
            return true;

        if( p[i] > pe[ i ] )
            return true;

        if( p[i] < pe[ i ] )
            return false;
    }

    return false; // less or equal
}

// OTHER GTK+ ======================================================================================
void
flush_gtk_event_queue()
{
    while( Gtk::Main::events_pending() )
        Gtk::Main::iteration();
}

Gdk::Rectangle
get_rectangle_from_event( const GdkEventButton* event )
{
    Gdk::Rectangle rect{ int( event->x ), int( event->y ), 0, 0 };
    return rect;
}

bool
select_image_file( Gtk::Window& parent, std::string& uri )
{
    auto* file_dialog{ new Gtk::FileChooserDialog( parent, _( "Select Image File" ) ) };

    auto filter_any = Gtk::FileFilter::create();
    auto filter_img = Gtk::FileFilter::create();

    filter_any->set_name( _( "All Files" ) );
    filter_any->add_pattern( "*" );
    filter_img->set_name( _( "Image Files" ) );
#ifdef _WIN32
    filter_img->add_pattern( "*.jpg" );
    filter_img->add_pattern( "*.png" );
#else
    filter_img->add_mime_type( "image/jpeg" );
    filter_img->add_mime_type( "image/png" );
#endif

    file_dialog->add_filter( filter_any );
    file_dialog->add_filter( filter_img );

    file_dialog->add_button( Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL );
    file_dialog->add_button( Gtk::Stock::OPEN, Gtk::RESPONSE_OK );

    if( file_dialog->run() == Gtk::RESPONSE_OK )
    {
        uri = file_dialog->get_uri();

        delete file_dialog;

        return true;
    }

    delete file_dialog;

    return false;
}

} // end of name space
