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

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

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

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


#ifndef LIFEOGRAPH_HELPERS_HEADER
#define LIFEOGRAPH_HELPERS_HEADER


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

#ifdef _WIN32
#include <winsock2.h> // to silence warnings on Windows
#endif

#include <iostream>
#include <fstream>
#include <sstream>
#include <iomanip>
#include <set>

#include <libintl.h>

#include <gtkmm.h>
#include <gcrypt.h>

// DEFINITIONS FOR LIBGETTEXT
#define _(String)               gettext(String)
#define gettext_noop(String)    String
#define N_(String)              gettext_noop(String)
// END OF LIBGETTEXT DEFINITIONS

namespace HELPERS
{
// CONSTANTS
static constexpr double MI_TO_KM_RATIO{ 1.609344 };
static constexpr double PI = 3.141592653589793;

// TYPE DEFINITIONS
typedef unsigned long                           date_t;
typedef double                                  Value;

typedef std::string                             String;
typedef std::string::size_type                  StringSize;
typedef Glib::ustring                           Ustring;
typedef Glib::ustring::size_type                UstringSize;
typedef std::pair< const Ustring, const Ustring >   PairConstStrings;
typedef std::vector< Ustring >                  VecUstrings;
typedef std::set< String >                      SetStrings;
typedef std::vector< String >                   VecStrings;
typedef std::list< Ustring >                    ListUstrings;
typedef std::set< Ustring >                     SetUstrings;
typedef gunichar                                Wchar;
typedef Glib::RefPtr< Gdk::Pixbuf >             Icon;
typedef std::map< String, Icon >                MapPathsIcons;
typedef Gdk::RGBA                               Color;
typedef Glib::RefPtr< Gio::SimpleAction >       Action;

typedef sigc::signal< void >                    SignalVoid;
typedef sigc::signal< void, const Ustring& >    SignalVoidUstring;
typedef sigc::signal< void, date_t >            SignalVoidDate;
typedef sigc::signal< void, date_t, const Ustring& >    SignalVoidDateUstring;
typedef sigc::signal< bool, const Ustring& >    SignalBoolUstring;

enum class ArrayPosition { FIRST, INTERMEDIATE, LAST };

// ERROR for throw-ing
class Error
{
    public:
                        Error( const Ustring& );
};

// RESULT
enum Result
{
    OK,
    ABORTED,
    SUCCESS,
    FAILURE,
    COULD_NOT_START,
    COULD_NOT_FINISH,
    WRONG_PASSWORD,
    //APPARENTLY_ENCRYTED_FILE,
    //APPARENTLY_PLAIN_FILE,
    INCOMPATIBLE_FILE_OLD,
    INCOMPATIBLE_FILE_NEW,
    CORRUPT_FILE,
    // EMPTY_DATABASE, // not used anymore

    // RESULTS USED BY set_path():
    FILE_NOT_FOUND,
    FILE_NOT_READABLE,
    FILE_NOT_WRITABLE,
    FILE_LOCKED,

    // RESULTS USED BY Date
    OUT_OF_RANGE,
    INVALID
};

// STRING OPERATIONS ===============================================================================
struct FuncCmpStrings
{
    bool operator()( const Ustring& l, const Ustring& r )
    const { return( l < r ); }
};

class STR
{
    private:
        static void             print_internal( String& ) {}

        template< typename Arg1, typename... Args >
        static void             print_internal( String& str, Arg1 arg1, Args... args )
        {
            str += format( arg1 );
            print_internal( str, args... );
        }
        static const char*      format( const char* str ){ return str; }
        static const String     format( const String& str ){ return str; }
        static const char       format( const char ch ){ return ch; }
        static const String     format( int num ){ return std::to_string( num ); }
        static const String     format( unsigned int num ){ return std::to_string( num ); }
        static const String     format( long num ){ return std::to_string( num ); }
        static const String     format( unsigned long num ){ return std::to_string( num ); }
        static const String     format( unsigned long long num ){ return std::to_string( num ); }
        static const String     format( double num ){ return format_number( num ); }

