/*
 * Copyright (C) 2015 by Multi-Tech Systems
 *
 * This file is part of libmts.
 *
 * libmts is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * libmts 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with libmts.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

#include <mts/MTS_Text.h>
//#include <mts/MTS_Logger.h>
#include <algorithm>
#include <sstream>
#include <iostream>
#include <limits>

using namespace MTS;

//strftime
//%a    Abbreviated weekday name *          Thu
//%A    Full weekday name *                 Thursday
//%b    Abbreviated month name *            Aug
//%B    Full month name *   August
//%c    Date and time representation *      Thu Aug 23 14:55:02 2001
//%d    Day of the month (01-31)            23
//%H    Hour in 24h format (00-23)          14
//%I    Hour in 12h format (01-12)          02
//%j    Day of the year (001-366)           235
//%m    Month as a decimal number (01-12)   08
//%M    Minute (00-59)                      55
//%p    AM or PM designation                PM
//%S    Second (00-61)                      02
//%U    Week number with the first Sunday as the first day of week one (00-53)  33
//%w    Weekday as a decimal number with Sunday as 0 (0-6)  4
//%W    Week number with the first Monday as the first day of week one (00-53)  34
//%x    Date representation *               08/23/01
//%X    Time representation *               14:55:02
//%y    Year, last two digits (00-99)       01
//%Y    Year                                2001
//%Z    Timezone name or abbreviation       CDT
//%%    A % sign                            %

//Example
///* strftime example */
//#include <stdio.h>
//#include <time.h>
//
//int main ()
//{
//  time_t rawtime;
//  struct tm * timeinfo;
//  char buffer [80];
//
//  time ( &rawtime );
//  timeinfo = localtime ( &rawtime );
//
//  strftime (buffer,80,"Now it's %I:%M%p.",timeinfo);
//  puts (buffer);
//
//  return 0;
//}

std::string Text::time(const uint64_t& iTimeMicros, const TIMEFORMAT& eTime) {
    int64_t now = iTimeMicros / 1000;
    int64_t millis = now % 1000;
    int64_t secs = (now / 1000) % 60;
    int64_t mins = (now / 60000) % 60;
    int64_t hours = (now / 3600000) % 24;
    std::stringstream sBuffer;

    switch (eTime) {
        case HHcMMcSScmmm:
            sBuffer << hours << ":" << mins << ":" << secs << ":" << millis;
            return sBuffer.str();
            break;
        case HHcMMcSS:
            sBuffer << (hours < 10 ? "0" : "") << hours << ":" << (mins < 10 ? "0" : "") << mins << ":" << (secs < 10 ? "0" : "") << secs;
            return sBuffer.str();
            break;

    }
    return std::string("UNKNOWN-TIME-FORMAT");
}

