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

const std::string string::type_name = "string";

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

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

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

std::string string::serialize(const interpreter_context&) const {
    return str;
}

const std::string& string::type() const {
    return type_name;
}

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

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

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>();
        scalar_hp::ntype i = s2.as<scalar_hp>().value;
        scalar_hp::ntype n = s.str.size();
        i = fmod(i, n);
        if (i < numeric_traits<scalar_hp::ntype>::zero)
            i += n;
        return new string(std::string(1, s.str[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>();
        scalar::ntype i = s2.as<scalar>().value;
        scalar::ntype n = s.str.size();
        i = fmod(i, n);
        if (i < numeric_traits<scalar::ntype>::zero)
            i += n;
        return new string(std::string(1, s.str[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.str + ss2.str);
    });

    context.register_function<string>("trim", [&context](const std::vector<symbol>& args) {
        std::string s = args[0].as<string>().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 new string(s);
    });

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

        bool found = true;
        while (found && !s.empty()) {
            found = false;
            for (char c : chars) {
                if (s[0] == c) {
                    s.erase(0, 1);
                    found = true;
                    break;
                }
            }
        }

        found = true;
        while (found && !s.empty()) {
            found = false;
            for (char c : chars) {
                if (s[s.size()-1] == c) {
                    s.erase(s.size()-1, 1);
                    found = true;
                    break;
                }
            }
        }

        return new string(s);
    });

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

        array* a = new array();
        size_t pos = s.find(token);
        while (pos != s.npos) {
            if (pos != 0)
                a->push(symbol(new string(s.substr(0, pos))));
            s.erase(0, pos+token.size());
            pos = s.find(token);
        }

        if (!s.empty())
            a->push(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>().str;
        std::string tokens = args[1].as<string>().str;

        array* a = new array();
        size_t pos = s.find_first_of(tokens);
        while (pos != s.npos) {
            if (pos != 0)
                a->push(symbol(new string(s.substr(0, pos))));
            s.erase(0, pos+1);
            pos = s.find_first_of(tokens);
        }

        if (!s.empty())
            a->push(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>().str;
        std::string token = args[1].as<string>().str;
        std::string rep   = args[2].as<string>().str;

        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>().str;
        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>().str;
        std::ifstream f(path);
        if (!f.is_open()) {
            context.msg("note: cannot find file '" + path + "'");
            return new string("");
        }

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

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

        return new string(s);
    });

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

    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<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>().str;
        auto ret = context.evaluate(code);
        return ret;
    });
}


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

    std::string str = tmp;
    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;
    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) {
    size_t pos = str.find(token);
    while (pos != str.npos) {
        str.replace(pos, token.size(), rep);
        pos = str.find(token, pos+rep.size());
    }
}
