/* util.cc
 * This file belongs to Worker, a file manager for UN*X/X11.
 * Copyright (C) 2001-2023 Ralf Hoffmann.
 * You can contact me at: ralf@boomerangsworld.de
 *   or http://www.boomerangsworld.de/worker
 *
 * This program 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 2 of the License, or
 * (at your option) any later version.
 *
 * 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
 */

#include "util.h"
#include "lowlevelfunc.h"
#include <cctype>
#include <algorithm>
#include <stdarg.h>
#include <functional>
#include <stdlib.h>
#include <cmath>
#include "utf8.hh"

int createLines( const char *text, char ***ret_lines, const char split )
{
  int lines;
  int i, p;
  short ignorenext;

  lines=1;
  ignorenext = 0;
  for(i=0;i<(int)strlen(text);i++) {
    if ( ignorenext == 0 ) {
      if ( text[i] == split ) lines++;
      else if ( text[i] == '\\' ) ignorenext = 1;
    } else {
      ignorenext = 0;
    }
  }
  char **liness=(char**)_allocsafe(sizeof(char*)*lines);
  if(lines==1) {
    liness[0]=dupstring(text);
  } else {
    char *tstr1,*tstr2;;
    tstr1=dupstring(text);
    for(i=0;i<lines;i++) {
      liness[i]=dupstring(tstr1);
      int j;
      ignorenext = 0;
      for(j=0;j<(int)strlen(liness[i]);j++) {
	if ( ignorenext == 0 ) {
	  if ( liness[i][j] == split ) {
	    liness[i][j]=0;
	    break;
	  } else if ( liness[i][j] == '\\' ) ignorenext = 1;
	} else {
	  ignorenext = 0;
	}
      }
      for ( p = j = 0; liness[i][j] != '\0'; j++ ) {
        if ( ( liness[i][j] == '\\' ) && ( liness[i][j+1] == split ) ) {
	  liness[i][p++] = liness[i][++j];
        } else if ( ( liness[i][j] == '\\' ) && ( liness[i][j+1] == '\\' ) ) {
	  liness[i][p++] = liness[i][++j];
	} else {
	  liness[i][p++] = liness[i][j];
	}
      }
      liness[i][p] = '\0';
      if((i+1)>=lines) break; //last line
      tstr2=dupstring(tstr1+j+1);
      _freesafe(tstr1);
      tstr1=tstr2;
    }
    _freesafe(tstr1);
  }
  *ret_lines=liness;
  return lines;
}

namespace AGUIXUtils
{
    std::string bytes_to_human_readable( loff_t number, int threshold )
    {
        std::string return_str;
        const struct {
            const char *suffix;
        } suffix_list[] = {
            { "B" },
            { "KB" },
            { "MB" },
            { "GB" },
            { "TB" }
        };
        const int max_levels = sizeof( suffix_list ) / sizeof( suffix_list[0] );
        int current_level = 0;

        if ( threshold < 1 ) threshold = 1;
        if ( threshold > 1024 ) threshold = 1024;

        for ( current_level = 0; current_level < ( max_levels - 1 ); current_level++ ) {
            if ( number < threshold * 1024 ) break;

            // advance to next level
            number /= 1024;
        }

        MakeLong2NiceStr( number, return_str, false );

        return_str += " ";
        return_str += suffix_list[current_level].suffix;

        return return_str;
    }
    
    std::string tolower( const std::string &str )
    {
        std::string res( str );
        
        std::transform( res.begin(),
                        res.end(),
                        res.begin(),
                        [](auto c) { return std::tolower( c ); } );
        return res;
    }

    bool stringIsShorter( const char *str, int len )
    {
        if ( str == NULL ) return true;
        
        int my_len;
        
        for ( my_len = 0; str[my_len] != '\0' && my_len < len; my_len++ );
        
        if ( my_len < len ) return true;
        return false;
    }

    std::string formatStringToString( const char *format, ... )
    {
        va_list va;
        int n, size = 1024;
        char *tstr;
        std::string string1;
        
        if ( format != NULL ) {
            // first create str
            while ( 1 ) {
                tstr = (char*)_allocsafe( size );
                va_start( va, format );
                n = vsnprintf( tstr, size, format, va );
                va_end( va );
                if ( n > -1 && n < size ) break;
                _freesafe( tstr );
                size *= 2;
            }
            string1 = tstr;
            _freesafe( tstr );
            return string1;
        }
        return "";
    }

