/*
 * Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
 *
 * This file is part of PortaPack.
 *
 * 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, 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; see the file COPYING.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street,
 * Boston, MA 02110-1301, USA.
 */

#include "string_format.hpp"

using namespace std::literals;

/* This takes a pointer to the end of a buffer
 * and fills it backwards towards the front.
 * The return value 'q' is a pointer to the start.
 * TODO: use std::array for all this. */
template <typename Int>
static char* to_string_dec_uint_internal(
    char* p,
    Int n) {
    *p = 0;
    auto q = p;

    do {
        *(--q) = n % 10 + '0';
        n /= 10;
    } while (n != 0);

    return q;
}

static char* to_string_dec_uint_pad_internal(
    char* const term,
    const uint32_t n,
    const int32_t l,
    const char fill) {
    auto q = to_string_dec_uint_internal(term, n);

    // Fill with padding if needed.
    // TODO: use std::array instead. There's no
    // bounds checks on any of this!
    if (fill) {
        while ((term - q) < l) {
            *(--q) = fill;
        }
    }

    return q;
}

static char* to_string_dec_uint_internal(uint64_t n, StringFormatBuffer& buffer, size_t& length) {
    auto end = &buffer.back();
    auto start = to_string_dec_uint_internal(end, n);
    length = end - start;
    return start;
}

char* to_string_dec_uint(uint64_t n, StringFormatBuffer& buffer, size_t& length) {
    return to_string_dec_uint_internal(n, buffer, length);
}

char* to_string_dec_int(int64_t n, StringFormatBuffer& buffer, size_t& length) {
    bool negative = n < 0;
    auto start = to_string_dec_uint(negative ? -n : n, buffer, length);

    if (negative) {
        *(--start) = '-';
        ++length;
    }

    return start;
}

std::string to_string_dec_int(int64_t n) {
    StringFormatBuffer b{};
    size_t len{};
    char* str = to_string_dec_int(n, b, len);
    return std::string(str, len);
}

std::string to_string_dec_uint(uint64_t n) {
    StringFormatBuffer b{};
    size_t len{};
    char* str = to_string_dec_uint(n, b, len);
    return std::string(str, len);
}

std::string to_string_bin(
    const uint32_t n,
    const uint8_t l) {
    char p[33];
    for (uint8_t c = 0; c < l; c++) {
        if (n & (1 << (l - 1 - c)))
            p[c] = '1';
        else
            p[c] = '0';
    }
    p[l] = 0;
    return p;
}

std::string to_string_dec_uint(
    const uint32_t n,
    const int32_t l,
    const char fill) {
    char p[16];
    auto term = p + sizeof(p) - 1;
    auto q = to_string_dec_uint_pad_internal(term, n, l, fill);

    // Right justify.
    // (This code is redundant and won't do anything if a fill character was specified)
    while ((term - q) < l) {
        *(--q) = ' ';
    }

    return q;
}

std::string to_string_dec_int(
    const int32_t n,
    const int32_t l,
    const char fill) {
    const size_t negative = (n < 0) ? 1 : 0;
    uint32_t n_abs = negative ? -n : n;

    char p[16];
    auto term = p + sizeof(p) - 1;
    auto q = to_string_dec_uint_pad_internal(term, n_abs, l - negative, fill);

    // Add sign.
    if (negative) {
        *(--q) = '-';
    }

    // Right justify.
    // (This code is redundant and won't do anything if a fill character was specified)
    while ((term - q) < l) {
        *(--q) = ' ';
    }

    return q;
}

std::string to_string_decimal(float decimal, int8_t precision) {
    double integer_part;
    double fractional_part;

    std::string result;

    fractional_part = modf(decimal, &integer_part) * pow(10, precision);

    if (fractional_part < 0) {
        fractional_part = -fractional_part;
    }

    result = to_string_dec_int(integer_part) + "." + to_string_dec_uint(fractional_part, precision, '0');

    return result;
}

// right-justified frequency in Hz, always 10 characters
std::string to_string_freq(const uint64_t f) {
    std::string final_str{""};

    if (f < 1000000)
        final_str = to_string_dec_int(f, 10, ' ');
    else
        final_str = to_string_dec_int(f / 1000000, 4) + to_string_dec_int(f % 1000000, 6, '0');

    return final_str;
}