    public:
        template< typename... Args >
        static Ustring          compose( Args... args )
        {
            String str;
            print_internal( str, args... );

            return str;
        }

        static String           format_percentage( double );
        static String           format_number( double );

        static bool             ends_with( const String&, const String& );
        static bool             get_line( const String&, StringSize&, String& );
        static int              replace( String&, const String&, const String& );
        static String           replace_spaces( const String& );

        static Ustring          lowercase( const Ustring& );

        //static long             get_ulong( const String& ); use std::stoul instead
        static unsigned long    get_ul( const String&, int& i );
        static double           get_d( const String& ); // std::stod creates locale issues
        static double           get_d( const String&, int& i ); // version 2
};

// OTHER STRING OPERATIONS =========================================================================
Wchar               char_lower( Wchar );
bool                is_char_alpha( Wchar );
String              get_env_lang();
#ifdef _WIN32
wchar_t*            convert_utf8_to_16( const Ustring& );
char*               convert_utf16_to_8( const wchar_t* );
#endif

// DATE ============================================================================================
// order: 10 bits
// day:    5 bits
// month:  4 bits
// year:  12 bits
// ordinal flag:  1 bit (32nd bit)

class Date
{
    public:
        static const unsigned long  NOT_APPLICABLE       =        0x0;
        static const unsigned long  NOT_SET              = 0xFFFFFFFF;
        static const unsigned long  DATE_MAX             = 0xFFFFFFFF;

        static const unsigned int   YEAR_MIN             = 1900;
        static const unsigned int   YEAR_MAX             = 2199;
        static const unsigned long  ORDER_MAX            = 1023;
        static const unsigned long  ORDER_1ST_MAX        = 0x3FF00000; // bits 21..30
        static const unsigned long  ORDER_2ND_MAX        =    0xFFC00; // bits 11..20
        static const unsigned int   ORDER_3RD_MAX        =      0x3FF; // bits 01..10

        static const unsigned long  ORDER_1ST_STEP       =   0x100000; // bit 21
        static const unsigned long  ORDER_2ND_STEP       =      0x400; // bit 11

        static const unsigned long  FILTER_DAY           =     0x7C00; // bits 11..15
        static const unsigned long  FILTER_MONTH         =    0x78000; // bits 16..19
        static const unsigned long  FILTER_YEAR          = 0x7FF80000; // bits 20..31
        static const unsigned long  FILTER_YEARMONTH     = FILTER_YEAR|FILTER_MONTH;
        static const unsigned int   FILTER_PURE          = DATE_MAX ^ ORDER_3RD_MAX;
        static const unsigned long  FILTER_ORDER_1ST_INV = DATE_MAX ^ ORDER_1ST_MAX;
        static const unsigned long  FILTER_ORDER_2ND_INV = DATE_MAX ^ ORDER_2ND_MAX;
        static const unsigned long  FILTER_ORDER_3RD_INV = DATE_MAX ^ ORDER_3RD_MAX; // FILTER_PURE
        static const unsigned long  FILTER_DAY_INV       = DATE_MAX ^ FILTER_DAY;
        static const unsigned long  FILTER_MONTH_INV     = DATE_MAX ^ FILTER_MONTH;
        static const unsigned long  FILTER_YEAR_INV      = DATE_MAX ^ FILTER_YEAR;

        // hidden elements' sequence numbers are not shown
        static const unsigned long  FLAG_VISIBLE         = 0x40000000; // only for ordinal items
        static const unsigned long  FLAG_ORDINAL         = 0x80000000; // 32nd bit

        static const unsigned long  NUMBERED_MIN         = FLAG_VISIBLE|FLAG_ORDINAL;
        static const unsigned long  FREE_MIN             = FLAG_ORDINAL;

        static String               s_format_order;
        static char                 s_format_separator;

        void operator=( const Date& date )
        { m_date = date.m_date; }
        void operator=( const date_t& date )
        { m_date = date; }

        bool operator>( const Date& date_r )
        { return( m_date > date_r.m_date ); }
        bool operator>=( const Date& date_r )
        { return( m_date >= date_r.m_date ); }
        bool operator<( const Date& date_r )
        { return( m_date < date_r.m_date ); }

