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

collection::collection() {
}

void collection::push_back(symbol&& s) {
    vals.emplace_back(std::move(s));
}

void collection::push_front(symbol&& s) {
    vals.emplace_front(std::move(s));
}

symbol collection::pop_back() {
    symbol s(vals.back().release());
    vals.pop_back();
    return s;
}

symbol collection::pop_front() {
    symbol s(vals.front().release());
    vals.pop_front();
    return s;
}

std::size_t collection::size() const {
    return vals.size();
}

bool collection::empty() const {
    return vals.empty();
}

symbol& collection::operator [] (std::size_t i) {
    return vals[i];
}

const symbol& collection::operator [] (std::size_t i) const {
    return vals[i];
}

collection::iterator collection::begin() {
    return vals.begin();
}

collection::const_iterator collection::begin() const {
    return vals.begin();
}

collection::reverse_iterator collection::rbegin() {
    return vals.rbegin();
}

collection::const_reverse_iterator collection::rbegin() const {
    return vals.rbegin();
}

collection::iterator collection::end() {
    return vals.end();
}

collection::const_iterator collection::end() const {
    return vals.end();
}

collection::reverse_iterator collection::rend() {
    return vals.rend();
}

collection::const_reverse_iterator collection::rend() const {
    return vals.rend();
}

collection* collection::clone() const {
    collection* a = new collection();
    for (const symbol& s : vals) {
        a->push_back(s.clone());
    }

    return a;
}

collection* collection::evaluate(const interpreter_context& context) const {
    collection* a = new collection();
    try {
        for (const symbol& s : vals) {
            a->push_back(s.evaluate(context));
        }
    } catch (...) {
        delete a;
        throw;
    }
    return a;
}

std::string collection::serialize(const interpreter_context& context) const {
    if (vals.empty()) return "{}";

    std::string s = "{" + vals[0].serialize(context);

    for (std::size_t i = 1; i < vals.size(); ++i) {
        s += ", " + vals[i].serialize(context);
    }

    s += "}";

    return s;
}

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

bool collection::is_litteral() const {
    for (const symbol& s : vals) {
        if (!s.is_litteral()) return false;
    }

    return true;
}

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