std::string Text::date(const tm& stTime, const DATEFORMAT& eDate) {
    char buffer[80];

    switch (eDate) {
        case MMsDDsYY:
            strftime(buffer, 80, "%x", &stTime);
            return std::string(buffer);
            break;
        case MMsDDsYY_HHcMMcSS:
            strftime(buffer, 80, "%m/%d/%Y %H:%M:%S", &stTime);
            return std::string(buffer);
            break;
        case RFC_1123:
            std::stringstream sBuffer;
            //Thu, 01 Jan 1970 00:00:01 GMT
            //Week
            switch (stTime.tm_wday) {
                case 0:
                    sBuffer << "Sun, ";
                    break;
                case 1:
                    sBuffer << "Mon, ";
                    break;
                case 2:
                    sBuffer << "Tue, ";
                    break;
                case 3:
                    sBuffer << "Wed, ";
                    break;
                case 4:
                    sBuffer << "Thu, ";
                    break;
                case 5:
                    sBuffer << "Fri, ";
                    break;
                case 6:
                    sBuffer << "Sat, ";
                    break;
            }

            //Day of Month
            sBuffer << (stTime.tm_mday < 10 ? "0" : "") << stTime.tm_mday;

            //Month
            switch (stTime.tm_mon) {
                case 0:
                    sBuffer << " Jan ";
                    break;
                case 1:
                    sBuffer << " Feb ";
                    break;
                case 2:
                    sBuffer << " Mar ";
                    break;
                case 3:
                    sBuffer << " Apr ";
                    break;
                case 4:
                    sBuffer << " May ";
                    break;
                case 5:
                    sBuffer << " Jun ";
                    break;
                case 6:
                    sBuffer << " Jul ";
                    break;
                case 7:
                    sBuffer << " Aug ";
                    break;
                case 8:
                    sBuffer << " Sep ";
                    break;
                case 9:
                    sBuffer << " Oct ";
                    break;
                case 10:
                    sBuffer << " Nov ";
                    break;
                case 11:
                    sBuffer << " Dec ";
                    break;
            }

            sBuffer << (stTime.tm_year + 1900);
            sBuffer << (stTime.tm_hour < 10 ? " 0" : " ") << stTime.tm_hour << ":" << (stTime.tm_min < 10 ? "0" : "") << stTime.tm_min << ":" << (stTime.tm_sec < 10 ? "0" : "")
                    << stTime.tm_sec;
            sBuffer << " GMT";
            return sBuffer.str();
            break;
    }
    return std::string("UNKNOWN-DATE-FORMAT");
}

bool Text::datetimeIsBefore(const std::string& time1, const std::string& time2) {

    struct tm tm1;
    struct tm tm2;

    strptime(time1.c_str(), "%x %T", &tm1);
    strptime(time2.c_str(), "%x %T", &tm2);

    return difftime(mktime(&tm2), mktime(&tm1)) > 0;
}

std::vector<std::string> Text::split(const std::string& str, char delimiter, int limit) {
    return split(str, std::string(1, delimiter), limit);
}

std::vector<std::string> Text::split(const std::string& str, const std::string& delimiter, int limit) {
    std::vector<std::string> result;
    if(str.size() == 0) {
        return result;
    }
    if (limit <= 0) {
        limit = std::numeric_limits<int>::max();
    }
    size_t start = 0;
    size_t end = str.find(delimiter, start);
    for (int i = 1; (i < limit) && (end != std::string::npos); ++i) {
        result.push_back(str.substr(start, end - start));
        start = end + delimiter.length();
        end = str.find(delimiter, start);
    }
    result.push_back(str.substr(start));
    return result;
}

std::string Text::join(std::vector<std::string> list, char delimiter) {

    std::stringstream ss;
    for (uint32_t i = 0; i < list.size(); i++) {
        ss << list[i];
        if (i < list.size() - 1)
            ss << delimiter;
    }
    return ss.str();
}

std::string Text::replace(const std::string& original, const std::string& from, const std::string& to) {
    size_t start_pos = 0;
    std::string str(original);
    while ((start_pos = str.find(from, start_pos)) != std::string::npos) {
        str.replace(start_pos, from.length(), to);
        start_pos += to.length(); // In case 'to' contains 'from', like replacing 'x' with 'yx'
    }
    return str;
}

uint32_t Text::count(const std::string& str, const std::string& element) {
    uint32_t count = 0;
    size_t start_pos = 0;
    while ((start_pos = str.find(element, start_pos)) != std::string::npos) {
        start_pos++;
        count++;
    }
    return count;
}

uint32_t Text::count(const std::string& str, const uint8_t& element) {
    uint32_t count = 0;
    size_t start_pos = 0;
    while ((start_pos = str.find(element, start_pos)) != std::string::npos) {
        start_pos++;
        count++;
    }
    return count;
}

bool Text::endsWith(const std::string& str, const std::string& key) {
    if (key.size() > str.size()) return false;
    return std::equal(str.begin() + str.size() - key.size(), str.end(), key.begin());
}