        bool operator==( const Date& date )
        { return( m_date == date.m_date ); }
        bool operator==( const date_t& date )
        { return( m_date == date ); }

        bool operator!=( const Date& date )
        { return( m_date != date.m_date ); }

        explicit                    Date( date_t date )
            :   m_date( date ) {}
        explicit                    Date( unsigned int y, unsigned int m, unsigned int d,
                                          unsigned int o = 0 )
            :   m_date( ( y << 19 ) | ( m << 15 ) | ( d << 10 ) | o ) {}
        explicit                    Date( const String& );
        // TODO: later: explicit                    Date( time_t );
        // ORDINAL C'TOR
        explicit                    Date( bool f_n,
                                          unsigned int o1, unsigned int o2, unsigned int o3 )
            : m_date( ( f_n ? NUMBERED_MIN : FREE_MIN ) | ( o1 << 20 ) | ( o2 << 10 ) | o3 ) {}

        // TEMPORAL METHODS
        static date_t               get_today( int order = 0 )
        {
            time_t t = time( NULL );
            struct tm* ti = localtime( &t );
            return make( ti->tm_year + 1900, ti->tm_mon + 1, ti->tm_mday, order );
        }

        void                        set_now( int order = 0 )
        { m_date = get_today( order ); }

        date_t                      get_pure() const
        { return( m_date & FILTER_PURE ); }
        static date_t               get_pure( const date_t d )
        { return( d & FILTER_PURE ); }

        date_t                      get_yearmonth() const
        { return( m_date & FILTER_YEARMONTH ); }
        static date_t               get_yearmonth( const date_t d )
        { return( d & FILTER_YEARMONTH ); }

        unsigned int                get_year() const
        { return ( ( m_date & FILTER_YEAR ) >> 19 ); }
        static unsigned int         get_year( const date_t d )
        { return ( ( d & FILTER_YEAR ) >> 19 ); }

        static bool                 is_leap_year( const date_t d )
        {
            int year( get_year( d ) );
            if( ( year % 400 ) == 0 )
                return true;
            else if( ( year % 100 ) == 0 )
                return false;

            return( ( year % 4 ) == 0 );
        }
        bool                        is_leap_year() const
        { return is_leap_year( m_date ); }

        unsigned int                get_month() const
        { return( ( m_date & FILTER_MONTH ) >> 15 ); }
        static unsigned int         get_month( const date_t d )
        { return( ( d & FILTER_MONTH ) >> 15 ); }
        Glib::Date::Month           get_month_glib() const;

        unsigned int                get_day() const
        { return( ( m_date & FILTER_DAY ) >> 10 ); }
        static unsigned int         get_day( const date_t d )
        { return( ( d & FILTER_DAY ) >> 10 ); }

        unsigned int                get_weekday() const
        { return get_weekday( m_date ); }
        static unsigned int         get_weekday( const date_t& );

        unsigned int                get_yearday() const
        { return get_yearday( m_date ); }
        static unsigned int         get_yearday( const date_t& );

        static unsigned int         get_days_in_month( const date_t d );
        unsigned int                get_days_in_month() const
        { return get_days_in_month( m_date ); }

        static unsigned int         get_days_in_year( const date_t d )
        { return( is_leap_year( d ) ? 366 : 365 ); }
        unsigned int                get_days_in_year() const
        { return get_days_in_year( m_date ); }

        Glib::Date                  get_glib() const
        { return Glib::Date( get_day(), get_month_glib(), get_year() ); }

        time_t                      get_ctime() const
        { return get_ctime( m_date ); }
        static time_t               get_ctime( const date_t );

        void                        set_year( unsigned int y )
        {
            if( y >= YEAR_MIN && y <= YEAR_MAX )
            {
                m_date &= FILTER_YEAR_INV;
                m_date |= ( y << 19 );
            }
        }
        void                        set_month( unsigned int m )
        {
            if( m < 13 )
            {
                m_date &= FILTER_MONTH_INV;
                m_date |= ( m << 15 );
            }
        }
        static void                 set_day( date_t& date, unsigned int d )
        {
            if( d < 32 )
            {
                date &= FILTER_DAY_INV;
                date |= ( d << 10 );
            }
        }
        void                        set_day( unsigned int d )
        { set_day( m_date, d ); }