void collection::reg(interpreter_context& context) {
    context.register_operator<collection>("+", [&context](const symbol& s) {
        collection* na = new collection();
        try {
            const collection& a = s.as<collection>();
            for (std::size_t i = 0; i < a.size(); ++i) {
                na->push_back(symbol(context.unary_operator("+", a[i])));
            }
            return na;
        } catch (...) {
            delete na;
            throw;
        }
    });
    context.register_operator<collection>("-", [&context](const symbol& s) {
        collection* na = new collection();
        try {
            const collection& a = s.as<collection>();
            for (std::size_t i = 0; i < a.size(); ++i) {
                na->push_back(symbol(context.unary_operator("-", a[i])));
            }
            return na;
        } catch (...) {
            delete na;
            throw;
        }
    });

    if (context.high_precision) {
    context.register_operator<collection, scalar_hp>("[]", [&context](const symbol& s1, const symbol& s2) {
        const collection& a = s1.as<collection>();
        if (a.empty()) return (symbol_impl*)new no_type();
        auto i = context.get_index(s2.as<scalar_hp>().value, a.size());
        return a[i].clone().release();
    });
    } else {
    context.register_operator<collection, scalar>("[]", [&context](const symbol& s1, const symbol& s2) {
        const collection& a = s1.as<collection>();
        if (a.empty()) return (symbol_impl*)new no_type();
        auto i = context.get_index(s2.as<scalar>().value, a.size());
        return a[i].clone().release();
    });
    }
    context.register_commutative_operator<collection, collection>("+", [&context](const symbol& s1, const symbol& s2) {
        collection* na = new collection();
        try {
            const collection& a1 = s1.as<collection>();
            const collection& a2 = s2.as<collection>();
            if (a1.size() != a2.size()) {
                context.error("operator 'collection + collection' requires collections of equal lengths");
                throw parser_exception();
            }
            for (std::size_t i = 0; i < a1.size(); ++i) {
                na->push_back(symbol(context.binary_operator("+", a1[i], a2[i])));
            }
            return na;
        } catch (...) {
            delete na;
            throw;
        }
    });

    context.register_commutative_operator<collection, collection>("-", [&context](const symbol& s1, const symbol& s2) {
        collection* na = new collection();
        try {
            const collection& a1 = s1.as<collection>();
            const collection& a2 = s2.as<collection>();
            if (a1.size() != a2.size()) {
                context.error("operator 'collection - collection' requires collections of equal lengths");
                throw parser_exception();
            }
            for (std::size_t i = 0; i < a1.size(); ++i) {
                na->push_back(symbol(context.binary_operator("-", a1[i], a2[i])));
            }
            return na;
        } catch (...) {
            delete na;
            throw;
        }
    });

    context.register_commutative_operator<collection, collection>("*", [&context](const symbol& s1, const symbol& s2) {
        collection* na = new collection();
        try {
            const collection& a1 = s1.as<collection>();
            const collection& a2 = s2.as<collection>();
            if (a1.size() != a2.size()) {
                context.error("operator 'collection * collection' requires collections of equal lengths");
                throw parser_exception();
            }
            for (std::size_t i = 0; i < a1.size(); ++i) {
                na->push_back(symbol(context.binary_operator("*", a1[i], a2[i])));
            }
            return na;
        } catch (...) {
            delete na;
            throw;
        }
    });

    context.register_commutative_operator<collection, collection>("/", [&context](const symbol& s1, const symbol& s2) {
        collection* na = new collection();
        try {
            const collection& a1 = s1.as<collection>();
            const collection& a2 = s2.as<collection>();
            if (a1.size() != a2.size()) {
                context.error("operator 'collection / collection' requires collections of equal lengths");
                throw parser_exception();
            }
            for (std::size_t i = 0; i < a1.size(); ++i) {
                na->push_back(symbol(context.binary_operator("/", a1[i], a2[i])));
            }
            return na;
        } catch (...) {
            delete na;
            throw;
        }
    });

    context.register_operator<collection, any_type>("^", [&context](const symbol& s1, const symbol& s2) {
        collection* na = new collection();
        try {
            const collection& oa = s1.as<collection>();
            for (const symbol& s : oa.vals) {
                na->push_back(symbol(context.binary_operator("^", s, s2)));
            }
            return na;
        } catch (...) {
            delete na;
            throw;
        }
    });

    context.register_operator<any_type, collection>("^", [&context](const symbol& s1, const symbol& s2) {
        collection* na = new collection();
        try {
            const collection& oa = s2.as<collection>();
            for (const symbol& s : oa.vals) {
                na->push_back(symbol(context.binary_operator("^", s1, s)));
            }
            return na;
        } catch (...) {
            delete na;
            throw;
        }
    });

    context.register_operator<collection, any_type>("*", [&context](const symbol& s1, const symbol& s2) {
        collection* na = new collection();
        try {
            const collection& oa = s1.as<collection>();
            for (const symbol& s : oa.vals) {
                na->push_back(symbol(context.binary_operator("*", s, s2)));
            }
            return na;
        } catch (...) {
            delete na;
            throw;
        }
    });

    context.register_operator<any_type, collection>("*", [&context](const symbol& s1, const symbol& s2) {
        collection* na = new collection();
        try {
            const collection& oa = s2.as<collection>();
            for (const symbol& s : oa.vals) {
                na->push_back(symbol(context.binary_operator("*", s1, s)));
            }
            return na;
        } catch (...) {
            delete na;
            throw;
        }
    });

    context.register_operator<collection, any_type>("+", [&context](const symbol& s1, const symbol& s2) {
        collection* na = new collection();
        try {
            const collection& oa = s1.as<collection>();
            for (const symbol& s : oa.vals) {
                na->push_back(symbol(context.binary_operator("+", s, s2)));
            }
            return na;
        } catch (...) {
            delete na;
            throw;
        }
    });

    context.register_operator<any_type, collection>("+", [&context](const symbol& s1, const symbol& s2) {
        collection* na = new collection();
        try {
            const collection& oa = s2.as<collection>();
            for (const symbol& s : oa.vals) {
                na->push_back(symbol(context.binary_operator("+", s1, s)));
            }
            return na;
        } catch (...) {
            delete na;
            throw;
        }
    });

    context.register_operator<collection, any_type>("-", [&context](const symbol& s1, const symbol& s2) {
        collection* na = new collection();
        try {
            const collection& oa = s1.as<collection>();
            for (const symbol& s : oa.vals) {
                na->push_back(symbol(context.binary_operator("-", s, s2)));
            }
            return na;
        } catch (...) {
            delete na;
            throw;
        }
    });

    context.register_operator<any_type, collection>("-", [&context](const symbol& s1, const symbol& s2) {
        collection* na = new collection();
        try {
            const collection& oa = s2.as<collection>();
            for (const symbol& s : oa.vals) {
                na->push_back(symbol(context.binary_operator("-", s1, s)));
            }
            return na;
        } catch (...) {
            delete na;
            throw;
        }
    });

    context.register_operator<collection, any_type>("/", [&context](const symbol& s1, const symbol& s2) {
        collection* na = new collection();
        try {
            const collection& oa = s1.as<collection>();
            for (const symbol& s : oa.vals) {
                na->push_back(symbol(context.binary_operator("/", s, s2)));
            }
            return na;
        } catch (...) {
            delete na;
            throw;
        }
    });

    context.register_operator<any_type, collection>("/", [&context](const symbol& s1, const symbol& s2) {
        collection* na = new collection();
        try {
            const collection& oa = s2.as<collection>();
            for (const symbol& s : oa.vals) {
                na->push_back(symbol(context.binary_operator("/", s1, s)));
            }
            return na;
        } catch (...) {
            delete na;
            throw;
        }
    });

    context.register_function<collection>("size", [&context](const std::vector<symbol>& args) -> symbol_impl* {
        if (context.high_precision) {
            return new scalar_hp(args[0].as<collection>().size());
        } else {
            return new scalar(args[0].as<collection>().size());
        }
    });

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