    loff_t convertHumanStringToNumber( const std::string &str )
    {
        struct {
            const char *suffix;
            int mod;
        } suffix_list[] = {
            { "gib", 1024*1024*1024 },
            { "mib", 1024*1024 },
            { "kib", 1024 },
            { "gb", 1024*1024*1024 },
            { "mb", 1024*1024 },
            { "kb", 1024 },
            { "b", 1 },
            { "k", 1000 },
            { "m", 1000000 },
            { "g", 1000000000 }
        };

        char *rest = NULL;
        double erg = 0.0;
        
        erg = strtod( str.c_str(), &rest );
        
        if ( rest != NULL && *rest != '\0' ) {
            while ( *rest != '\0' ) {
                if ( std::isspace( *rest ) ) rest++;
                else break;
            }

            if ( *rest != '\0' ) {
                std::string lstr = tolower( rest );
                for ( unsigned int i = 0; i < sizeof( suffix_list ) / sizeof( suffix_list[0] ); i++ ) {
                    if ( strcmp( lstr.c_str(), suffix_list[i].suffix ) == 0 ) {
                        erg *= suffix_list[i].mod;
                        break;
                    }
                }
            }
        }
        
        return (loff_t)erg;
    }

    std::string bytes_to_human_readable_f( loff_t number,
                                           int threshold,
                                           int precision,
                                           byte_multiplier_t suffix_format,
                                           loff_t precision_hint )
    {
        return bytes_to_human_readable_f( (double)number,
                                          threshold,
                                          precision,
                                          suffix_format,
                                          (double)precision_hint );
    }

    std::string bytes_to_human_readable_f( double number,
                                           int threshold,
                                           int precision,
                                           byte_multiplier_t suffix_format,
                                           double precision_hint )
    {
        std::string return_str;
        struct {
            const char *suffix;
            double mod;
            byte_multiplier_t suffix_format;
        } suffix_list[] = {
            { "TiB", 1024.0 * 1024.0 * 1024.0 * 1024.0, BM_IEC },
            { "TB", 1024.0 * 1024.0 * 1024.0 * 1024.0, BM_TRADITIONAL },
            { "TB", 1000.0 * 1000.0 * 1000.0 * 1000.0, BM_SI },
            { "GiB", 1024.0 * 1024.0 * 1024.0, BM_IEC },
            { "GB", 1024.0 * 1024.0 * 1024.0, BM_TRADITIONAL },
            { "GB", 1000.0 * 1000.0 * 1000.0, BM_SI },
            { "MiB", 1024.0 * 1024.0, BM_IEC },
            { "MB", 1024.0 * 1024.0, BM_TRADITIONAL },
            { "MB", 1000.0 * 1000.0, BM_SI },
            { "KiB", 1024.0, BM_IEC },
            { "KB", 1024.0, BM_TRADITIONAL },
            { "KB", 1000.0, BM_SI },
            { "B", 1.0, BM_TRADITIONAL }
        };

        const int max_levels = sizeof( suffix_list ) / sizeof( suffix_list[0] );
        int current_level = 0;

        if ( threshold < 1 ) threshold = 1;
        if ( threshold > 1000 ) threshold = 1000;
        if ( precision < 0 || precision > 17 ) {
            precision = 1;
        }

        if ( suffix_format == BM_SYSTEM_SETTING ) {
            //TODO get system setting
            suffix_format = BM_IEC;
        }

        for ( current_level = 0; current_level < ( max_levels - 1 ); current_level++ ) {
            if ( suffix_list[current_level].suffix_format == suffix_format ) {
                if ( number >= suffix_list[current_level].mod ) {
                    double t = number / suffix_list[current_level].mod;
                    if ( t >= (double)threshold ) break;
                }
            }
        }

        if ( current_level == ( max_levels - 1 ) ) {
            precision = 0;
        }

        double f = number / suffix_list[current_level].mod;
        double new_precision_hint = precision_hint / suffix_list[current_level].mod;

        if ( new_precision_hint > 0 ) {
            double variable_precision = log( new_precision_hint ) / log( 10 );

            if ( variable_precision >= 0 ) {
                precision = 0;
            } else {
                variable_precision = ceil( - variable_precision );
                if ( variable_precision < (double)precision ) {
                    precision = (int)variable_precision;
                }
            }
        }

        char formatStr[ sizeof( "%.xxf %s" ) + 1 ];

        snprintf( formatStr, sizeof( formatStr ), "%%.%df %%s", precision );
        formatStr[ sizeof( formatStr ) - 1 ] = '\0';

        return_str = formatStringToString( formatStr, f, suffix_list[current_level].suffix );

        return return_str;
    }