        static void                 forward_months( date_t& d, unsigned int months );
        void                        forward_months( unsigned int months )
        { forward_months( m_date, months ); }
        static void                 forward_days( date_t&, unsigned int );
        void                        forward_days( unsigned int days )
        { forward_days( m_date, days ); }

        void                        backward_months( unsigned int months )
        { backward_months( m_date, months ); }
        static void                 backward_months( date_t&, unsigned int );
        static void                 backward_days( date_t&, unsigned int );
        void                        backward_days( unsigned int days )
        { backward_days( m_date, days ); }

        static void                 backward_to_week_start( date_t& );

        // ORDINAL METHODS
        static date_t               get_lowest_ordinal( bool flag_free ) {
            return( flag_free ? FREE_MIN : NUMBERED_MIN );
        }

        void                        set_order_1st( unsigned int o )
        { m_date &= FILTER_ORDER_1ST_INV; m_date |= ( o << 20 ); }
        void                        set_order_2nd( unsigned int o )
        { m_date &= FILTER_ORDER_2ND_INV; m_date |= ( o << 10 ); }
        void                        set_order_3rd( unsigned int o )
        { m_date &= FILTER_ORDER_3RD_INV; m_date |= o; }

        static void                 set_order_1st( date_t& d, unsigned int o )
        { d &= FILTER_ORDER_1ST_INV; d |= ( o << 20 ); }
        static void                 set_order_2nd( date_t& d, unsigned int o )
        { d &= FILTER_ORDER_2ND_INV; d |= ( o << 10 ); }
        static void                 set_order_3rd( date_t& d, unsigned int o )
        { d &= FILTER_ORDER_3RD_INV; d |= o; }

        // the below methods are related to get_pure methods
        void                        reset_order_3rd_0()
        { m_date &= FILTER_ORDER_3RD_INV; }
        static void                 reset_order_3rd_0( date_t& date )
        { date &= FILTER_ORDER_3RD_INV; }
        void                        reset_order_3rd_1()
        { m_date &= FILTER_ORDER_3RD_INV; m_date |= 0x1; }
        static void                 reset_order_3rd_1( date_t& date )
        { date &= FILTER_ORDER_3RD_INV; date |= 0x1; }
        static date_t               get_as_order_3rd_1( const date_t& date )
        { return( ( date & FILTER_ORDER_3RD_INV ) | 0x1 ); }

        unsigned int                get_order_3rd() const
        { return( m_date & ORDER_3RD_MAX ); }
        static unsigned int         get_order_3rd( const date_t d )
        { return( d & ORDER_3RD_MAX ); }

        unsigned int                get_order_2nd() const
        { return( ( m_date & ORDER_2ND_MAX ) >> 10 ); }
        static unsigned int         get_order_2nd( const date_t d )
        { return( ( d & ORDER_2ND_MAX ) >> 10 ); }

        unsigned int                get_order_1st() const
        { return( ( m_date & ORDER_1ST_MAX ) >> 20 ); }
        static unsigned int         get_order_1st( const date_t d )
        { return( ( d & ORDER_1ST_MAX ) >> 20 ); }

        static void                 set_order( date_t& d, int l, unsigned int o )
        {
            switch( l )
            {
                case 1: set_order_1st( d, o ); break;
                case 2: set_order_2nd( d, o ); break;
                case 3:
                case 4: set_order_3rd( d, o ); break; // 4: temporal order
            }
        }
        void                        set_order( int l, unsigned int o )
        { set_order( m_date, l, o ); }
        static unsigned int         get_order( const date_t d, int level )
        {
            switch( level )
            {
                case 1: return get_order_1st( d );
                case 2: return get_order_2nd( d );
                case 3: return get_order_3rd( d );
            }
            return NOT_SET;
        }
        unsigned int                get_order( int level ) const
        { return get_order( m_date, level ); }