// right-justified frequency in MHz, rounded to 4 decimal places, always 9 characters
std::string to_string_short_freq(const uint64_t f) {
    auto final_str = to_string_dec_int(f / 1000000, 4) + "." + to_string_dec_int(((f + 50) / 100) % 10000, 4, '0');
    return final_str;
}

// non-justified non-padded frequency in MHz, rounded to specified number of decimal places
std::string to_string_rounded_freq(const uint64_t f, int8_t precision) {
    std::string final_str{""};
    static constexpr uint32_t pow10[7] = {
        1,
        10,
        100,
        1000,
        10000,
        100000,
        1000000,
    };

    if (precision < 1) {
        final_str = to_string_dec_uint(f / 1000000);
    } else {
        if (precision > 6)
            precision = 6;

        uint32_t divisor = pow10[6 - precision];

        final_str = to_string_dec_uint(f / 1000000) + "." + to_string_dec_int(((f + (divisor / 2)) / divisor) % pow10[precision], precision, '0');
    }
    return final_str;
}

std::string to_string_time_ms(const uint32_t ms) {
    std::string final_str{""};

    if (ms < 1000) {
        final_str = to_string_dec_uint(ms) + "ms";
    } else {
        auto seconds = ms / 1000;

        if (seconds >= 60)
            final_str = to_string_dec_uint(seconds / 60) + "m";

        return final_str + to_string_dec_uint(seconds % 60) + "s";
    }

    return final_str;
}

static char* to_string_hex_internal(char* ptr, uint64_t value, uint8_t length) {
    if (length == 0)
        return ptr;

    *(--ptr) = uint_to_char(value & 0xF, 16);
    return to_string_hex_internal(ptr, value >> 4, length - 1);
}

std::string to_string_hex(uint64_t value, int32_t length) {
    constexpr uint8_t buffer_length = 33;
    char buffer[buffer_length];

    char* ptr = &buffer[buffer_length - 1];
    *ptr = '\0';

    length = std::min<uint8_t>(buffer_length - 1, length);
    return to_string_hex_internal(ptr, value, length);
}

std::string to_string_hex_array(uint8_t* array, int32_t length) {
    std::string str_return;
    str_return.reserve(length * 2);

    for (uint8_t i = 0; i < length; i++)
        str_return += to_string_hex(array[i], 2);

    return str_return;
}

std::string to_string_datetime(const rtc::RTC& value, const TimeFormat format) {
    std::string string{""};

    if (format == YMDHMS) {
        string += to_string_dec_uint(value.year(), 4) + "-" +
                  to_string_dec_uint(value.month(), 2, '0') + "-" +
                  to_string_dec_uint(value.day(), 2, '0') + " ";
    }

    string += to_string_dec_uint(value.hour(), 2, '0') + ":" +
              to_string_dec_uint(value.minute(), 2, '0');

    if ((format == YMDHMS) || (format == HMS))
        string += ":" + to_string_dec_uint(value.second(), 2, '0');

    return string;
}

std::string to_string_timestamp(const rtc::RTC& value) {
    return to_string_dec_uint(value.year(), 4, '0') +
           to_string_dec_uint(value.month(), 2, '0') +
           to_string_dec_uint(value.day(), 2, '0') +
           to_string_dec_uint(value.hour(), 2, '0') +
           to_string_dec_uint(value.minute(), 2, '0') +
           to_string_dec_uint(value.second(), 2, '0');
}

std::string to_string_FAT_timestamp(const FATTimestamp& timestamp) {
    return to_string_dec_uint((timestamp.FAT_date >> 9) + 1980) + "-" +
           to_string_dec_uint((timestamp.FAT_date >> 5) & 0xF, 2, '0') + "-" +
           to_string_dec_uint((timestamp.FAT_date & 0x1F), 2, '0') + " " +
           to_string_dec_uint((timestamp.FAT_time >> 11), 2, '0') + ":" +
           to_string_dec_uint((timestamp.FAT_time >> 5) & 0x3F, 2, '0');
}

std::string to_string_file_size(uint32_t file_size) {
    static const std::string suffix[5] = {"B", "kB", "MB", "GB", "??"};
    size_t suffix_index = 0;

    while (file_size >= 1024) {
        file_size /= 1024;
        suffix_index++;
    }

    if (suffix_index > 4)
        suffix_index = 4;

    return to_string_dec_uint(file_size) + suffix[suffix_index];
}

