#include "string.hpp"
#include "variable.hpp"
#include "collection.hpp"
#include "scalar.hpp"
#include "context.hpp"
#include <fstream>
#include <algorithm>

const std::string type_traits<std::string>::type_name = string::static_type_name();

string::string(const std::string& s) : value(s) {}
string::string(std::string&& s) : value(s) {}

string* string::clone() const {
    return new string(value);
}

symbol_impl* string::evaluate(const interpreter_context& context) const {
    return clone();
}

std::string string::serialize(const interpreter_context& context) const {
    return type_traits<std::string>::serialize(value, context);
}

const std::string& string::static_type_name() {
    static const std::string name = "string";
    return name;
}

bool string::is_litteral() const {
    return true;
}

std::pair<std::size_t,std::size_t> string::priority(const interpreter_context&) const {
    return std::pair<std::size_t,std::size_t>(0, 0);
}

std::string type_traits<std::string>::serialize(const std::string& s, const interpreter_context& context) {
    return '\"'+s+'\"';
}

void string::reg(interpreter_context& context) {
    if (context.high_precision) {
    context.register_operator<string, scalar_hp>("[]", [&context](const symbol& s1, const symbol& s2) {
        const string& s = s1.as<string>();
        if (s.value.size() == 0) return (symbol_impl*)new no_type();
        scalar_hp::ntype i = s2.as<scalar_hp>().value;
        scalar_hp::ntype n = s.value.size();
        i = fmod(i, n);
        if (i < numeric_traits<scalar_hp::ntype>::zero) i += n;
        return (symbol_impl*)new string(std::string(1, s.value[numeric_traits<scalar_hp::ntype>::to_uint(i)]));
    });
    } else {
    context.register_operator<string, scalar>("[]", [&context](const symbol& s1, const symbol& s2) {
        const string& s = s1.as<string>();
        if (s.value.size() == 0) return (symbol_impl*)new no_type();
        scalar::ntype i = s2.as<scalar>().value;
        scalar::ntype n = s.value.size();
        i = fmod(i, n);
        if (i < numeric_traits<scalar::ntype>::zero) i += n;
        return (symbol_impl*)new string(std::string(1, s.value[numeric_traits<scalar::ntype>::to_uint(i)]));
    });
    }

    context.register_operator<string, string>("+", [&context](const symbol& s1, const symbol& s2) {
        const string& ss1 = s1.as<string>();
        const string& ss2 = s2.as<string>();
        return new string(ss1.value + ss2.value);
    });

    context.register_function<string>("trim", [&context](const std::vector<symbol>& args) {
        return new string(trim(args[0].as<string>().value));
    });

    context.register_function<string, string>("trim", [&context](const std::vector<symbol>& args) {
        return new string(trim(args[0].as<string>().value, args[1].as<string>().value));
    });

    context.register_function<string, string>("split_token", [&context](const std::vector<symbol>& args) {
        std::string s            = args[0].as<string>().value;
        const std::string& token = args[1].as<string>().value;

        // TODO: switch that to array<string>
        collection* a = new collection();
        std::size_t pos = s.find(token);
        while (pos != s.npos) {
            if (pos != 0)
                a->push_back(symbol(new string(s.substr(0, pos))));
            s.erase(0, pos+token.size());
            pos = s.find(token);
        }

        if (!s.empty()) {
            a->push_back(symbol(new string(s)));
        }

        return a;
    });

    context.register_function<string, string>("split", [&context](const std::vector<symbol>& args) {
        std::string s             = args[0].as<string>().value;
        const std::string& tokens = args[1].as<string>().value;

        // TODO: switch that to array<string>
        collection* a = new collection();
        std::size_t pos = s.find_first_of(tokens);
        while (pos != s.npos) {
            if (pos != 0)
                a->push_back(symbol(new string(s.substr(0, pos))));
            s.erase(0, pos+1);
            pos = s.find_first_of(tokens);
        }

        if (!s.empty()) {
            a->push_back(symbol(new string(s)));
        }

        return a;
    });

    context.register_function<string, string, string>("replace", [&context](const std::vector<symbol>& args) {
        std::string s            = args[0].as<string>().value;
        const std::string& token = args[1].as<string>().value;
        const std::string& rep   = args[2].as<string>().value;

        std::size_t pos = s.find(token);
        while (pos != s.npos) {
            s.replace(pos, token.size(), rep);
            pos = s.find(token, pos+rep.size());
        }

        return new string(s);
    });

    context.register_function<string>("reverse", [&context](const std::vector<symbol>& args) {
        std::string s = args[0].as<string>().value;
        std::reverse(s.begin(), s.end());
        return new string(s);
    });

    context.register_function<string>("load", [&context](const std::vector<symbol>& args) {
        const std::string& path = args[0].as<string>().value;
        std::ifstream f(path, std::ios::binary);
        if (!f.is_open()) {
            context.note("cannot find file '" + path + "'");
            return new string();
        }

        f.seekg(0, std::ios::end);
        std::size_t length = f.tellg();
        f.seekg(0, std::ios::beg);

        char* tmp = new char[length];
        f.read(tmp, length);
        std::string s(tmp, length);
        delete[] tmp;

        // Convert all files to UNIX line endings
        replace(s, "\r", "");

        return new string(s);
    });

    context.register_function<any_type>("type", [&context](const std::vector<symbol>& args) {
        return new string(args[0].type_name());
    });

    context.register_function<variadic_type>("print", [&context](const std::vector<symbol>& args) {
        for (auto& s : args) {
            std::cout << s.serialize(context);
        }
        std::cout << std::flush;
        return new no_type();
    });

    context.register_function<variadic_type>("help", [&context](const std::vector<symbol>& args) {
        if (args.empty()) {
            context.print_help();
        } else {
            for (std::size_t i = 0; i < args.size(); ++i) {
                const symbol& s = args[i];
                std::string str;
                if (s.type_is<variable>()) {
                    str = context.serialize_variable(s.as<variable>().name);
                } else {
                    str = s.serialize(context) + " (" + s.type_name() + ")";
                }

                context.format_for_output(str);
                std::cout << str << std::endl;
            }
        }

        return new no_type();
    });

    context.register_function<any_type>("to_string", [&context](const std::vector<symbol>& args) {
        return new string(args[0].serialize(context));
    });

    context.register_function<string>("evaluate", [&context](const std::vector<symbol>& args) {
        const std::string& code = args[0].as<string>().value;
        auto ret = context.evaluate(code);
        return ret;
    });
}