        // right order (least significant order)
        static unsigned int         get_order_r( const date_t d )
        {
            switch( get_level( d ) )
            {
                case 1: return get_order_1st( d );
                case 2: return get_order_2nd( d );
                case 3:
                case 4: return get_order_3rd( d );
            }
            return NOT_SET;
        }
        unsigned int                get_order_r() const
        { return get_order_r( m_date ); }

        bool                        is_1st_level() const
        { return( ( m_date & ( ORDER_2ND_MAX | ORDER_3RD_MAX ) ) == 0 ); }
        static bool                 is_1st_level( const date_t d )
        { return( ( d & ( ORDER_2ND_MAX | ORDER_3RD_MAX ) ) == 0 ); }

        bool                        is_2nd_level() const
        { return( ( m_date & ORDER_2ND_MAX ) != 0 && ( m_date & ORDER_3RD_MAX ) == 0 ); }
        static bool                 is_2nd_level( const date_t d )
        { return( ( d & ORDER_2ND_MAX ) != 0 && ( d & ORDER_3RD_MAX ) == 0 ); }

        bool                        is_3rd_level() const
        { return( ( m_date & ORDER_2ND_MAX ) != 0 && ( m_date & ORDER_3RD_MAX ) != 0 ); }
        static bool                 is_3rd_level( const date_t d )
        { return( ( d & ORDER_2ND_MAX ) != 0 && ( d & ORDER_3RD_MAX ) != 0 ); }

        static int                  get_level( const date_t d )
        {
            if( not( is_ordinal( d ) ) ) return 4; // temporal
            if( is_1st_level( d ) ) return 1;
            if( is_2nd_level( d ) ) return 2;
            return 3;
        }
        int                         get_level() const
        { return get_level( m_date ); }

        void                        backward_order_1st()
        { if( get_order_1st() > 0 ) m_date -= ORDER_1ST_STEP; }
        static void                 backward_order_1st( date_t& d )
        { if( get_order_1st( d ) > 0 ) d -= ORDER_1ST_STEP; }
        void                        forward_order_1st()
        { if( ( m_date & ORDER_1ST_MAX ) < ORDER_1ST_MAX ) m_date += ORDER_1ST_STEP; }
        static void                 forward_order_1st( date_t& d )
        { if( ( d & ORDER_1ST_MAX ) != ORDER_1ST_MAX ) d += ORDER_1ST_STEP; }
        void                        backward_order_2nd()
        { if( get_order_2nd() > 0 ) m_date -= ORDER_2ND_STEP; }
        static void                 backward_order_2nd( date_t& d )
        { if( get_order_2nd( d ) > 0 ) d -= ORDER_2ND_STEP; }
        void                        forward_order_2nd()
        { if( ( m_date & ORDER_2ND_MAX ) < ORDER_2ND_MAX ) m_date += ORDER_2ND_STEP; }
        static void                 forward_order_2nd( date_t& d )
        { if( ( d & ORDER_2ND_MAX ) != ORDER_2ND_MAX ) d += ORDER_2ND_STEP; }

        static void                 backward_order( date_t& d, int level )
        {
            if     ( level == 1 ) backward_order_1st( d );
            else if( level == 2 ) backward_order_2nd( d );
            else if( level == 3 ) d--;
        }
        void                        backward_order( int level )
        { backward_order( m_date, level ); }

        static void                 forward_order( date_t& d, int level )
        {
            if     ( level == 1 ) forward_order_1st( d );
            else if( level == 2 ) forward_order_2nd( d );
            else if( level == 3 ) d++;
        }
        void                        forward_order( int level )
        { forward_order( m_date, level ); }

        // RELATIONSHIP METHODS
        static date_t               get_parent( const date_t d )
        {
            switch( get_level( d ) )
            {
                case 1: return( DATE_MAX );
                case 2: return( d & FILTER_ORDER_2ND_INV & FILTER_ORDER_3RD_INV );
                default: return( d & FILTER_ORDER_3RD_INV );
            }
        }
        date_t                      get_parent() const
        { return get_parent( m_date ); }

        static date_t               get_next_date( date_t d )
        {
            if( get_order_r( d ) < ORDER_MAX )
                set_order( d, get_level( d ), get_order_r( d ) + 1 );

            return d;
        }
        date_t                      get_next_date()
        { return get_next_date( m_date ); }