    int calc_precision( double v )
    {
        int precision = 0;
        
        if ( v > 0 ) {
            double variable_precision = log( v ) / log( 10 );
            
            if ( variable_precision >= 0 ) {
                precision = 0;
            } else {
                variable_precision = ceil( - variable_precision );
                precision = (int)variable_precision;
            }
        }
        
        return precision;
    }

    void split_string( std::vector< std::string > &v,
                       const std::string &str1,
                       const char split_element )
    {
        std::string::size_type start_pos = 0;

        if ( start_pos >= str1.length() ) {
            // empty string
            v.push_back( str1 );
            return;
        }

        std::string::size_type next_pos = str1.find( split_element, start_pos );

        if ( next_pos == std::string::npos ) {
            // no split_element found
            v.push_back( str1 );
            return;
        }

        // now there is at least one split element

        while ( start_pos >= 0 && start_pos < str1.length() ) {
            next_pos = str1.find( split_element, start_pos );

            int basename_length;

            if ( next_pos == std::string::npos ) {
                basename_length = str1.length() - start_pos;
            } else {
                basename_length = next_pos - start_pos;
            }

            std::string basename( str1, start_pos, basename_length );

            v.push_back( basename );

            if ( next_pos == std::string::npos ) {
                break;
            }

            start_pos = next_pos + 1;
        }

        if ( start_pos >= str1.length() ) {
            // ended with split_element so push empty string
            v.push_back( "" );
        }
    }

    bool starts_with( const std::string &str,
                      const std::string &prefix )
    {
        if ( str.length() >= prefix.length() &&
             str.compare( 0, prefix.length(), prefix ) == 0 ) {
            return true;
        }

        return false;
    }

    bool ends_with( const std::string &str,
                    const std::string &suffix )
    {
        if ( str.length() >= suffix.length() &&
             str.compare( str.length() - suffix.length(),
                          suffix.length(), suffix ) == 0 ) {
            return true;
        }

        return false;
    }

    bool contains( const std::string &str,
                   const std::string &infix )
    {
        if ( str.find( infix ) != std::string::npos ) {
            return true;
        }

        return false;
    }

    void rstrip( std::string &str )
    {
        if ( str.empty() ) return;

        int pos = str.length() - 1;

        for ( pos = str.length() - 1;
              pos >= 0 && str[pos] == ' ';
              pos-- );

        str.resize( pos + 1 );
    }

    bool char_in_string( const char ch,
                         const std::string &str )
    {
        for ( auto &str_ch : str ) {
            if ( ch == str_ch ) return true;
        }

        return false;
    }
    
    void rstrip( std::string &str, const std::string &strip_chars )
    {
        if ( str.empty() ) return;

        int pos = str.length() - 1;

        for ( pos = str.length() - 1;
              pos >= 0 && char_in_string( str[pos], strip_chars );
              pos-- );

        str.resize( pos + 1 );
    }

    void strip( std::string &str )
    {
        if ( str.empty() ) return;

        int pos = str.length() - 1;

        for ( pos = str.length() - 1;
              pos >= 0 && str[pos] == ' ';
              pos-- );

        str.resize( pos + 1 );

        for ( pos = 0;
              pos < (int)str.length() && str[pos] == ' ';
              pos++ );

        if ( pos > 0 ) {
            std::string nv( str, pos );
            str = nv;
        }
    }

    std::string remove_char( const std::string &str,
                             char ch )
    {
        std::string res;
        const char *in = str.c_str();

        for ( ; *in != '\0'; ) {
            size_t l = UTF8::getLenOfCharacter( in );

            if ( l < 1 ) {
                break;
            } if ( l == 1 && *in == ch ) {
                // skip
                in++;
            } else {
                for ( size_t t = 0; t < l; t++ ) {
                    res += *in++;
                }
            }
        }
        return res;
    }

    std::string timeToString( time_t t )
    {
        char buf[128];
        struct tm converted_tm = { 0 };

        localtime_r( &t, &converted_tm );
    
        if ( strftime( buf, sizeof( buf ),
                       "%Y-%m-%d %H:%M:%S", &converted_tm ) > 0 ) {
            return std::string( buf );
        }

        return "";
    }

