#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <iterator>
#include <unistd.h>

#include "ktype.h"
#include "show.h"
#include "util.h"
#include "log.h"
#include "file.h"

bool color_enabled(const std::ostream& os)
{
    // Function-local statics: isatty is asked once per descriptor, on first
    // use, so there is no static-initialization-order question and no syscall
    // per colour written.
    static const bool no_color = [] {
        const char* v = std::getenv("NO_COLOR");
        return v != nullptr && v[0] != '\0';
    }();
    static const bool tty_out = isatty(fileno(stdout)) != 0;
    static const bool tty_err = isatty(fileno(stderr)) != 0;
    if (no_color) {
        return false;
    }
    if (&os == &std::cout) {
        return tty_out;
    }
    if (&os == &std::cerr || &os == &std::clog) {
        return tty_err;
    }
    return false;
}

std::ostream& operator<<(std::ostream& os, const Color& color)
{
    if (color_enabled(os)) {
        os << color.code();
    }
    return os;
}

std::ostream& msg(Locator loc)
{
    std::cout << blue << loc.abbrev(false) << black << "  ";
    return std::cout;
}

std::ostream& fmsg(Locator loc)
{
    std::cout << blue << loc.abbrev(false) << black
              << kall << ktype << kindex << kreplaced << "  ";
    return std::cout;
}

// std::vector<int>

std::ostream& operator<<(std::ostream& os, const std::vector<int>& ns)
{
    if (!ns.empty()) {
        auto rest = std::vector<int>(ns.begin() + 1, ns.end());
        os << ns[0];
        for (auto n : rest) {
            os << ", " << n;
        }
    }
    return os;
}

// strings_t

std::ostream& operator<<(std::ostream& os, const strings_t& ss)
{
    if (!ss.empty()) {
        for (const std::string& s : ss) {
            os << "<" << s << ">";
        }
    }
    return os;
}

// std::vector<fs::path>

std::ostream& operator<<(std::ostream& os, const std::vector<fs::path>& pp)
{
    if (!pp.empty()) {
        for (const fs::path& p : pp) {
            os << "<" << p.string() << ">";
        }
    }
    return os;
}


// std::map<std::string,std::string>

std::ostream& operator<<(std::ostream& os, const std::map<std::string,std::string>& sm)
{
    size_t name_length = 0;
    for (auto [k,v] : sm) {
        name_length = std::max(k.size(), name_length);
    }
    for (auto [k,v] : sm) {
        os << std::setfill(' ') << "  " << std::setw(name_length)
           << k << sp_arrow << " " << display_string(v) << "\n";
    }
    return os;
}

// Katom iterator pair
std::ostream& operator<<(std::ostream& os, const std::pair<katom_iter,katom_iter>& iters)
{
    for (auto e = iters.first; e < iters.second; e++) {
        std::cout << e;
    }
    std::cout << "\n";
    return os;
}

// Locator

std::ostream& operator<<(std::ostream& os, const Locator& loc)
{
    os << loc.str();
    return os;
}

// Katom

std::string subscript(int n)
{
    strings_t chars {
        "\u2080", "\u2081", "\u2082", "\u2083", "\u2084", 
        "\u2085", "\u2086", "\u2087", "\u2088", "\u2089" }; 
    
    std::string prefix {};
    if (n < 0)
        prefix = "-";
    std::string sn {std::to_string(std::abs(n))};
    std::string result { prefix };
    for (unsigned int i = 0; i < sn.size(); i++) {
        std::string d {sn[i]};
        result += chars[std::stoi(d)];
    }
    return result;
}

void os_char(std::ostream& os, katom_t type, unsigned char c)
{
    if (type == katom_t::eval_result) {
        os << c;
    } else if ((Katom::show_all || Katom::show_whitespace) and c == ' ') {
        if (is_active(type)) os << boldgreen;
        const std::string space_s { "\u00B7" }; 
        os << space_s;
        if (is_active(type)) os << black;
    } else if ((Katom::show_all || Katom::show_whitespace) and c == '\n') {
        if (is_active(type)) os << boldgreen;
        os << "/";
        if (is_active(type)) os << black;
    } else if (c != '\n') 
        os << c;
}