        static date_t               get_prev_date( date_t d )
        {
            if( get_order_r( d ) > 0 )
                set_order( d, get_level( d ), get_order_r( d ) - 1 );

            return d;
        }
        date_t                      get_prev_date()
        { return get_prev_date( m_date ); }

        static bool                 is_valid( const date_t d )
        { return( is_set( d ) && get_day( d ) > 0 && get_day( d ) <= get_days_in_month( d ) ); }
        bool                        is_valid() const
        {
            return( is_valid( m_date) );
        }
        static bool                 is_set( const date_t date )
        { return( date != NOT_SET ); }
        bool                        is_set() const
        { return( m_date != NOT_SET ); }

        bool                        is_ordinal() const
        { return( m_date & FLAG_ORDINAL ); }
        static bool                 is_ordinal( const date_t d )
        { return( d & FLAG_ORDINAL ); }

        bool                        is_hidden() const
        { return( is_ordinal() && !( m_date & FLAG_VISIBLE ) ); }
        static bool                 is_hidden( const date_t d )
        { return( is_ordinal( d ) && !( d & FLAG_VISIBLE ) ); }

        static bool                 is_same_kind( const date_t d1, const date_t d2 )
        {
            return( ( d1 & ( FLAG_VISIBLE|FLAG_ORDINAL ) ) ==
                    ( d2 & ( FLAG_VISIBLE|FLAG_ORDINAL ) ) );
        }
        static bool                 is_sibling( const date_t d1, const date_t d2 )
        { return( is_ordinal( d1 ) && get_parent( d1 ) == get_parent( d2 ) ); }
        static bool                 is_child_of( const date_t dc, const date_t dp )
        { return( is_ordinal( dc ) && get_parent( dc ) == dp ); }
        static bool                 is_descendant_of( const date_t dc, const date_t dp )
        {
            if( dc == dp ) return false;
            if( dp == DATE_MAX ) return true; // DATE_MAX means all dates
            if( ( dc & FLAG_VISIBLE ) != ( dp & FLAG_VISIBLE ) ) return false;
            if( is_1st_level( dp ) ) return( get_order_1st( dc ) == get_order_1st( dp ) );
            if( is_2nd_level( dp ) ) return( get_order_1st( dc ) == get_order_1st( dp ) &&
                                             get_order_2nd( dc ) == get_order_2nd( dp ) );
            return false;
        }

        // MAKE METHODS
        static date_t               make_year( unsigned int y )
        { return( y << 19 ); }
        static date_t               make_month( unsigned int m )
        { return( m << 15 ); }
        static date_t               make_day( unsigned int m )
        { return( m << 10 ); }
        static date_t               make( unsigned int y, unsigned int m, unsigned int d,
                                          unsigned int o = 0 )
        { return( ( y << 19 ) | ( m << 15 ) | ( d << 10 ) | o ); }
        static date_t               make_ordinal( bool f_num,
                                                  unsigned int o1,
                                                  unsigned int o2,
                                                  unsigned int o3 = 0 )
        { return( ( f_num ? NUMBERED_MIN : FREE_MIN ) | ( o1 << 20 ) | ( o2 << 10 ) | o3 ); }
        static date_t               make_from_ctime( const tm* timeinfo )
        {
            return make( timeinfo->tm_year + 1900, timeinfo->tm_mon + 1, timeinfo->tm_mday );
        }
        static date_t               make_from_ctime( const time_t t )
        {
            struct tm* timeinfo = localtime( &t );
            return make( timeinfo->tm_year + 1900, timeinfo->tm_mon + 1, timeinfo->tm_mday );
        }

        // CALCULATE METHODS
        unsigned int                calculate_days_between( const Date& ) const;
        static unsigned int         calculate_weeks_between( date_t, date_t );
        unsigned int                calculate_months_between( date_t date2 ) const
        { return calculate_months_between( m_date, date2 ); }
        static unsigned int         calculate_months_between( date_t, date_t );
        static int                  calculate_months_between_neg( date_t, date_t );