    bool parseStringToTime( const std::string &time_string,
                            time_t *return_time )
    {
        struct tm parsed_tm = { 0 };
        time_t converted_time;

        parsed_tm.tm_isdst = -1;

        if ( strptime( time_string.c_str(),
                       "%Y-%m-%d %H:%M:%S",
                       &parsed_tm ) == NULL ) {
            if ( strptime( time_string.c_str(),
                           "%Y-%m-%d",
                           &parsed_tm ) == NULL ) {
                return false;
            }
        }

        converted_time = mktime( &parsed_tm );

        if ( converted_time == (time_t)-1 ) {
            return false;
        }

        if ( return_time ) {
            *return_time = converted_time;
        }

        return true;
    }

    std::string escape_uri( const std::string &uri )
    {
        std::string res;

        for ( auto &ch : uri ) {
            if ( std::isalnum( ch ) ||
                 ch == '-' ||
                 ch == '_' ||
                 ch == '.' ||
                 ch == '!' ||
                 ch == '~' ||
                 ch == '*' ||
                 ch == '\'' ||
                 ch == '(' ||
                 ch == ')' ||
                 ch == '/' ) {
                res += ch;
            } else {
                res += formatStringToString( "%%%02x", (unsigned char)ch );
            }
        }

        return res;
    }

    std::string unescape_uri( const std::string &uri )
    {
        std::string res;

        for ( size_t pos = 0; pos < uri.length(); pos++ ) {
            if ( uri[pos] != '%' ) {
                res += uri[pos];
            } else {
                if ( pos + 2 >= uri.length() ) return "";

                int ch[2] = { std::tolower( uri[ pos + 1 ] ),
                              std::tolower( uri[ pos + 2 ] ) };
                char unescaped_ch = 0;

                for ( size_t run = 0; run < 2; run++ ) {
                    unescaped_ch <<= 4;

                    if ( ch[run] >= '0' && ch[run] <= '9' ) {
                        unescaped_ch |= ch[run] - '0';
                    } else if ( ch[run] >= 'a' && ch[run] <= 'f' ) {
                        unescaped_ch |= 10 + ch[run] - 'a';
                    } else {
                        return "";
                    }
                }

                res += unescaped_ch;

                pos += 2;
            }
        }

        return res;
    }

    int escape_characters( const std::string &input,
                           std::string &output,
                           escape_type_t type )
    {
        if ( type != ESCAPE_JSON ) return -EINVAL;

        output.clear();

        // escape ", \, newline, cr, tab
        for ( auto &ch : input ) {
            switch ( ch ) {
                case '"':
                case '\\':
                    output += "\\";
                    output += ch;
                    break;
                case '\n':
                    output += "\\n";
                    break;
                case '\r':
                    output += "\\r";
                    break;
                case '\t':
                    output += "\\t";
                    break;
                default:
                    output += ch;
                    break;
            }
        }

        return 0;
    }

    int unescape_characters( const std::string &input,
                             std::string &output,
                             escape_type_t type )
    {
        if ( type != ESCAPE_JSON ) return -EINVAL;

        output.clear();

        for ( size_t pos = 0; pos < input.length(); pos++ ) {
            char ch = input[pos];

            if ( ch != '\\' ) {
                output += ch;
                continue;
            }

            if ( pos + 1 >= input.length() ) {
                fprintf( stderr, "Missing character at end of string: %s\n", input.c_str() );
                return -EINVAL;
            }

            char ch2 = input[pos + 1];

            if ( ch2 == '"' || ch2 == '\\' ) {
                output += ch2;
            } else if ( ch2 == 'n' ) {
                output += '\n';
            } else if ( ch2 == 'r' ) {
                output += '\r';
            } else if ( ch2 == 't' ) {
                output += '\t';
            } else {
                fprintf( stderr, "Invalid escaped character %d (%c)\n", ch2, ch2 );
                return -EINVAL;
            }
            pos++;
        }

        return 0;
    }

    std::string replace_percent_s_quoted( const std::string &src,
                                          const std::string &replacement,
                                          std::string *return_suffix )
    {
        // TODO it would be more accurate to check for backslashed %
        // and so on to find the real, unprotected %s
        size_t p = src.find( "%s" );

        if ( p == std::string::npos ) {
            return src;
        }

        std::string prefix( src, 0, p );
        std::string suffix( src, p + 2 );

        std::string res = AGUIX_catTrustedAndUnTrusted2( prefix,
                                                         replacement );

        if ( return_suffix ) {
            *return_suffix = suffix;
        } else {
            res += suffix;
        }

        return res;
    }

    uint64_t current_time_ms()
    {
        struct timespec ts;

        if ( clock_gettime( CLOCK_MONOTONIC, &ts ) != 0 ) return 0;

        uint64_t res = ts.tv_sec * 1000;
        res += ts.tv_nsec / 1000000;

        return res;
    }
}