void os_body(std::ostream& os, const Katom& k)
{
    bool bracketed = true;
    if (k.is_whitespace() || k.is_word() || k.is_text() || k.is_literal() || k.is_nonascii()) {
        bracketed = Katom::show_all;
    }
    if (bracketed)
        os << left_bracket;
    for (auto c : k.m_text) {
        os_char(os, k.m_type, c);
    }
    if (bracketed) {
        if (Katom::show_index) {
            os << subscript(k.m_index);
        }
        if (Katom::show_type){
            if (Katom::show_index) {
                os << ".";
            }
            if (k.m_initial_type != k.m_type) {
                os << subscript(static_cast<int>(k.m_initial_type)) << right_arrow;
            }
            os << subscript(static_cast<int>(k.m_type));
        }
        os << right_bracket;
    }
}

std::ostream& operator<<(std::ostream& os, const Katom& k)
{
    //const std::string space_s     { "\u2423" }; 
    /// const std::string space_s     { "\u00B7" }; 
    // const std::string tab         { "\u2023" };
    // const std::string left_index  { "\u27EA" };
    // const std::string right_index { "\u27EB" };
    // const std::string diamond     { "\u2B29" };

    //bool in_red = mark_as_error(k.m_type);
    //if (k.m_type == katom_t::ignored)
    //  show_type = false;

    //if (in_red)
    //if (is_error(k.m_type))
    //    os << red;
    if (k.m_type == katom_t::replaced) {
        os << blue; // cyan;
    } else if (k.m_type == katom_t::ignored) {
        os << yellow;
    }
    //else if (k.m_type == katom_t::literal) 
    // os << green;
    else {
        os << black;
    }

    if (is_active(k.m_type) or
        (Katom::show_replaced and (k.m_type == katom_t::replaced)) or
        (Katom::show_ignored and (k.m_type == katom_t::ignored))) {
        os_body(os, k);
        if (k.m_initial_type == katom_t::newline ||
            (k.m_type == katom_t::ws_added && k.m_text == "\n")) {
            os << "\n";
        }
    }
    //os << black;
    return os;
}

katom_list abbrev(const katom_list& ks, unsigned int max_length)
{
    if (ks.size() > max_length) {
        katom_list result(ks.begin(), (ks.begin())+(max_length));
        Katom ellipsis = Katom("[...]", katom_t::word, Locator());
        result.push_back(ellipsis);
        result.push_back(ks.back());
        std::for_each(result.begin(), result.end(), [](Katom& k) {
            if (k.m_type == katom_t::newline) {
                //std::cout << "NEWLINE\n";
                k.m_type = katom_t::word;
                k.m_text = "/";
                k.m_src = "/";
                k.m_initial_type = katom_t::word;
            }
        });
        return result;
    } else {
        katom_list result(ks.begin(), ks.end());
        std::for_each(result.begin(), result.end(), [](Katom& k) {
            if (k.m_type == katom_t::newline) {
                //std::cout << "NEWLINE\n";
                k.m_type = katom_t::word;
                k.m_text = "/";
                k.m_initial_type = katom_t::word;
            }
        });
        return result;
    }
}


std::ostream& operator<<(std::ostream& os, const katom_lists& ks)
{
    //os << double_bar;
    //os << red < "|" << black;
    for (const auto& k : ks) {
        os << left_double_bracket << abbrev(k) << right_double_bracket << " ";
        //os << k << red << "|" << black;
    }
    // os << "\n";
    return os;
}

std::ostream& operator<<(std::ostream& os, const katom_ptr& k)
{
    os << *k;
    return os;
}

std::ostream& operator<<(std::ostream& os, const katom_iter& k)
{
    os << *k;
    return os;
}

std::ostream& operator<<(std::ostream& os, const std::vector<Katom>& ks)
{
    for (const auto& k : ks) {
        os << k;
    }
    return os;
}

std::ostream& operator<<(std::ostream& os, const std::vector<katom_iter>& ks)
{
    for (const auto& k : ks) {
        os << *k;
    }
    return os;
}



std::ostream &kindex(std::ostream &os)
{
    Katom::show_index = true;
    return os;
}

std::ostream &ktype(std::ostream &os)
{
    Katom::show_type = true;
    return os;
}

std::ostream &kws(std::ostream &os)
{
    Katom::show_whitespace = true;
    return os;
}

std::ostream &kall(std::ostream &os)
{
    Katom::show_all = true;
    return os;
}

std::ostream &kreplaced(std::ostream &os)
{
    Katom::show_replaced = true;
    return os;
}