        // STRING METHODS
        String                      get_year_str() const;
        Ustring                     get_month_str() const;
        //String                      get_day_str() const;
        Ustring                     get_weekday_str() const;

        static Result               parse_string( date_t*, const Ustring& );

        Ustring                     format_string() const
        { return format_string( m_date, s_format_order, s_format_separator ); }
        Ustring                     format_string( const String& format,
                                                   const char separator = s_format_separator )
        { return format_string( m_date, format, separator ); }
        static Ustring              format_string( const date_t date )
        { return format_string( date, s_format_order, s_format_separator ); }
        static Ustring              format_string( const date_t, const String&,
                                                   const char = s_format_separator );
        static Ustring              format_string_dt( const time_t );
        static Ustring              format_string_d( const time_t );

    //protected:
        date_t                      m_date{ 0 };
};

/*
inline  int
subtract_months( Date d1, Date d2 )
{
    return ( ( isolate_month( d1 ) - isolate_month( d2 ) ) +
             ( ( isolate_year( d1 ) - isolate_year( d2 ) ) * 12 ) );
}
*/

inline bool
compare_dates( const date_t& date_l, const date_t& date_r )
{
    return( date_l > date_r );
}

typedef bool( *FuncCompareDates )( const date_t&, const date_t& ) ;

// CONSOLE MESSAGES ================================================================================
class Console
{
    private:
        static void print( std::ostream& os )
        {
            os << std::endl;
        }

        template< typename Arg1, typename... Args >
        static void print( std::ostream& os, Arg1 arg1, Args... args )
        {
            try{ os << arg1; } // this is necessary for Windows iconv exceptions
            catch( Glib::Error& er )
            { os << "Error printing text:" << er.what() << std::endl; }

            print( os, args... );
        }

#if LIFEOGRAPH_DEBUG_BUILD
        static unsigned DEBUG_LINE_I;
#endif

    template< typename... Args >
    friend void print_error( Args... );

    template< typename... Args >
    friend void print_info( Args... );

    template< typename... Args >
    friend void PRINT_DEBUG( Args... );
};

template< typename... Args >
void print_info( Args... args )
{
    Console::print( std::cout, "INFO: ", args... );
}

template< typename... Args >
void print_error( Args... args )
{
    Console::print( std::cerr, "ERROR: ", args... );
}

#if LIFEOGRAPH_DEBUG_BUILD
    template< typename... Args >
    void PRINT_DEBUG( Args... args )
    {
        Console::print( std::cout, "* DBG:", Console::DEBUG_LINE_I++, " * ", args... );
    }
#else
#define PRINT_DEBUG( ... ) ;
#endif

// COLOR OPERATIONS ================================================================================
inline float
get_color_diff( const Color& c1, const Color c2 )
{
    return( fabs( c2.get_red() - c1.get_red() ) +
            fabs( c2.get_green() - c1.get_green() ) +
            fabs( c2.get_blue() - c1.get_blue() ) );
}

inline Ustring
convert_gdkcolor_to_html( const Color& gdkcolor )
{
    // this function's source of inspiration is Geany
    char buffer[ 8 ];

    g_snprintf( buffer, 8, "#%02X%02X%02X",
            gdkcolor.get_red_u() >> 8,
            gdkcolor.get_green_u() >> 8,
            gdkcolor.get_blue_u() >> 8 );
    return buffer;
}

inline Ustring
convert_gdkrgba_to_string( const Color& gdkcolor )
{
    char buffer[ 14 ];
    g_snprintf( buffer, 14, "#%04X%04X%04X",
            int( gdkcolor.get_red() * 0xFFFF ),
            int( gdkcolor.get_green() * 0xFFFF ),
            int( gdkcolor.get_blue() * 0xFFFF ) );
    return buffer;
}

Color               contrast2( const Color&, const Color&, const Color& );
Color               midtone( const Color&, const Color& );
Color               midtone( const Color&, const Color&, double );

// FILE OPERATIONS =================================================================================
#ifdef _WIN32
String              get_exec_path();
#endif

std::ios::pos_type  get_file_size( std::ifstream& );

bool                copy_file( const String&, const String&, bool );
bool                copy_file_suffix( const String&, const String&, int, bool );

bool                is_dir( const String& );

inline String
get_filename_base( const String& path )
{
    return Glib::filename_display_basename( path );
}

#ifdef _WIN32

String
convert_filename_to_win32_locale( const String& );

inline String
convert_locale_to_utf8( const String& str )
{
    return Glib::locale_to_utf8( str );
}

#define PATH( A ) convert_filename_to_win32_locale( A )
#else
#define PATH( A ) ( A )
#define convert_locale_to_utf8( A ) ( A )
#endif

// the following is not used right now but may be helpful in the future
void                get_all_files_in_path( const String&, SetStrings& );

// ENCRYPTION ======================================================================================
class Cipher
{
    public:
        static const int    cCIPHER_ALGORITHM   = GCRY_CIPHER_AES256;
        static const int    cCIPHER_MODE        = GCRY_CIPHER_MODE_CFB;
        static const int    cIV_SIZE            = 16; // = 128 bits
        static const int    cSALT_SIZE          = 16; // = 128 bits
        static const int    cKEY_SIZE           = 32; // = 256 bits
        static const int    cHASH_ALGORITHM     = GCRY_MD_SHA256;

