#include "array.hpp"
#include "context.hpp"

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

array::array() {
}

void array::push(symbol&& s) {
    vals.push_back(std::move(s));
}

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

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

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

array* array::clone() const {
    array* a = new array();
    for (const symbol& s : vals)
        a->push(s.clone());

    return a;
}

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

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

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

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

    s += ")";

    return s;
}

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

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

    return true;
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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