std::ostream &kignored(std::ostream &os)
{
    Katom::show_ignored = true;
    return os;
}

std::ostream &kreset(std::ostream &os)
{
    Katom::show_index = false;
    Katom::show_type = false;
    Katom::show_all = false;
    Katom::show_replaced = false;
    Katom::show_ignored = false;
    Katom::show_whitespace = false;
    os << black;
    return os;
}

/*
std::ostream& operator<<(std::ostream& os, const std::vector<Katom> ks)
{
    std::ranges::copy(ks, std::ostream_iterator<Katom>(os, ""));
    return os;
}
*/

// Argtype

std::ostream& operator<<(std::ostream& os, const Argtype& a)
{
    return os
        << left_bracket << "\U0001D504" << broken_bar
        << a.m_name << broken_bar
        << a.m_symbolic_pattern << broken_bar
        << a.m_count << broken_bar
        << a.m_mincount << broken_bar
        << a.m_maxcount << right_bracket;
}

// Parameter

std::ostream& operator<<(std::ostream& os, const Parameter& arg)
{
    os << left_bracket << "\U0001D513" << broken_bar;
    if (arg.m_optional)
        os << ":";
    os << arg.m_name << "." << arg.m_argtype.m_name;
    //if (arg.m_default.size() != 0)
    if (!arg.m_default.empty()) {
        os << broken_bar << arg.m_default;
    }
    os << right_bracket;
    return os;
}

// std::vector<Parameter>

std::ostream& operator<<(std::ostream& os, const std::vector<Parameter>& as)
{
    for (const auto& a : as) {
        os << a;
    }
    return os;
}

// Parameter_set

std::ostream& operator<<(std::ostream& os, const Parameter_set& as)
{
    os << left_bracket << "\U0001D513\U0001D530" << broken_bar
       << as.m_positional.size() << broken_bar
       << as.m_optional.size() << broken_bar
        // << (as.m_rest.undefined() ? "-" : "+")
       << (as.m_rest.empty() ? "-" : "+")
       << broken_bar << "[" << as.m_katoms.size() << "]"
       << right_bracket;
    return os;
}

// Var
std::ostream& operator<<(std::ostream& os, const Var& v)
{
    os << "<" << v.m_name << ">";
    return os;
}

// Frame
std::ostream& operator<<(std::ostream& os, const Frame& f)
{
    os << "{";
    // for (auto [k,v] : f.m_vars) {
    //     os << v;
    // }
    os << join(f.names(), "|");
    os << "}";
    return os;
}

// State
std::ostream& operator<<(std::ostream& os, const State& s)
{
    //for (int i = s.m_frames.size() - 1; i != 0; --i) {
    for (auto f : s.m_frames) {
        os << f << "\n";
    }
    return os;
}


// Klammer


std::ostream& operator<<(std::ostream& os, const Klammer& k)
{
    std::string targets = join(k.get_target_names(), ","s);
    os << left_bracket << "\U0001D50E" << broken_bar
       << k.m_name << broken_bar
       << "p" << k.m_parameters.m_positional.size() << broken_bar
       << "o" << k.m_parameters.m_optional.size() << broken_bar
       << (targets.empty() ? "?" : targets)
       << right_bracket; 
    return os;
}

// Klammer::variable_map_t

std::ostream& operator<<(std::ostream& os, const Klammer::variable_map_t& vm)
{
    for (auto [k, v] : vm) {
        os << "  " << k << sp_arrow << v << "\n";
    }
    return os;
}


// Klammer::components

std::string katom_type_name(katom_t type)
{
    // Could cache, but why bother - only for kdesc.
    for (auto t : katom_types) {
        if (t.m_type == type) {
            return t.m_name;
        }
    }
    throw Internal_error("Unknown katom_t: " + std::to_string((int)type));
}

std::ostream& operator<<(std::ostream& os, const Klammer::components& kc)
{
    std::string del = "  ";
    auto [target, deftype, parameters, body, varmap, locator] = kc;
    os << std::setw(4) << target << del 
        // << katom_types[(int)deftype].m_name << del
       << katom_type_name(deftype) << del
       << parameters << del << kall << ktype << red << body << del << locator;
    return os;
}


// Klammer_registry

