#include "range.hpp"
#include "array.hpp"
#include "string.hpp"
#include "collection.hpp"
#include "scalar.hpp"
#include "context.hpp"

range::range(symbol&& begin, symbol&& end) : begin(std::move(begin)), end(std::move(end)) {}

range* range::clone() const {
    return new range(symbol(begin.clone()), symbol(end.clone()));
}

range* range::evaluate(const interpreter_context& context) const {
    symbol nstart(begin.evaluate(context));
    symbol nend(end.evaluate(context));
    return new range(std::move(nstart), std::move(nend));
}

std::string range::serialize(const interpreter_context& context) const {
    std::pair<std::size_t,std::size_t> p = priority(context), p1 = begin.priority(context), p2 = end.priority(context);
    std::string s;

    if (p.first < p1.first) {
        s += "(" + begin.serialize(context) + ")";
    } else {
        s += begin.serialize(context);
    }

    s += ":";

    if (p.first < p2.first || (p.first == p2.first && p.second < p2.second)) {
        s += "(" + end.serialize(context) + ")";
    } else {
        s += end.serialize(context);
    }

    return s;
}

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

bool range::is_litteral() const {
    return begin.is_litteral() && end.is_litteral();
}

std::pair<std::size_t,std::size_t> range::priority(const interpreter_context& context) const {
    return context.binary_operator_priority(":");
}