std::string Text::getLine(const std::string& source, const size_t& start, size_t& cursor) {
    char delimiters[2];
    delimiters[0] = '\n';
    delimiters[1] = '\r';
    if(start > source.size()) {
        cursor = std::string::npos;
        return "";
    }
    size_t end = source.find_first_of(delimiters, start, 2);
    std::string line(source.substr(start, end - start));
    //printDebug("[TEXT] Start [%d] Cursor[%d] Size[%d] Line: %s", start, end, source.size(), line.c_str());
    if (end < source.size()) {
        if (end < source.size() - 1)
            if ((source[end] == '\n' && source[end + 1] == '\r') || (source[end] == '\r' && source[end + 1] == '\n')) {
                //Advance an additional character in scenarios where lines end in \r\n or \n\r
                end++;
            }
        end++;
    }
    cursor = end;
    return line;
}

std::string Text::trim(const std::string& str) {
    size_t length = str.length();
    size_t start = length;
    for (size_t i = 0; i < length; ++i) {
        if (!isspace(static_cast<unsigned char>(str[i]))) {
            start = i;
            break;
        }
    }
    size_t end = std::string::npos;
    for (size_t i = length - 1; i != std::string::npos; --i) {
        if (!isspace(static_cast<unsigned char>(str[i]))) {
            end = i + 1;
            break;
        }
    }
    return str.substr(start, end - start);
}

std::string Text::trim(const std::string& str, const uint8_t& element) {
    size_t length = str.length();
    size_t start = length;
    for (size_t i = 0; i < length; ++i) {
        if (static_cast<unsigned char>(str[i]) != element) {
            start = i;
            break;
        }
    }
    size_t end = std::string::npos;
    for (size_t i = length - 1; i != std::string::npos; --i) {
        if (static_cast<unsigned char>(str[i]) != element) {
            end = i + 1;
            break;
        }
    }
    return str.substr(start, end - start);
}


std::string Text::strip(const std::string& str, const std::vector<uint8_t>& elements) {
    std::string result(str);
    for (uint32_t i = 0; i < elements.size(); i++) {
      result.erase (std::remove(result.begin(), result.end(), elements[i]), result.end());
    }
    return result;
}

std::string Text::strip(const std::string& str, const uint8_t& element) {
    std::string result(str);
    result.erase (std::remove(result.begin(), result.end(), element), result.end());
    return result;
}

std::string Text::toLowerCase(const std::string& str) {
    std::string result(str);
    size_t length = result.length();
    for (size_t i = 0; i < length; ++i) {
        result[i] = static_cast<char>(tolower(static_cast<unsigned char>(result[i])));
    }
    return result;
}

std::string Text::toUpperCase(const std::string& str) {
    std::string result(str);
    size_t length = result.length();
    for (size_t i = 0; i < length; ++i) {
        result[i] = static_cast<char>(toupper(static_cast<unsigned char>(result[i])));
    }
    return result;
}

std::string Text::toCapitalized(const std::string& str) {
    std::string result;
    result = str;
    if (str.size() == 0)
        return result;
    else if (str.size() == 1)
        return toUpperCase(result);
    else
        return toUpperCase(result.substr(0, 1)) + toLowerCase(result.substr(1));
}

std::string Text::toCamelCase(const std::string& str) {
    std::string result;
    if (str.size() == 0) {
        return result;
    }

    std::vector<std::string> parts = MTS::Text::split(str, ' ');

    if (parts.size() == 0 || parts[0].size() == 0) {
        return result;
    }
    result.append(MTS::Text::toLowerCase(parts[0]));

    for (uint32_t i = 1; i < parts.size(); i++) {
        result.append(MTS::Text::toCapitalized(parts[i]));
    }

    return result;
}

std::string Text::toCommandLineEscaped(const std::string& str) {
    std::string result(str);
    result = Text::replace(result, "#", "\\#");
    result = Text::replace(result, ".", "\\.");
    result = Text::replace(result, "&", "\\&");
    result = Text::replace(result, "?", "\\?");
    return result;
}