std::ostream& operator<<(std::ostream& os, const Klammer_registry& ks)
{
    for (const auto& klam : ks.m_klammers) {
        for (const auto& [k,v] : klam.second.m_defloc) {
           os << "  " << k << sp_arrow << v << "\n";
        }
        //klam.second.m_defloc.str() << "\n";
        std::cout << "    " << klam.first << sp_arrow << klam.second << " " << "\n";
    }
    return os;
}

// Target

void show_arrow_pair(std::ostream& os, std::pair<std::string,std::string> transform)
{
    os << transform.first << sp_arrow << transform.second;
}

std::ostream& operator<<(std::ostream& os, const Target& t)
{
    os << "<\U0001D517" << broken_bar << t.m_name << broken_bar << t.m_desc << broken_bar;
    for (const auto& i : t.m_includes) {
        os << i << right_arrow << t.m_name << broken_bar;
    }
    for (const auto& p : t.m_provides) {
        os << t.m_name << right_arrow << p << broken_bar;
    }
    os << ">";
    return os;
}

// Target_registry

std::ostream& operator<<(std::ostream& os, const Target_registry& ts)
{
    size_t width = 0;
    for (const auto& t : ts.m_targets) {
        width = std::max(width, t.first.size());
    }
    for (const auto& [name, target] : ts.m_targets) {
        os << std::setw(width) << name << sp_arrow << target << "\n";
    }
    return os;
}

// Klammerset

std::ostream& operator<<(std::ostream& os, const Klammerset& ks)
{
    os << "<\U0001D516" << broken_bar << ks.m_symbol << broken_bar << ks.m_desc;
    if (!ks.m_date.empty()) {
        os << broken_bar << ks.m_date;
    }
    if (!ks.m_requires.empty()) {
        os << broken_bar << "requires " << join(ks.m_requires, " / ");
    }
    if (!ks.m_files.empty()) {
        os << broken_bar << join(ks.m_files, " / ");
    }
    os << ">";
    return os;
}

// Klammerset_registry

std::ostream& operator<<(std::ostream& os, const Klammerset_registry& kr)
{
    size_t width = 0;
    for (const auto& k : kr.m_klammersets) {
        width = std::max(width, k.first.size());
    }
    for (const auto& [symbol, klammerset] : kr.m_klammersets) {
        os << std::setw(width) << symbol << sp_arrow << klammerset << "\n";
    }
    return os;
}

// Machine 


std::string describe_sources(Machine m)
{
    std::stringstream ss {};
    for (auto s : m.m_sources) {
        if (std::holds_alternative<std::string>(s)) {
            ss << "    String: " << trim(std::get<std::string>(s)) << "\n";
        } else {
            ss << "  Filename: " << std::get<fs::path>(s).string() << "\n";
        }
    }
    return ss.str();
}

std::string label(std::string name, int count)
{
    std::stringstream ss {};
    ss << " " << name << " (" << count << "):\n";
    return ss.str();
}

std::ostream& operator<<(std::ostream& os, const Machine& m)
{
    std::string argtypes_desc = m.m_argtypes.describe(false, 8);
    std::string state_desc = m.m_state.describe(false, 3);
    std::string targets_desc = m.m_targets.describe(4);
    std::string klammersets_desc = m.m_klammersets.describe(4);
    std::string option_sets_desc = m.m_option_sets.describe(4);
    std::string klammer_desc = m.m_klammers.describe(2);
    std::string source_desc = describe_sources(m);

    os << "\nMachine " << "\U000133DE \U00013000\n"
       << label("Sources", m.m_sources.size()) << source_desc << "\n"
       << label("Argtypes", m.m_argtypes.m_names.size()) << argtypes_desc << "\n"
       << label("Targets", m.m_targets.m_names.size()) << targets_desc << "\n"
       << label("Klammersets", m.m_klammersets.m_symbols.size()) << klammersets_desc << "\n"
       << label("Option sets", m.m_option_sets.m_names.size()) << option_sets_desc << "\n"
       << label("Klammers", m.m_klammers.m_klammers.size()) << klammer_desc << "\n"
       << label("State", m.m_state.m_frames.size()) << state_desc;
    return os;
}

void modify_stream(const std::string& name)
{
    if (name == "all") std::cout << kall;
    if (name == "type") std::cout << ktype;
    if (name == "index") std::cout << kindex;
    if (name == "ignored") std::cout << kignored;
    if (name == "replaced") std::cout << kreplaced;
};
