#include "tableprint.hpp"
#include <iomanip>
#include <algorithm>
#include <locale>
#include <cwchar>
#include <string>
#include <sstream>
#include <codecvt>
#include <iostream>
#include <map>

enum kTextColors {
    kTextColor_Default,
    kTextColor_Red,
    kTextColor_Green,
    kTextColor_Yellow,
    kTextColor_Blue,
    kTextColor_Magenta,
    kTextColor_Cyan,
    kTextColor_White,
    kTextColor_DarkGrey,
    kTextColor_DarkYellow,
    kTextColor_LightGrey
};

struct coloredText {
    std::string text;
    kTextColors color;
};

const std::map<std::string, coloredText> kReplacements = {
    {":tick:", {"+", kTextColor_Green}},
    {":cross:", {"x", kTextColor_Red}},
    {":warning:", {"!", kTextColor_Yellow}},
    {":info:", {"i", kTextColor_Blue}},
    {":check:", {"+", kTextColor_Green}},
    {":x:", {"x", kTextColor_Red}},
    {":error:", {"!", kTextColor_Red}},
    {":question:", {"?", kTextColor_DarkGrey}}
};

// Helper function to get ANSI color code
std::string get_color_code(kTextColors color) {
    switch (color) {
        case kTextColor_Red: return "\033[1;31m";
        case kTextColor_Green: return "\033[1;32m";
        case kTextColor_Yellow: return "\033[1;33m";
        case kTextColor_Blue: return "\033[1;34m";
        case kTextColor_Magenta: return "\033[1;35m";
        case kTextColor_Cyan: return "\033[1;36m";
        case kTextColor_White: return "\033[1;37m";
        case kTextColor_DarkGrey: return "\033[90m";
        case kTextColor_DarkYellow: return "\033[38;5;142m";
        case kTextColor_LightGrey: return "\033[38;5;250m";
        default: return "\033[0m";
    }
}

int get_codepoints(const std::string& str) {
    int num_code_points = 0;
    for (char byte: str) {
        if((byte & 0xC0) != 0x80) {
            num_code_points++;
        }
    }
    return num_code_points;
}

int get_visible_length(const std::string& str) {
    int length = get_codepoints(str);

    size_t pos = 0;
    while (pos < str.length()) {
        for (const auto& [key, value] : kReplacements) {
            if (str.compare(pos, key.length(), key) == 0) {
                length = length - get_codepoints(key) + get_codepoints(value.text);
                pos += key.length();
                break;
            }
        }
        pos++;
    }
    return length;
}

std::string process_cell(std::string str,std::string rowcolor) {
    std::string result;

    size_t pos = 0;
    while (pos < str.length()) {
        bool found_replacement = false;
        for (const auto& [key, value] : kReplacements) {
            if (str.compare(pos, key.length(), key) == 0) {
                found_replacement = true;                
                result += get_color_code(value.color) + value.text + rowcolor;
                pos += key.length();
                break;
            }
        }
        if (!found_replacement) {
            result += str[pos];
            pos++;
        }
    }
    return result;
}

std::string width_print_centered(std::string str,int width, std::string rowcolor) {
    size_t padding = (width - get_visible_length(str));
    size_t lpad = padding/2;
    size_t rpad = padding - lpad;
    std::ostringstream oss;
    oss << rowcolor << std::string(lpad, ' ') << process_cell(str, rowcolor) << 
        std::string(rpad, ' ') << "\033[0m";

    // std::cout << "str = "<<str <<std::endl;
    // std::cout << "width = "<<width <<std::endl;
    // std::cout << "padding = "<<padding <<std::endl;
    // std::cout << "get_visible_length(str) = "<<get_visible_length(str) <<std::endl;
    // std::cout << "get_codepoints(str) = "<<get_codepoints(str) <<std::endl;
    // std::cout << "oss.str() = ["<<oss.str() <<"]"<<std::endl;

    return oss.str();
}

std::string width_print_left(std::string str,int width, std::string rowcolor) {
    size_t padding = (width - get_visible_length(str));
    size_t lpad = (padding>1 ? 1 : 0);
    size_t rpad = padding - lpad;
    std::ostringstream oss;
    oss << rowcolor << std::string(lpad, ' ') << process_cell(str, rowcolor)<< std::string(rpad, ' ') << "\033[0m";
    return oss.str();
}

tableprint::tableprint(const std::string title, bool compact) : title(title), mCompact(compact) {
    // Set locale for wide character support
    std::setlocale(LC_ALL, "");
}

tableprint::~tableprint() {}

void tableprint::set_title(const std::string title) {
    this->title = title;
}

void tableprint::add_row(const std::vector<std::string>& row) {
    std::vector<std::string> trimmed_row;
    for (const auto& cell : row) {
        // Trim whitespace from start and end
        auto start = cell.find_first_not_of(" \t");
        auto end = cell.find_last_not_of(" \t");
        if (start == std::string::npos) {
            trimmed_row.push_back("");
        } else {
            trimmed_row.push_back(cell.substr(start, end - start + 1));
        }
    }
    rows.push_back(trimmed_row);
}