        static bool         init();

        static void         create_iv( unsigned char** );
        static void         expand_key( char const*,
                                        const unsigned char*,
                                        unsigned char** );
        static void         create_new_key( char const*,
                                            unsigned char**,
                                            unsigned char** );
        static void         encrypt_buffer( unsigned char*,
                                            size_t&,
                                            const unsigned char*,
                                            const unsigned char* );
        static void         decrypt_buffer( unsigned char*,
                                            size_t,
                                            const unsigned char*,
                                            const unsigned char* );

    protected:

    private:

};

struct CipherBuffers
{
    CipherBuffers()
    :   buffer( NULL ), salt( NULL ), iv( NULL ), key( NULL ) {}

    unsigned char* buffer;
    unsigned char* salt;
    unsigned char* iv;
    unsigned char* key;

    void clear()
    {
        if( buffer ) delete[] buffer;
        if( salt ) delete[] salt;
        if( iv ) delete[] iv;
        if( key ) delete[] key;
    }
};

// ENTRY WIDGET WITH SELF-HANDLED CLEAR ICON =======================================================
class EntryClear : public Gtk::Entry
{
    public:
                            EntryClear();
                            EntryClear( BaseObjectType*, const Glib::RefPtr< Gtk::Builder >& );

    protected:
        void                handle_icon_press( Gtk::EntryIconPosition, const GdkEventButton* );
        void                handle_icon_release( Gtk::EntryIconPosition, const GdkEventButton* );
        virtual void        on_changed();
        virtual bool        on_key_release_event( GdkEventKey* );
        bool                m_pressed;
};

// DIALOGEVENT =====================================================================================
class DialogEvent : public Gtk::Dialog
{
    public:
                            DialogEvent( const Glib::ustring& );
                            DialogEvent( BaseObjectType*,
                                         const Glib::RefPtr< Gtk::Builder >& );

    protected:
        bool                on_event( GdkEvent* );
        void                handle_logout();

};

// FRAME FOR PRINTING ==============================================================================
Gtk::Frame* create_frame( const Glib::ustring&, Gtk::Widget& );

// TREEVIEW ========================================================================================
// scroll_to_row( Path& ) does not work for some reason. So, implemented our own
bool is_treepath_less( const Gtk::TreePath&, const Gtk::TreePath& );
bool is_treepath_more( const Gtk::TreePath&, const Gtk::TreePath& );

// OTHER GTK HELPERS ===============================================================================
Gtk::MenuItem*      create_menuitem_markup( const Ustring&,
                                            const Glib::SignalProxy0< void >::SlotType& );

bool                set_widget_css( Gtk::Widget*, const Ustring& );

void                flush_gtk_event_queue();

Gdk::Rectangle      get_rectangle_from_event( const GdkEventButton* event );

bool                select_image_file( Gtk::Window&, String& );

} // end of namespace HELPERS

#endif