std::wstring Text::widen(const std::string& str) {
    std::wstringstream wss;
    size_t length = str.length();
    for (size_t i = 0; i < length; ++i) {
        wss << wss.widen(str[i]);
    }
    return wss.str();
}

std::string Text::narrow(const std::wstring& str) {
    std::wstringstream wss;
    std::stringstream ss;
    size_t length = str.length();
    for (size_t i = 0; i < length; ++i) {
        ss << wss.narrow(str[i], '?');
    }
    return ss.str();
}

std::string Text::format(bool value) {
    std::stringstream ss;
    ss.setf(ss.boolalpha);
    ss << value;
    return ss.str();
}

std::string Text::format(double value, std::streamsize percision) {
    std::stringstream ss;
    ss.setf(ss.fixed, ss.floatfield);
    ss.precision(percision);
    ss << value;
    std::string str = ss.str();
    // strip trailing zeros
    size_t pos = str.find_last_not_of('0');
    if (pos != std::string::npos) {
        if (str[pos] == '.') {
            pos += 2;
        } else {
            pos += 1;
        }
    }
    return str.substr(0, pos);
}

std::string Text::format(int8_t value) {
    return format(static_cast<int64_t>(value));
}

std::string Text::format(int16_t value) {
    return format(static_cast<int64_t>(value));
}

std::string Text::format(int32_t value) {
    return format(static_cast<int64_t>(value));
}

std::string Text::format(int64_t value) {
    std::stringstream ss;
    ss.setf(ss.dec, ss.basefield);
    ss << value;
    return ss.str();
}

std::string Text::format(uint8_t value) {
    return format(static_cast<uint64_t>(value));
}

std::string Text::format(uint16_t value) {
    return format(static_cast<uint64_t>(value));
}

std::string Text::format(uint32_t value) {
    return format(static_cast<uint64_t>(value));
}

std::string Text::format(uint64_t value) {
    std::stringstream ss;
    ss.setf(ss.dec, ss.basefield);
    ss << value;
    return ss.str();
}

static std::string toHexString(uint64_t value, bool pad, std::streamsize padSize) {
    std::stringstream ss;
    ss.unsetf(ss.showbase);
    ss.setf(ss.hex, ss.basefield);
    ss.setf(ss.uppercase);
    if (pad) {
        ss.width(padSize);
        ss.fill('0');
    }
    ss << value;
    return ss.str();
}

std::string Text::formatHex(int8_t value, bool pad) {
    return formatHex(static_cast<uint8_t>(value), pad);
}

std::string Text::formatHex(int16_t value, bool pad) {
    return formatHex(static_cast<uint16_t>(value), pad);
}

std::string Text::formatHex(int32_t value, bool pad) {
    return formatHex(static_cast<uint32_t>(value), pad);
}

std::string Text::formatHex(int64_t value, bool pad) {
    return formatHex(static_cast<uint64_t>(value), pad);
}

std::string Text::formatHex(uint8_t value, bool pad) {
    return toHexString(value, pad, 2);
}

std::string Text::formatHex(uint16_t value, bool pad) {
    return toHexString(value, pad, 4);
}

std::string Text::formatHex(uint32_t value, bool pad) {
    return toHexString(value, pad, 8);
}

std::string Text::formatHex(uint64_t value, bool pad) {
    return toHexString(value, pad, 16);
}

std::string Text::formatHex(const Buffer& value) {
    char hex[4];
    std::string hexstr;
    for (uint32_t i = 0; i < value.getSize(); i++) {
        sprintf(hex, "%X", value.at(i));
        hexstr.append(hex);
    }
    return hexstr;
}

bool Text::parse(bool& value, const std::string& str) {
    if (str.empty()) {
        return false;
    }
    std::istringstream ss(str);
    ss.setf(ss.boolalpha);
    ss.unsetf(ss.skipws);
    ss >> value;
    return ((uint8_t) ss.rdstate() == RDSTATE_SUCCESS_MASK);
}