void tableprint::print() {
    if (rows.empty()) return;

    // Calculate column widths based on header text
    std::vector<size_t> col_widths(rows[0].size(), 0);
    for (size_t i = 0; i < rows[0].size(); ++i) {
        col_widths[i] = rows[0][i].length();
    }

    // Adjust widths for any cells with replacements
    for (size_t row_idx = 1; row_idx < rows.size(); ++row_idx) {
        const auto& row = rows[row_idx];
        for (size_t i = 0; i < row.size(); ++i) {
            int l = get_visible_length(row[i]);
            if (l > col_widths[i]) 
                col_widths[i] = l;
        }
    }

    // Debug output
    // std::cerr << "Column widths: ";
    // for (size_t width : col_widths) {
    //     std::cerr << width << " ";
    // }
    // std::cerr << std::endl;

    // Calculate total table width
    size_t total_width = 0;
    for (size_t width : col_widths) {
        total_width += width + 2; // +2 for padding
    }
    total_width += col_widths.size() - 1; // Add space for vertical borders

    // Print title if it exists
    if (!title.empty()) {
        std::cout << "\033[90m"; // Dark grey color for borders
        std::cout << "+";
        for (size_t i = 0; i < rows[0].size(); ++i) {
            std::cout << std::string(col_widths[i] + 2, '-');
            if (i < rows[0].size() - 1) std::cout << "-";
        }
        std::cout << "+" << std::endl;
        
        std::cout << "|"; // White color for title
        size_t title_width = 0;
        for (size_t width : col_widths) {
            title_width += width + 2; // +2 for padding
        }
        title_width += col_widths.size() - 1; // Add space for vertical borders

        std::cout << width_print_centered(title,title_width,"\033[1;37m");
        std::cout << "\033[90m|" << std::endl;
        
        // Use └─┴─┘ for bottom of title box to connect with table
        std::cout << "+";
        for (size_t i = 0; i < rows[0].size(); ++i) {
            std::cout << std::string(col_widths[i] + 2, '-');
            if (i < rows[0].size() - 1) std::cout << "-";
        }
        std::cout << "+" << std::endl;
    } else {
        // Print top border if no title
        std::cout << "\033[90m"; // Dark grey color for borders
        std::cout << "+";
        for (size_t i = 0; i < rows[0].size(); ++i) {
            std::cout << std::string(col_widths[i] + 2, '-');
            if (i < rows[0].size() - 1) std::cout << "+";
        }
        std::cout << "+" << std::endl;
    }

    // Print header
    std::cout << "|";
    for (size_t i = 0; i < rows[0].size(); ++i) {
        std::cout << width_print_centered(rows[0][i],col_widths[i]+2,"\033[1;36m");
        if (i < rows[0].size() - 1) {
            std::cout << "\033[90m|\033[1;36m"; // Border color then back to cyan
        } else {
            std::cout << "\033[90m|"; // Just border color for last column
        }
    }
    std::cout << std::endl;

    // Print header separator
    if (!mCompact) {
        std::cout << "+";
        for (size_t i = 0; i < rows[0].size(); ++i) {
            for (size_t j = 0; j < col_widths[i] + 2; ++j) {
                std::cout << "-";
            }
            if (i < rows[0].size() - 1) std::cout << "+";
        }
        std::cout << "+" << std::endl;
    }

    // Print rows
    for (size_t row_idx = 1; row_idx < rows.size(); ++row_idx) {
        const auto& row = rows[row_idx];
        std::cout << "|";
        for (size_t i = 0; i < row.size(); ++i) {
            // Set the appropriate color for the row
            std::string rowcolor = (row_idx % 2 == 1) ? "\033[38;5;142m" : "\033[38;5;250m";
            std::cout << width_print_left(row[i],col_widths[i]+2,rowcolor);
            std::cout << "\033[90m" << "|";
        }
        std::cout << std::endl;

        // Print row separator if not the last row
        if (row_idx < rows.size() - 1 && !mCompact) {
            std::cout << "+";
            for (size_t i = 0; i < rows[0].size(); ++i) {
                for (size_t j = 0; j < col_widths[i] + 2; ++j) {
                    std::cout << "-";
                }
                if (i < rows[0].size() - 1) std::cout << "+";
            }
            std::cout << "+" << std::endl;
        }
    }

    // Print bottom border
    std::cout << "+";
    for (size_t i = 0; i < rows[0].size(); ++i) {
        for (size_t j = 0; j < col_widths[i] + 2; ++j) {
            std::cout << "-";
        }
        if (i < rows[0].size() - 1) std::cout << "+";
    }
    std::cout << "+" << "\033[0m" << std::endl; // Reset color
}