std::string trim(const std::string& str) {
    std::string s = str;
    while (!s.empty() && (s[0] == ' ' || s[0] == '\t' || s[0] == '\n' || s[0] == '\r')) {
        s.erase(0, 1);
    }
    while (!s.empty() && (s[s.size()-1] == ' ' || s[s.size()-1] == '\t' || s[s.size()-1] == '\n' || s[s.size()-1] == '\r')) {
        s.erase(s.size()-1, 1);
    }

    return s;
}

std::string trim(const std::string& str, const std::string& chars) {
    std::string s = str;
    while (!s.empty() && s.find_first_of(chars) == 0) {
        s.erase(0, 1);
    }
    while (!s.empty() && s.find_last_of(chars) == s.size()-1) {
        s.erase(s.size()-1, 1);
    }

    return s;
}

std::vector<std::string> split_token(const std::string& tmp, const std::string& token) {
    std::vector<std::string> words;

    std::string str = tmp;
    std::size_t pos = str.find(token);
    while (pos != str.npos) {
        if (pos != 0)
            words.push_back(str.substr(0, pos));
        str.erase(0, pos+token.size());
        pos = str.find(token);
    }

    if (!str.empty())
        words.push_back(str);

    return words;
}

std::vector<std::string> split(const std::string& tmp, const std::string& tokens) {
    std::vector<std::string> words;

    std::string str = tmp;
    std::size_t pos = str.find_first_of(tokens);
    while (pos != str.npos) {
        if (pos != 0)
            words.push_back(str.substr(0, pos));
        str.erase(0, pos+1);
        pos = str.find_first_of(tokens);
    }

    if (!str.empty())
        words.push_back(str);

    return words;
}

void replace(std::string& str, const std::string& token, const std::string& rep) {
    std::size_t pos = str.find(token);
    while (pos != str.npos) {
        str.replace(pos, token.size(), rep);
        pos = str.find(token, pos+rep.size());
    }
}