void range::reg(interpreter_context& context) {
    context.register_operator<any_type, any_type>(":", [&context](const symbol& s1, const symbol& s2) {
        return new range(s1.clone(), s2.clone());
    });

    if (context.high_precision) {
    context.register_operator<collection, range>("[]", [&context](const symbol& s1, const symbol& s2) {
        const collection& a = s1.as<collection>();
        if (a.empty()) return (symbol_impl*)new no_type();
        const range& r = s2.as<range>();
        if (!r.begin.type_is<scalar_hp>() && !r.end.type_is<scalar_hp>()) {
            context.error("cannot call 'operator []' with ranges that are not scalar");
            throw parser_exception();
        }

        auto begin = context.get_index(r.begin.as<scalar_hp>().value, a.size());
        auto end   = context.get_index(r.end.as<scalar_hp>().value, a.size());
        if (end < begin) {
            context.error("indices in a range must be sorted in ascending order (low:high)");
            throw parser_exception();
        }

        if (begin == end) {
            return a[begin].clone().release();
        } else {
            collection* na = new collection();
            for (std::size_t i = begin; i <= end; ++i) {
                na->push_back(a[i].clone());
            }

            return (symbol_impl*)na;
        }
    });
    } else {
    context.register_operator<collection, range>("[]", [&context](const symbol& s1, const symbol& s2) {
        const collection& a = s1.as<collection>();
        if (a.empty()) return (symbol_impl*)new no_type();
        const range& r = s2.as<range>();
        if (!r.begin.type_is<scalar>() && !r.end.type_is<scalar>()) {
            context.error("cannot call 'operator []' with ranges that are not scalar");
            throw parser_exception();
        }

        auto begin = context.get_index(r.begin.as<scalar>().value, a.size());
        auto end   = context.get_index(r.end.as<scalar>().value, a.size());
        if (end < begin) {
            context.error("indices in a range must be sorted in ascending order (low:high)");
            throw parser_exception();
        }

        if (begin == end) {
            return a[begin].clone().release();
        } else {
            collection* na = new collection();
            for (std::size_t i = begin; i <= end; ++i) {
                na->push_back(a[i].clone());
            }

            return (symbol_impl*)na;
        }
    });
    }

    if (context.high_precision) {
    context.register_operator<string, range>("[]", [&context](const symbol& s1, const symbol& s2) {
        const string& a = s1.as<string>();
        if (a.value.empty()) return (symbol_impl*)new no_type();
        const range& r = s2.as<range>();
        if (!r.begin.type_is<scalar_hp>() && !r.end.type_is<scalar_hp>()) {
            context.error("cannot call 'operator []' with ranges that are not scalar");
            throw parser_exception();
        }

        auto begin = context.get_index(r.begin.as<scalar_hp>().value, a.value.size());
        auto end   = context.get_index(r.end.as<scalar_hp>().value, a.value.size());
        if (end < begin) {
            context.error("indices in a range must be sorted in ascending order (low:high)");
            throw parser_exception();
        }

        return (symbol_impl*)new string(a.value.substr(begin, end-begin+1));
    });
    } else {
    context.register_operator<string, range>("[]", [&context](const symbol& s1, const symbol& s2) {
        const string& a = s1.as<string>();
        if (a.value.empty()) return (symbol_impl*)new no_type();
        const range& r = s2.as<range>();
        if (!r.begin.type_is<scalar>() && !r.end.type_is<scalar>()) {
            context.error("cannot call 'operator []' with ranges that are not scalar");
            throw parser_exception();
        }

        auto begin = context.get_index(r.begin.as<scalar>().value, a.value.size());
        auto end   = context.get_index(r.end.as<scalar>().value, a.value.size());
        if (end < begin) {
            context.error("indices in a range must be sorted in ascending order (low:high)");
            throw parser_exception();
        }

        return (symbol_impl*)new string(a.value.substr(begin, end-begin+1));
    });
    }

    context.register_operator<range>("+", [&context](const symbol& s) {
        const range& r = s.as<range>();
        symbol begin(context.unary_operator("+", r.begin));
        symbol end(context.unary_operator("+", r.end));
        return new range(std::move(begin), std::move(end));
    });
    context.register_operator<range>("-", [&context](const symbol& s) {
        const range& r = s.as<range>();
        symbol begin(context.unary_operator("-", r.begin));
        symbol end(context.unary_operator("-", r.end));
        return new range(std::move(begin), std::move(end));
    });

    if (context.high_precision) {
    context.register_operator<range, scalar_hp>("[]", [&context](const symbol& s1, const symbol& s2) {
        const range& r = s1.as<range>();
        int i = numeric_traits<scalar_hp::ntype>::to_uint(s2.as<scalar_hp>().value);
        if (i == 0) {
            return r.begin.clone().release();
        } else {
            return r.end.clone().release();
        }
    });
    } else {
    context.register_operator<range, scalar>("[]", [&context](const symbol& s1, const symbol& s2) {
        const range& r = s1.as<range>();
        int i = numeric_traits<scalar::ntype>::to_uint(s2.as<scalar>().value);
        if (i == 0) {
            return r.begin.clone().release();
        } else {
            return r.end.clone().release();
        }
    });
    }

    context.register_commutative_operator<range, range>("+", [&context](const symbol& s1, const symbol& s2) {
        const range& r1 = s1.as<range>();
        const range& r2 = s2.as<range>();
        symbol begin(context.binary_operator("+", r1.begin, r2.begin));
        symbol end(context.binary_operator("+", r1.end, r2.end));
        return new range(std::move(begin), std::move(end));
    });

    context.register_commutative_operator<range, range>("-", [&context](const symbol& s1, const symbol& s2) {
        const range& r1 = s1.as<range>();
        const range& r2 = s2.as<range>();
        symbol begin(context.binary_operator("-", r1.begin, r2.begin));
        symbol end(context.binary_operator("-", r1.end, r2.end));
        return new range(std::move(begin), std::move(end));
    });

    context.register_commutative_operator<range, range>("*", [&context](const symbol& s1, const symbol& s2) {
        const range& r1 = s1.as<range>();
        const range& r2 = s2.as<range>();
        symbol begin(context.binary_operator("*", r1.begin, r2.begin));
        symbol end(context.binary_operator("*", r1.end, r2.end));
        return new range(std::move(begin), std::move(end));
    });

    context.register_commutative_operator<range, range>("/", [&context](const symbol& s1, const symbol& s2) {
        const range& r1 = s1.as<range>();
        const range& r2 = s2.as<range>();
        symbol begin(context.binary_operator("/", r1.begin, r2.begin));
        symbol end(context.binary_operator("/", r1.end, r2.end));
        return new range(std::move(begin), std::move(end));
    });

    context.register_operator<range, any_type>("^", [&context](const symbol& s1, const symbol& s2) {
        const range& r = s1.as<range>();
        symbol begin(context.binary_operator("^", r.begin, s2));
        symbol end(context.binary_operator("^", r.end, s2));
        return new range(std::move(begin), std::move(end));
    });

    context.register_operator<range, any_type>("^", [&context](const symbol& s1, const symbol& s2) {
        const range& r = s2.as<range>();
        symbol begin(context.binary_operator("^", s1, r.begin));
        symbol end(context.binary_operator("^", s1, r.end));
        return new range(std::move(begin), std::move(end));
    });

    context.register_operator<range, any_type>("*", [&context](const symbol& s1, const symbol& s2) {
        const range& r = s1.as<range>();
        symbol begin(context.binary_operator("*", r.begin, s2));
        symbol end(context.binary_operator("*", r.end, s2));
        return new range(std::move(begin), std::move(end));
    });

    context.register_operator<any_type, range>("*", [&context](const symbol& s1, const symbol& s2) {
        const range& r = s2.as<range>();
        symbol begin(context.binary_operator("*", s1, r.begin));
        symbol end(context.binary_operator("*", s1, r.end));
        return new range(std::move(begin), std::move(end));
    });

    context.register_operator<range, any_type>("+", [&context](const symbol& s1, const symbol& s2) {
        const range& r = s1.as<range>();
        symbol begin(context.binary_operator("+", r.begin, s2));
        symbol end(context.binary_operator("+", r.end, s2));
        return new range(std::move(begin), std::move(end));
    });

    context.register_operator<any_type, range>("+", [&context](const symbol& s1, const symbol& s2) {
        const range& r = s2.as<range>();
        symbol begin(context.binary_operator("+", s1, r.begin));
        symbol end(context.binary_operator("+", s1, r.end));
        return new range(std::move(begin), std::move(end));
    });

    context.register_operator<range, any_type>("-", [&context](const symbol& s1, const symbol& s2) {
        const range& r = s1.as<range>();
        symbol begin(context.binary_operator("-", r.begin, s2));
        symbol end(context.binary_operator("-", r.end, s2));
        return new range(std::move(begin), std::move(end));
    });

    context.register_operator<any_type, range>("-", [&context](const symbol& s1, const symbol& s2) {
        const range& r = s2.as<range>();
        symbol begin(context.binary_operator("-", s1, r.begin));
        symbol end(context.binary_operator("-", s1, r.end));
        return new range(std::move(begin), std::move(end));
    });

    context.register_operator<range, any_type>("/", [&context](const symbol& s1, const symbol& s2) {
        const range& r = s1.as<range>();
        symbol begin(context.binary_operator("/", r.begin, s2));
        symbol end(context.binary_operator("/", r.end, s2));
        return new range(std::move(begin), std::move(end));
    });

    context.register_operator<any_type, range>("/", [&context](const symbol& s1, const symbol& s2) {
        const range& r = s2.as<range>();
        symbol begin(context.binary_operator("/", s1, r.begin));
        symbol end(context.binary_operator("/", s1, r.end));
        return new range(std::move(begin), std::move(end));
    });

    context.register_function<range>("size", [&context](const std::vector<symbol>& args) -> symbol_impl* {
        const range& r = args[0].as<range>();

        if (context.high_precision) {
            if (!r.begin.type_is<scalar_hp>() || !r.end.type_is<scalar_hp>()) {
                context.error("cannot call 'size' on a range that is not scalar");
                throw parser_exception();
            }
            auto begin = floor(r.begin.as<scalar_hp>().value);
            auto end = floor(r.end.as<scalar_hp>().value);
            if (end > begin) {
                return new scalar_hp(end - begin + numeric_traits<scalar_hp::ntype>::one);
            } else {
                return new scalar_hp(begin - end + numeric_traits<scalar_hp::ntype>::one);
            }
        } else {
            if (!r.begin.type_is<scalar>() || !r.end.type_is<scalar>()) {
                context.error("cannot call 'size' on a range that is not scalar");
                throw parser_exception();
            }
            auto begin = floor(r.begin.as<scalar>().value);
            auto end = floor(r.end.as<scalar>().value);
            if (end > begin) {
                return new scalar(end - begin + numeric_traits<scalar::ntype>::one);
            } else {
                return new scalar(begin - end + numeric_traits<scalar::ntype>::one);
            }
        }
    });

    context.register_function<range>("reverse", [&context](const std::vector<symbol>& args) -> symbol_impl* {
        const range& r = args[0].as<range>();
        return new range(symbol(r.end.clone()), symbol(r.begin.clone()));
    });

    if (context.high_precision) {
    context.register_function<range>("indgen", [&context](const std::vector<symbol>& args) -> symbol_impl* {
        const range& r = args[0].as<range>();
        if (!r.begin.type_is<scalar_hp>() && !r.end.type_is<scalar_hp>()) {
            context.error("cannot call 'indgen' with ranges that are not scalar");
            throw parser_exception();
        }

        auto begin = r.begin.as<scalar_hp>().value;
        auto end = r.end.as<scalar_hp>().value;
        if (end > begin) {
            array<scalar_hp::ntype>* a = new array<scalar_hp::ntype>(numeric_traits<scalar_hp::ntype>::to_uint(end - begin) + 1);
            std::size_t k = 0;
            for (scalar_hp::ntype i = begin; i <= end; ++i, ++k) {
                (*a)[k] = i;
            }
            return a;
        } else {
            array<scalar_hp::ntype>* a = new array<scalar_hp::ntype>(numeric_traits<scalar_hp::ntype>::to_uint(begin - end) + 1);
            std::size_t k = 0;
            for (scalar_hp::ntype i = begin; i >= end; --i) {
                (*a)[k] = i;
            }
            return a;
        }
    });
    } else {
    context.register_function<range>("indgen", [&context](const std::vector<symbol>& args) -> symbol_impl* {
        const range& r = args[0].as<range>();
        if (!r.begin.type_is<scalar>() && !r.end.type_is<scalar>()) {
            context.error("cannot call 'indgen' with ranges that are not scalar");
            throw parser_exception();
        }

        auto begin = r.begin.as<scalar>().value;
        auto end = r.end.as<scalar>().value;
        if (end > begin) {
            array<scalar::ntype>* a = new array<scalar::ntype>(numeric_traits<scalar::ntype>::to_uint(end - begin) + 1);
            std::size_t k = 0;
            for (scalar::ntype i = begin; i <= end; ++i, ++k) {
                (*a)[k] = i;
            }
            return a;
        } else {
            array<scalar::ntype>* a = new array<scalar::ntype>(numeric_traits<scalar::ntype>::to_uint(begin - end) + 1);
            std::size_t k = 0;
            for (scalar::ntype i = begin; i >= end; --i) {
                (*a)[k] = i;
            }
            return a;
        }
    });
    }
}