bool Text::parse(double& value, const std::string& str) {
    if (str.empty()) {
        return false;
    }
    std::istringstream ss(str);
    ss.setf(ss.fixed, ss.basefield);
    ss.unsetf(ss.skipws);
    ss >> value;
    return ((uint8_t) ss.rdstate() == RDSTATE_SUCCESS_MASK);
}

bool Text::parse(int8_t& value, const std::string& str) {
    int64_t i64 = 0;
    if (!parse(i64, str)) {
        return false;
    }
    if (i64 < INT8_MIN || i64 > INT8_MAX) {
        return false;
    }
    value = static_cast<int8_t>(i64);
    return true;
}

bool Text::parse(int16_t& value, const std::string& str) {
    int64_t i64 = 0;
    if (!parse(i64, str)) {
        return false;
    }
    if (i64 < INT16_MIN || i64 > INT16_MAX) {
        return false;
    }
    value = static_cast<int16_t>(i64);
    return true;
}

bool Text::parse(int32_t& value, const std::string& str) {
    int64_t i64 = 0;
    if (!parse(i64, str)) {
        return false;
    }
    if (i64 < INT32_MIN || i64 > INT32_MAX) {
        return false;
    }
    value = static_cast<int32_t>(i64);
    return true;
}

bool Text::parse(int64_t& value, const std::string& str) {
    if (str.empty()) {
        return false;
    }
    std::istringstream ss(str);
    ss.setf(ss.dec, ss.basefield);
    ss.unsetf(ss.skipws);
    ss >> value;
    return ((uint8_t) ss.rdstate() == RDSTATE_SUCCESS_MASK);
}

bool Text::parse(uint8_t& value, const std::string& str) {
    uint64_t u64 = 0;
    if (!parse(u64, str)) {
        return false;
    }
    if (u64 > UINT8_MAX) {
        return false;
    }
    value = static_cast<uint8_t>(u64);
    return true;
}

bool Text::parse(uint16_t& value, const std::string& str) {
    uint64_t u64 = 0;
    if (!parse(u64, str)) {
        return false;
    }
    if (u64 > UINT16_MAX) {
        return false;
    }
    value = static_cast<uint16_t>(u64);
    return true;
}

bool Text::parse(uint32_t& value, const std::string& str) {
    uint64_t u64 = 0;
    if (!parse(u64, str)) {
        return false;
    }
    if (u64 > UINT32_MAX) {
        return false;
    }
    value = static_cast<uint32_t>(u64);
    return true;
}

bool Text::parse(uint64_t& value, const std::string& str) {
    if (str.empty()) {
        return false;
    }
    std::istringstream ss(str);
    ss.setf(ss.dec, ss.basefield);
    ss.unsetf(ss.skipws);
    ss >> value;
    return ((uint8_t) ss.rdstate() == RDSTATE_SUCCESS_MASK);
}

bool Text::parseHex(uint8_t& value, const std::string& str) {
    uint64_t u64 = 0;
    if (!parseHex(u64, str)) {
        return false;
    }
    if (u64 > UINT8_MAX) {
        return false;
    }
    value = static_cast<uint8_t>(u64);
    return true;
}

bool Text::parseHex(uint16_t& value, const std::string& str) {
    uint64_t u64 = 0;
    if (!parseHex(u64, str)) {
        return false;
    }
    if (u64 > UINT16_MAX) {
        return false;
    }
    value = static_cast<uint16_t>(u64);
    return true;
}

bool Text::parseHex(uint32_t& value, const std::string& str) {
    uint64_t u64 = 0;
    if (!parseHex(u64, str)) {
        return false;
    }
    if (u64 > UINT32_MAX) {
        return false;
    }
    value = static_cast<uint32_t>(u64);
    return true;
}

bool Text::parseHex(uint64_t& value, const std::string& str) {

    if (str.empty()) {
        return false;
    }
    std::istringstream ss(str);
    ss >> std::hex >> value;
    return ((uint8_t) ss.rdstate() == RDSTATE_SUCCESS_MASK);
}