std::string to_string_mac_address(const uint8_t* macAddress, uint8_t length, bool noColon) {
    std::string string;

    string += to_string_hex(macAddress[0], 2);

    for (int i = 1; i < length; i++) {
        string += noColon ? to_string_hex(macAddress[i], 2) : ":" + to_string_hex(macAddress[i], 2);
    }

    return string;
}

std::string to_string_formatted_mac_address(const char* macAddress) {
    std::string formattedAddress;

    for (int i = 0; i < 12; i += 2) {
        if (i > 0) {
            formattedAddress += ':';
        }
        formattedAddress += macAddress[i];
        formattedAddress += macAddress[i + 1];
    }

    return formattedAddress;
}

void generateRandomMacAddress(char* macAddress) {
    const char hexDigits[] = "0123456789ABCDEF";

    // Generate 12 random hexadecimal characters
    for (int i = 0; i < 12; i++) {
        int randomIndex = rand() % 16;
        macAddress[i] = hexDigits[randomIndex];
    }
    macAddress[12] = '\0';  // Null-terminate the string
}

uint64_t readUntil(File& file, char* result, std::size_t maxBufferSize, char delimiter) {
    std::size_t bytesRead = 0;

    while (true) {
        char ch;
        File::Result<File::Size> readResult = file.read(&ch, 1);

        if (readResult.is_ok() && readResult.value() > 0) {
            if (ch == delimiter) {
                // Found a space character, stop reading
                break;
            } else if (bytesRead < maxBufferSize) {
                // Append the character to the result if there's space
                result[bytesRead++] = ch;
            } else {
                // Buffer is full, break to prevent overflow
                break;
            }
        } else {
            break;  // End of file or error
        }
    }

    // Null-terminate the result string
    result[bytesRead] = '\0';

    return bytesRead;
}

std::string unit_auto_scale(double n, const uint32_t base_unit, uint32_t precision) {
    const uint32_t powers_of_ten[5] = {1, 10, 100, 1000, 10000};
    std::string string{""};
    uint32_t prefix_index = base_unit;
    double integer_part;
    double fractional_part;

    precision = std::min((uint32_t)4, precision);

    while (n > 1000) {
        n /= 1000.0;
        prefix_index++;
    }

    fractional_part = modf(n, &integer_part) * powers_of_ten[precision];
    if (fractional_part < 0)
        fractional_part = -fractional_part;

    string = to_string_dec_int(integer_part);
    if (precision)
        string += '.' + to_string_dec_uint(fractional_part, precision, '0');

    if (prefix_index != 3)
        string += unit_prefix[prefix_index];

    return string;
}

double get_decimals(double num, int16_t mult, bool round) {
    num -= int(num);  // keep decimals only
    num *= mult;      // Shift decimals into integers
    if (!round) return num;
    int16_t intnum = int(num);  // Round it up if necessary
    num -= intnum;              // Get decimal part
    if (num > .5) intnum++;     // Round up
    return intnum;
}

static const char* whitespace_str = " \t\r\n";

std::string trim(std::string_view str) {
    auto first = str.find_first_not_of(whitespace_str);
    if (first == std::string::npos)
        return {};

    auto last = str.find_last_not_of(whitespace_str);
    return std::string{str.substr(first, last - first + 1)};
}

std::string trimr(std::string_view str) {
    size_t last = str.find_last_not_of(whitespace_str);
    return std::string{last != std::string::npos ? str.substr(0, last + 1) : ""};
}

std::string truncate(std::string_view str, size_t length) {
    return std::string{str.length() <= length ? str : str.substr(0, length)};
}

uint8_t char_to_uint(char c, uint8_t radix) {
    uint8_t v = 0;

    if (c >= '0' && c <= '9')
        v = c - '0';
    else if (c >= 'A' && c <= 'F')
        v = c - 'A' + 10;  // A is dec: 10
    else if (c >= 'a' && c <= 'f')
        v = c - 'a' + 10;  // A is dec: 10

    return v < radix ? v : 0;
}

char uint_to_char(uint8_t val, uint8_t radix) {
    if (val >= radix)
        return 0;

    if (val < 10)
        return '0' + val;
    else
        return 'A' + val - 10;  // A is dec: 10
}