// Copyright (c) 2014-2016 Dropbox, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "runtime/set.h"

#include "capi/typeobject.h"
#include "runtime/objmodel.h"

namespace pyston {

BoxedClass* set_iterator_cls;

extern "C" Box* createSet() {
    return new BoxedSet();
}

static void _setAddStolen(BoxedSet* self, STOLEN(BoxAndHash) val) {
    try {
        auto&& p = self->s.insert(val);

        // Is there a nicer way to represent try-else?
        try {
            if (!p.second /* already exists */) {
                // keep the original key
                Py_DECREF(val.value);
            }
        } catch (ExcInfo e) {
            abort();
        }
    } catch (ExcInfo e) {
        Py_DECREF(val.value);
        throw e;
    }
}

void _setAddStolen(BoxedSet* self, STOLEN(Box*) val) {
    AUTO_DECREF(val);
    BoxAndHash val_hashed(val); // this can throw!
    incref(val_hashed);
    _setAddStolen(self, val_hashed);
}

namespace set {

class BoxedSetIterator : public Box {
public:
    BoxedSet* s;
    decltype(BoxedSet::s)::iterator it;
    long size;

    BoxedSetIterator(BoxedSet* s) : s(s), it(s->s.begin()), size(s->s.size()) { Py_INCREF(s); }

    DEFAULT_CLASS_SIMPLE(set_iterator_cls, true);

    bool hasNext() { return it != s->s.end(); }

    Box* next() {
        Box* rtn = it->value;
        ++it;
        return incref(rtn);
    }

    static void dealloc(BoxedSetIterator* o) noexcept {
        PyObject_GC_UnTrack(o);

        Py_DECREF(o->s);

        o->cls->tp_free(o);
    }

    static int traverse(BoxedSetIterator* self, visitproc visit, void* arg) noexcept {
        Py_VISIT(self->s);
        return 0;
    }
};

Box* setiteratorHasnext(BoxedSetIterator* self) {
    RELEASE_ASSERT(self->cls == set_iterator_cls, "");
    return boxBool(self->hasNext());
}

Box* setiteratorNext(BoxedSetIterator* self) {
    RELEASE_ASSERT(self->cls == set_iterator_cls, "");
    if (self->s->s.size() != self->size) {
        raiseExcHelper(RuntimeError, "Set changed size during iteration");
    }
    return self->next();
}

Box* setiteratorLength(BoxedSetIterator* self) {
    RELEASE_ASSERT(self->cls == set_iterator_cls, "");
    return boxInt(self->s->s.size());
}

Box* setiter_next(Box* _self) noexcept {
    RELEASE_ASSERT(_self->cls == set_iterator_cls, "");
    BoxedSetIterator* self = (BoxedSetIterator*)_self;
    if (!self->hasNext())
        return NULL;
    return self->next();
}

Box* setiteratorIter(BoxedSetIterator* self) {
    RELEASE_ASSERT(self->cls == set_iterator_cls, "");
    return incref(self);
}

static void _setAdd(BoxedSet* self, BoxAndHash val) {
    Py_INCREF(val.value);
    _setAddStolen(self, val);
}

static bool _setRemove(BoxedSet* self, BoxAndHash val) {
    auto it = self->s.find(val);
    if (it == self->s.end())
        return false;
    Box* to_decref = it->value;
    self->s.erase(it);
    Py_DECREF(to_decref);
    return true;
}

// Creates a set of type 'cls' from 'container' (NULL to get an empty set).
// Works for frozenset and normal set types.
BoxedSet* makeNewSet(BoxedClass* cls, Box* container) {
    assert(isSubclass(cls, frozenset_cls) || isSubclass(cls, set_cls));

    BoxedSet* rtn = new (cls) BoxedSet();
    if (container) {
        AUTO_DECREF(rtn);
        if (PyAnySet_Check(container)) {
            for (auto&& elt : ((BoxedSet*)container)->s) {
                rtn->s.insert(incref(elt));
            }
        } else if (PyDict_CheckExact(container)) {
            for (auto&& elt : ((BoxedDict*)container)->d) {
                rtn->s.insert(incref(elt.first));
            }
        } else {
            for (auto elt : container->pyElements()) {
                _setAddStolen(rtn, elt);
            }
        }
        return incref(rtn);
    }
    return rtn;
}

Box* frozensetNew(Box* _cls, Box* container, BoxedDict* kwargs) {
    RELEASE_ASSERT(PyType_Check(_cls), "");
    BoxedClass* cls = static_cast<BoxedClass*>(_cls);
    RELEASE_ASSERT(isSubclass(cls, frozenset_cls), "");
    if (_cls == frozenset_cls && !_PyArg_NoKeywords("frozenset()", kwargs)) {
        throwCAPIException();
    }

    if (_cls != frozenset_cls) {
        return makeNewSet(cls, container);
    }

    if (container != NULL) {
        if (container->cls == frozenset_cls)
            return incref(container);

        BoxedSet* result = makeNewSet(cls, container);
        if (result->s.size() != 0) {
            return result;
        }
        Py_DECREF(result);
    }

    static Box* emptyfrozenset = NULL;
    if (!emptyfrozenset) {
        emptyfrozenset = new (frozenset_cls) BoxedSet();
        PyGC_RegisterStaticConstant(emptyfrozenset);
    }

    Py_INCREF(emptyfrozenset);
    return emptyfrozenset;
}

Box* setNew(Box* _cls, Box* container, BoxedDict* kwargs) {
    RELEASE_ASSERT(PyType_Check(_cls), "");
    BoxedClass* cls = static_cast<BoxedClass*>(_cls);
    RELEASE_ASSERT(isSubclass(cls, set_cls), "");

    if (_cls == set_cls && !_PyArg_NoKeywords("set()", kwargs)) {
        throwCAPIException();
    }

    // Note: set.__new__ explicitly ignores the container argument.
    return makeNewSet(cls, NULL);
}

static void setClearInternal(BoxedSet* self) {
    ASSERT(PyAnySet_Check(self), "");

    if (self->s.size()) {
        BoxedSet::Set tmp;
        std::swap(tmp, self->s);
        for (auto p : tmp) {
            Py_DECREF(p.value);
        }
        self->s.clear();
    }
}

Box* setInit(Box* _self, Box* container, BoxedDict* kwargs) {
    RELEASE_ASSERT(PySet_Check(_self), "");

    if (PySet_Check(_self) && !_PyArg_NoKeywords("set()", kwargs)) {
        throwCAPIException();
    }

    if (!container)
        return incref(Py_None);

    BoxedSet* self = static_cast<BoxedSet*>(_self);

    setClearInternal(self);

    if (PyAnySet_Check(container)) {
        for (auto&& elt : ((BoxedSet*)container)->s) {
            self->s.insert(incref(elt));
        }
    } else if (PyDict_CheckExact(container)) {
        for (auto&& elt : ((BoxedDict*)container)->d) {
            self->s.insert(incref(elt.first));
        }

    } else {
        for (auto elt : container->pyElements()) {
            _setAddStolen(self, elt);
        }
    }

    return incref(Py_None);
}

static Box* setRepr(Box* _self) {
    if (!PyAnySet_Check(_self))
        return setDescrTypeError<CXX>(_self, "set", "__repr__");

    BoxedSet* self = (BoxedSet*)_self;
    std::vector<char> chars;
    int status = Py_ReprEnter((PyObject*)self);

    if (status != 0) {
        if (status < 0)
            throwCAPIException();

        std::string ty = std::string(self->cls->tp_name);
        chars.insert(chars.end(), ty.begin(), ty.end());
        chars.push_back('(');
        chars.push_back('.');
        chars.push_back('.');
        chars.push_back('.');
        chars.push_back(')');

        return boxString(llvm::StringRef(&chars[0], chars.size()));
    }

    try {
        std::string ty = std::string(self->cls->tp_name);
        chars.insert(chars.end(), ty.begin(), ty.end());

        chars.push_back('(');
        chars.push_back('[');

        bool first = true;
        for (auto&& elt : self->s) {

            if (!first) {
                chars.push_back(',');
                chars.push_back(' ');
            }
            BoxedString* str = static_cast<BoxedString*>(repr(elt.value));
            AUTO_DECREF(str);
            chars.insert(chars.end(), str->s().begin(), str->s().end());

            first = false;
        }
        chars.push_back(']');
        chars.push_back(')');
    } catch (ExcInfo e) {
        Py_ReprLeave((PyObject*)self);
        throw e;
    }
    Py_ReprLeave((PyObject*)self);
    return boxString(llvm::StringRef(&chars[0], chars.size()));
}
static Box* set_repr(Box* self) noexcept {
    return callCXXFromStyle<CAPI>(setRepr, self);
}

static void _setSymmetricDifferenceUpdate(BoxedSet* self, Box* other) {
    if (!PyAnySet_Check(other)) {
        other = makeNewSet(self->cls, other);
    } else {
        Py_INCREF(other);
    }
    AUTO_DECREF(other);

    BoxedSet* other_set = static_cast<BoxedSet*>(other);

    for (auto elt : other_set->s) {
        auto&& p = self->s.insert(elt);
        if (!p.second /* already exists */) {
            _setRemove(self, elt);
        } else {
            Py_INCREF(elt.value);
        }
    }
}

static BoxedSet* setIntersection2(BoxedSet* self, Box* container) {
    RELEASE_ASSERT(PyAnySet_Check(self), "");

    BoxedSet* rtn = makeNewSet(self->cls, NULL);
    AUTO_DECREF(rtn);
    for (auto elt : container->pyElements()) {
        AUTO_DECREF(elt);
        BoxAndHash elt_hashed(elt); // this can throw!
        if (self->s.count(elt_hashed))
            _setAdd(rtn, elt_hashed);
    }
    return incref(rtn);
}

static Box* setIntersectionUpdate2(BoxedSet* self, Box* other) {
    Box* tmp = setIntersection2(self, other);
    std::swap(self->s, ((BoxedSet*)tmp)->s);
    Py_DECREF(tmp);
    return incref(Py_None);
}

Box* setIOr(BoxedSet* lhs, BoxedSet* rhs) {
    RELEASE_ASSERT(PyAnySet_Check(lhs), "");
    if (!PyAnySet_Check(rhs))
        return incref(NotImplemented);

    // TODO just [write and] call setUnionUpdate2
    for (auto&& elt : rhs->s) {
        _setAdd(lhs, elt);
    }
    return incref(lhs);
}

Box* setOr(BoxedSet* lhs, BoxedSet* rhs) {
    RELEASE_ASSERT(PyAnySet_Check(lhs), "");
    if (!PyAnySet_Check(rhs))
        return incref(NotImplemented);

    BoxedSet* rtn = makeNewSet(lhs->cls, lhs);
    AUTO_DECREF(rtn);
    return setIOr(rtn, rhs);
}

Box* setIAnd(BoxedSet* lhs, BoxedSet* rhs) {
    RELEASE_ASSERT(PyAnySet_Check(lhs), "");
    if (!PyAnySet_Check(rhs))
        return incref(NotImplemented);

    auto r = setIntersectionUpdate2(lhs, rhs);
    Py_DECREF(r);
    return incref(lhs);
}

Box* setAnd(BoxedSet* lhs, BoxedSet* rhs) {
    RELEASE_ASSERT(PyAnySet_Check(lhs), "");
    if (!PyAnySet_Check(rhs))
        return incref(NotImplemented);

    return setIntersection2(lhs, rhs);
}

Box* setISub(BoxedSet* lhs, BoxedSet* rhs) {
    RELEASE_ASSERT(PyAnySet_Check(lhs), "");
    if (!PyAnySet_Check(rhs))
        return incref(NotImplemented);

    // TODO: write and call setDifferenceUpdate2
    for (auto&& elt : rhs->s) {
        _setRemove(lhs, elt);
    }
    return incref(lhs);
}

Box* setSub(BoxedSet* lhs, BoxedSet* rhs) {
    RELEASE_ASSERT(PyAnySet_Check(lhs), "");
    if (!PyAnySet_Check(rhs))
        return incref(NotImplemented);

    BoxedSet* rtn = makeNewSet(lhs->cls, lhs);
    AUTO_DECREF(rtn);
    return setISub(rtn, rhs);
}

Box* setIXor(BoxedSet* lhs, BoxedSet* rhs) {
    RELEASE_ASSERT(PyAnySet_Check(lhs), "");
    if (!PyAnySet_Check(rhs))
        return incref(NotImplemented);

    _setSymmetricDifferenceUpdate(lhs, rhs);

    return incref(lhs);
}

Box* setXor(BoxedSet* lhs, BoxedSet* rhs) {
    RELEASE_ASSERT(PyAnySet_Check(lhs), "");
    if (!PyAnySet_Check(rhs))
        return incref(NotImplemented);

    BoxedSet* rtn = makeNewSet(lhs->cls, lhs);
    AUTO_DECREF(rtn);
    return setIXor(rtn, rhs);
}

Box* setIter(BoxedSet* self) noexcept {
    RELEASE_ASSERT(PyAnySet_Check(self), "");
    return new BoxedSetIterator(self);
}

Box* setLen(BoxedSet* self) {
    RELEASE_ASSERT(PyAnySet_Check(self), "");
    return boxInt(self->s.size());
}

Box* setAdd(BoxedSet* self, Box* v) {
    RELEASE_ASSERT(isSubclass(self->cls, set_cls), "%s", self->cls->tp_name);

    _setAdd(self, v);
    return incref(Py_None);
}

// Note: PySet_Add is allowed to apply to frozenset objects, though CPython has
// an check to make sure the refcount is 1.
// for example, the marshal library uses this to construct frozenset objects.
extern "C" int PySet_Add(PyObject* set, PyObject* key) noexcept {
    if (!PyAnySet_Check(set)) {
        PyErr_BadInternalCall();
        return -1;
    }

    try {
        _setAdd(static_cast<BoxedSet*>(set), key);
        return 0;
    } catch (ExcInfo e) {
        setCAPIException(e);
        return -1;
    }
}

extern "C" Py_ssize_t PySet_Size(PyObject* anyset) noexcept {
    if (!PyAnySet_Check(anyset)) {
        PyErr_BadInternalCall();
        return -1;
    }
    BoxedSet* self = (BoxedSet*)anyset;
    return self->s.size();
}

Box* setClear(BoxedSet* self) {
    RELEASE_ASSERT(isSubclass(self->cls, set_cls), "");

    setClearInternal(self);
    Py_RETURN_NONE;
}

extern "C" int PySet_Clear(PyObject* set) noexcept {
    if (!PySet_Check(set)) {
        PyErr_BadInternalCall();
        return -1;
    }
    setClearInternal(static_cast<BoxedSet*>(set));
    return 0;
}

Box* setUpdate(BoxedSet* self, BoxedTuple* args) {
    RELEASE_ASSERT(isSubclass(self->cls, set_cls), "");

    assert(args->cls == tuple_cls);

    for (auto l : *args) {
        if (l->cls == set_cls) {
            BoxedSet* s2 = static_cast<BoxedSet*>(l);
            for (auto&& p : s2->s) {
                _setAdd(self, p);
            }
        } else {
            for (auto e : l->pyElements()) {
                _setAddStolen(self, e);
            }
        }
    }

    return incref(Py_None);
}

Box* setUnion(BoxedSet* self, BoxedTuple* args) {
    if (!PyAnySet_Check(self))
        raiseExcHelper(TypeError, "descriptor 'union' requires a 'set' object but received a '%s'", getTypeName(self));

    BoxedSet* rtn = makeNewSet(self->cls, self);
    AUTO_DECREF(rtn);

    for (auto container : args->pyElements()) {
        AUTO_DECREF(container);
        for (auto elt : container->pyElements()) {
            _setAddStolen(rtn, elt);
        }
    }
    return incref(rtn);
}

static void _setDifferenceUpdate(BoxedSet* self, BoxedTuple* args) {
    for (auto container : args->pyElements()) {
        AUTO_DECREF(container);
        if (PyAnySet_Check(container)) {
            for (auto&& elt : ((BoxedSet*)container)->s) {
                _setRemove(self, elt);
            }
        } else if (PyDict_CheckExact(container)) {
            for (auto&& elt : ((BoxedDict*)container)->d) {
                _setRemove(self, elt.first);
            }
        } else {
            for (auto elt : container->pyElements()) {
                AUTO_DECREF(elt);
                _setRemove(self, elt);
            }
        }
    }
}

Box* setDifferenceUpdate(BoxedSet* self, BoxedTuple* args) {
    if (!PySet_Check(self))
        raiseExcHelper(TypeError, "descriptor 'difference_update' requires a 'set' object but received a '%s'",
                       getTypeName(self));

    _setDifferenceUpdate(self, args);
    return incref(Py_None);
}

Box* setDifference(BoxedSet* self, BoxedTuple* args) {
    if (!PyAnySet_Check(self))
        raiseExcHelper(TypeError, "descriptor 'difference' requires a 'set' object but received a '%s'",
                       getTypeName(self));

    BoxedSet* rtn = makeNewSet(self->cls, self);
    AUTO_DECREF(rtn);
    _setDifferenceUpdate(rtn, args);
    return incref(rtn);
}

Box* setSymmetricDifferenceUpdate(BoxedSet* self, Box* other) {
    if (!PySet_Check(self))
        raiseExcHelper(TypeError,
                       "descriptor 'symmetric_difference_update' requires a 'set' object but received a '%s'",
                       getTypeName(self));

    _setSymmetricDifferenceUpdate(self, other);
    return incref(Py_None);
}

Box* setSymmetricDifference(BoxedSet* self, Box* other) {
    if (!PyAnySet_Check(self))
        raiseExcHelper(TypeError, "descriptor 'symmetric_difference' requires a 'set' object but received a '%s'",
                       getTypeName(self));

    BoxedSet* rtn = makeNewSet(self->cls, self);
    AUTO_DECREF(rtn);
    _setSymmetricDifferenceUpdate(rtn, other);
    return incref(rtn);
}

static Box* setIssubset(BoxedSet* self, Box* container) {
    RELEASE_ASSERT(PyAnySet_Check(self), "");

    if (!PyAnySet_Check(container)) {
        container = makeNewSet(set_cls, container);
    } else {
        Py_INCREF(container);
    }
    AUTO_DECREF(container);
    assert(PyAnySet_Check(container));

    BoxedSet* rhs = static_cast<BoxedSet*>(container);
    if (self->s.size() > rhs->s.size())
        Py_RETURN_FALSE;

    for (auto e : self->s) {
        if (rhs->s.find(e) == rhs->s.end())
            Py_RETURN_FALSE;
    }
    Py_RETURN_TRUE;
}

static Box* setIssuperset(BoxedSet* self, Box* container) {
    RELEASE_ASSERT(PyAnySet_Check(self), "");

    if (!PyAnySet_Check(container)) {
        container = makeNewSet(set_cls, container);
    } else {
        Py_INCREF(container);
    }
    AUTO_DECREF(container);
    assert(PyAnySet_Check(container));
    return setIssubset((BoxedSet*)container, self);
}

static Box* setIsdisjoint(BoxedSet* self, Box* container) {
    RELEASE_ASSERT(PyAnySet_Check(self), "");

    for (auto e : container->pyElements()) {
        AUTO_DECREF(e);
        if (self->s.find(e) != self->s.end())
            Py_RETURN_FALSE;
    }
    Py_RETURN_TRUE;
}

static Box* setIntersection(BoxedSet* self, BoxedTuple* args) {
    if (!PyAnySet_Check(self))
        raiseExcHelper(TypeError, "descriptor 'intersection' requires a 'set' object but received a '%s'",
                       getTypeName(self));

    if (args->size() == 0)
        return makeNewSet(self->cls, self);

    BoxedSet* rtn = incref(self);
    for (auto container : *args) {
        AUTO_DECREF(rtn);
        rtn = setIntersection2(rtn, container);
    }
    return rtn;
}

static Box* setIntersectionUpdate(BoxedSet* self, BoxedTuple* args) {
    Box* tmp = setIntersection(self, args);
    AUTO_DECREF(tmp);
    std::swap(self->s, ((BoxedSet*)tmp)->s);
    return incref(Py_None);
}

Box* setCopy(BoxedSet* self) {
    RELEASE_ASSERT(PyAnySet_Check(self), "");

    BoxedSet* rtn = new BoxedSet();
    for (auto&& p : self->s)
        Py_INCREF(p.value);
    rtn->s = self->s;
    return rtn;
}

Box* frozensetCopy(BoxedSet* self) {
    RELEASE_ASSERT(PyAnySet_Check(self), "");

    if (self->cls == frozenset_cls) {
        return incref(self);
    }
    return setCopy(self);
}

Box* setPop(BoxedSet* self) {
    RELEASE_ASSERT(isSubclass(self->cls, set_cls), "");

    if (!self->s.size())
        raiseExcHelper(KeyError, "pop from an empty set");

    auto it = self->s.begin();
    Box* rtn = it->value;
    self->s.erase(it);
    return rtn;
}

Box* setEq(BoxedSet* self, BoxedSet* rhs) {
    RELEASE_ASSERT(PyAnySet_Check(self), "");
    if (!PyAnySet_Check(rhs))
        Py_RETURN_FALSE;

    if (self->s.size() != rhs->s.size())
        Py_RETURN_FALSE;

    return setIssubset(self, rhs);
}

Box* setNe(BoxedSet* self, BoxedSet* rhs) {
    Box* r = setEq(self, rhs);
    AUTO_DECREF(r);
    assert(r->cls == bool_cls);
    return boxBool(r == Py_False);
}

Box* setLe(BoxedSet* self, BoxedSet* rhs) {
    RELEASE_ASSERT(PyAnySet_Check(self), "");
    if (!PyAnySet_Check(rhs))
        raiseExcHelper(TypeError, "can only compare to a set");

    return setIssubset(self, rhs);
}

Box* setLt(BoxedSet* self, BoxedSet* rhs) {
    RELEASE_ASSERT(PyAnySet_Check(self), "");
    if (!PyAnySet_Check(rhs))
        raiseExcHelper(TypeError, "can only compare to a set");

    if (self->s.size() >= rhs->s.size())
        Py_RETURN_FALSE;

    return setIssubset(self, rhs);
}

Box* setGe(BoxedSet* self, BoxedSet* rhs) {
    RELEASE_ASSERT(PyAnySet_Check(self), "");
    if (!PyAnySet_Check(rhs))
        raiseExcHelper(TypeError, "can only compare to a set");

    return setIssuperset(self, rhs);
}

Box* setGt(BoxedSet* self, BoxedSet* rhs) {
    RELEASE_ASSERT(PyAnySet_Check(self), "");
    if (!PyAnySet_Check(rhs))
        raiseExcHelper(TypeError, "can only compare to a set");

    if (self->s.size() <= rhs->s.size())
        Py_RETURN_FALSE;

    return setIssuperset(self, rhs);
}

Box* setContains(BoxedSet* self, Box* key) {
    RELEASE_ASSERT(PyAnySet_Check(self), "");

    if (PySet_Check(key)) {
        try {
            BoxAndHash k_hash(key);
            return boxBool(self->s.find(k_hash) != self->s.end());
        } catch (ExcInfo e) {
            if (!e.matches(TypeError))
                throw e;

            e.clear();

            BoxedSet* tmpKey = makeNewSet(frozenset_cls, key);
            AUTO_DECREF(tmpKey);
            return boxBool(self->s.find(tmpKey) != self->s.end());
        }
    }

    return boxBool(self->s.find(key) != self->s.end());
}

Box* setRemove(BoxedSet* self, Box* key) {
    RELEASE_ASSERT(isSubclass(self->cls, set_cls), "");

    if (PySet_Check(key)) {
        try {
            bool existed = _setRemove(self, key);
            if (existed)
                return incref(Py_None);
        } catch (ExcInfo e) {
            if (!e.matches(TypeError))
                throw e;

            e.clear();

            BoxedSet* tmpKey = makeNewSet(frozenset_cls, key);
            AUTO_DECREF(tmpKey);
            bool existed = _setRemove(self, tmpKey);
            if (existed)
                return incref(Py_None);
        }
        raiseExcHelper(KeyError, key);
    }

    bool existed = _setRemove(self, key);
    if (existed)
        return incref(Py_None);
    raiseExcHelper(KeyError, key);
}

Box* setDiscard(BoxedSet* self, Box* key) {
    RELEASE_ASSERT(isSubclass(self->cls, set_cls), "");

    if (PySet_Check(key)) {
        try {
            _setRemove(self, key);
        } catch (ExcInfo e) {
            if (!e.matches(TypeError))
                throw e;

            e.clear();

            BoxedSet* tmpKey = makeNewSet(frozenset_cls, key);
            AUTO_DECREF(tmpKey);
            _setRemove(self, tmpKey);
        }
        return incref(Py_None);
    }

    _setRemove(self, key);

    return incref(Py_None);
}

Box* setNocmp(BoxedSet* self, BoxedSet* rhs) {
    raiseExcHelper(TypeError, "cannot compare sets using cmp()");
}

Box* setNonzero(BoxedSet* self) {
    RELEASE_ASSERT(PyAnySet_Check(self), "");
    return boxBool(self->s.size());
}

Box* setNotImplemented(BoxedSet* self) {
    raiseExcHelper(TypeError, "unhashable type: 'set'");
}

Box* setHash(BoxedSet* self) {
    RELEASE_ASSERT(isSubclass(self->cls, frozenset_cls), "");

    int64_t h, hash = 1927868237L;

    hash *= self->s.size() + 1;
    for (auto&& e : self->s) {
        h = e.hash;
        hash ^= (h ^ (h << 16) ^ 89869747L) * 3644798167u;
    }

    hash = hash * 69069L + 907133923L;
    if (hash == -1)
        hash = 590923713L;

    return boxInt(hash);
}

extern "C" PyObject* PySet_New(PyObject* iterable) noexcept {
    if (!iterable)
        return new BoxedSet(); // Fast path for empty set.

    try {
        return runtimeCall(set_cls, ArgPassSpec(iterable ? 1 : 0), iterable, NULL, NULL, NULL, NULL);
    } catch (ExcInfo e) {
        setCAPIException(e);
        return NULL;
    }
}

extern "C" PyObject* PyFrozenSet_New(PyObject* iterable) noexcept {
    try {
        return runtimeCall(frozenset_cls, ArgPassSpec(iterable ? 1 : 0), iterable, NULL, NULL, NULL, NULL);
    } catch (ExcInfo e) {
        setCAPIException(e);
        return NULL;
    }
}

static PyObject* set_reduce(BoxedSet* so) noexcept {
    PyObject* keys = NULL, * args = NULL, * result = NULL, * dict = NULL;

    keys = PySequence_List((PyObject*)so);
    if (keys == NULL)
        goto done;
    args = PyTuple_Pack(1, keys);
    if (args == NULL)
        goto done;
    dict = PyObject_GetAttrString((PyObject*)so, "__dict__");
    if (dict == NULL) {
        PyErr_Clear();
        dict = Py_None;
        Py_INCREF(dict);
    }
    result = PyTuple_Pack(3, Py_TYPE(so), args, dict);
done:
    Py_XDECREF(args);
    Py_XDECREF(keys);
    Py_XDECREF(dict);
    return result;
}


} // namespace set

using namespace pyston::set;

void BoxedSet::dealloc(Box* _o) noexcept {
    BoxedSet* o = (BoxedSet*)_o;

    PyObject_ClearWeakRefs(o);

    PyObject_GC_UnTrack(o);
    for (auto p : o->s) {
        Py_DECREF(p.value);
    }

    // Unfortunately, this assert requires accessing the type object, which might have been freed already:
    // assert(PyAnySet_Check(b));
    o->s.freeAllMemory();

    o->cls->tp_free(o);
}

int BoxedSet::traverse(Box* _o, visitproc visit, void* arg) noexcept {
    BoxedSet* o = (BoxedSet*)_o;

    for (auto p : o->s) {
        Py_VISIT(p.value);
    }
    return 0;
}

int BoxedSet::clear(Box* _o) noexcept {
    BoxedSet* o = (BoxedSet*)_o;

    setClearInternal(o);

    return 0;
}

static PyMethodDef set_methods[]
    = { { "__reduce__", (PyCFunction)set_reduce, METH_NOARGS, NULL }, { NULL, NULL, 0, NULL } };
static PyMethodDef frozenset_methods[]
    = { { "__reduce__", (PyCFunction)set_reduce, METH_NOARGS, NULL }, { NULL, NULL, 0, NULL } };

void setupSet() {
    static PySequenceMethods set_as_sequence;
    set_cls->tp_as_sequence = &set_as_sequence;
    static PyNumberMethods set_as_number;
    set_cls->tp_as_number = &set_as_number;
    static PySequenceMethods frozenset_as_sequence;
    frozenset_cls->tp_as_sequence = &frozenset_as_sequence;
    static PyNumberMethods frozenset_as_number;
    frozenset_cls->tp_as_number = &frozenset_as_number;

    set_cls->tp_dealloc = frozenset_cls->tp_dealloc = BoxedSet::dealloc;

    set_iterator_cls = BoxedClass::create(type_cls, object_cls, 0, 0, sizeof(BoxedSetIterator), false, "setiterator",
                                          false, (destructor)BoxedSetIterator::dealloc, NULL, true,
                                          (traverseproc)BoxedSetIterator::traverse, NOCLEAR);
    set_iterator_cls->giveAttr(
        "__iter__", new BoxedFunction(BoxedCode::create((void*)setiteratorIter, typeFromClass(set_iterator_cls), 1,
                                                        "setiterator.__iter__")));
    set_iterator_cls->giveAttr("__hasnext__", new BoxedFunction(BoxedCode::create((void*)setiteratorHasnext, BOXED_BOOL,
                                                                                  1, "setiterator.__hasnext__")));
    set_iterator_cls->giveAttr(
        "next", new BoxedFunction(BoxedCode::create((void*)setiteratorNext, UNKNOWN, 1, "setiterator.next")));
    set_iterator_cls->giveAttr(
        "__length_hint__",
        new BoxedFunction(BoxedCode::create((void*)setiteratorLength, BOXED_INT, 1, "setiterator.__length_hint__")));
    set_iterator_cls->freeze();
    set_iterator_cls->tp_iternext = setiter_next;
    set_iterator_cls->tp_iter = PyObject_SelfIter;

    set_cls->giveAttr(
        "__new__",
        new BoxedFunction(BoxedCode::create((void*)setNew, UNKNOWN, 2, false, true, "set.__new__"), { NULL }));
    set_cls->giveAttr(
        "__init__",
        new BoxedFunction(BoxedCode::create((void*)setInit, UNKNOWN, 2, false, true, "set.__init__"), { NULL }));
    frozenset_cls->giveAttr(
        "__new__", new BoxedFunction(
                       BoxedCode::create((void*)frozensetNew, UNKNOWN, 2, false, true, "fozenset.__new__"), { NULL }));

    Box* set_repr_func = new BoxedFunction(BoxedCode::create((void*)setRepr, STR, 1, "set.__repr__"));
    set_cls->giveAttrBorrowed("__repr__", set_repr_func);
    frozenset_cls->giveAttr("__repr__", set_repr_func);

    std::vector<ConcreteCompilerType*> v_ss, v_sf, v_su, v_ff, v_fs, v_fu;
    v_ss.push_back(SET);
    v_ss.push_back(SET);
    v_sf.push_back(SET);
    v_sf.push_back(FROZENSET);
    v_su.push_back(SET);
    v_su.push_back(UNKNOWN);

    v_ff.push_back(FROZENSET);
    v_ff.push_back(FROZENSET);
    v_fs.push_back(FROZENSET);
    v_fs.push_back(SET);
    v_fu.push_back(FROZENSET);
    v_fu.push_back(UNKNOWN);

    auto add = [&](const char* name, void* func) {
        auto func_obj = new BoxedFunction(BoxedCode::create((void*)func, UNKNOWN, 2, false, false, name));
        set_cls->giveAttrBorrowed(name, func_obj);
        frozenset_cls->giveAttr(name, func_obj);
        /*
        BoxedCode* func_obj = BoxedCode::create(2, false, false);
        addRTFunction(func_obj, (void*)func, SET, v_ss);
        addRTFunction(func_obj, (void*)func, SET, v_sf);
        addRTFunction(func_obj, (void*)func, FROZENSET, v_fs);
        addRTFunction(func_obj, (void*)func, FROZENSET, v_ff);
        set_cls->giveAttr(name, new BoxedFunction(func_obj));
        frozenset_cls->giveAttr(name, set_cls->getattr(internStringMortal(name)));
        */
    };

    add("__or__", (void*)setOr);
    add("__sub__", (void*)setSub);
    add("__xor__", (void*)setXor);
    add("__and__", (void*)setAnd);
    add("__ior__", (void*)setIOr);
    add("__isub__", (void*)setISub);
    add("__ixor__", (void*)setIXor);
    add("__iand__", (void*)setIAnd);

    set_cls->giveAttr("__iter__", new BoxedFunction(BoxedCode::create((void*)setIter, typeFromClass(set_iterator_cls),
                                                                      1, "set.__iter__")));
    frozenset_cls->giveAttrBorrowed("__iter__", set_cls->getattr(getStaticString("__iter__")));

    set_cls->giveAttr("__len__", new BoxedFunction(BoxedCode::create((void*)setLen, BOXED_INT, 1, "set.__len__")));
    frozenset_cls->giveAttrBorrowed("__len__", set_cls->getattr(getStaticString("__len__")));

    set_cls->giveAttr("__contains__",
                      new BoxedFunction(BoxedCode::create((void*)setContains, BOXED_BOOL, 2, "set.__contains__")));
    frozenset_cls->giveAttrBorrowed("__contains__", set_cls->getattr(getStaticString("__contains__")));

    set_cls->giveAttr("__cmp__", new BoxedFunction(BoxedCode::create((void*)setNocmp, NONE, 2, "set.__cmp__")));
    frozenset_cls->giveAttr("__cmp__", new BoxedFunction(BoxedCode::create((void*)setNocmp, NONE, 2, "set.__cmp__")));
    set_cls->giveAttr("__eq__", new BoxedFunction(BoxedCode::create((void*)setEq, BOXED_BOOL, 2, "set.__eq__")));
    frozenset_cls->giveAttrBorrowed("__eq__", set_cls->getattr(getStaticString("__eq__")));
    set_cls->giveAttr("__ne__", new BoxedFunction(BoxedCode::create((void*)setNe, BOXED_BOOL, 2, "set.__ne__")));
    frozenset_cls->giveAttrBorrowed("__ne__", set_cls->getattr(getStaticString("__ne__")));
    set_cls->giveAttr("__le__", new BoxedFunction(BoxedCode::create((void*)setLe, BOXED_BOOL, 2, "set.__le__")));
    frozenset_cls->giveAttrBorrowed("__le__", set_cls->getattr(getStaticString("__le__")));
    set_cls->giveAttr("__lt__", new BoxedFunction(BoxedCode::create((void*)setLt, BOXED_BOOL, 2, "set.__lt__")));
    frozenset_cls->giveAttrBorrowed("__lt__", set_cls->getattr(getStaticString("__lt__")));
    set_cls->giveAttr("__ge__", new BoxedFunction(BoxedCode::create((void*)setGe, BOXED_BOOL, 2, "set.__ge__")));
    frozenset_cls->giveAttrBorrowed("__ge__", set_cls->getattr(getStaticString("__ge__")));
    set_cls->giveAttr("__gt__", new BoxedFunction(BoxedCode::create((void*)setGt, BOXED_BOOL, 2, "set.__gt__")));
    frozenset_cls->giveAttrBorrowed("__gt__", set_cls->getattr(getStaticString("__gt__")));

    set_cls->giveAttr("__nonzero__",
                      new BoxedFunction(BoxedCode::create((void*)setNonzero, BOXED_BOOL, 1, "set.__nonzero__")));
    frozenset_cls->giveAttrBorrowed("__nonzero__", set_cls->getattr(getStaticString("__nonzero__")));

    frozenset_cls->giveAttr("__hash__",
                            new BoxedFunction(BoxedCode::create((void*)setHash, BOXED_INT, 1, "set.__hash__")));
    set_cls->giveAttrBorrowed("__hash__", Py_None);

    set_cls->giveAttr("add", new BoxedFunction(BoxedCode::create((void*)setAdd, NONE, 2, "set.add")));
    set_cls->giveAttr("remove", new BoxedFunction(BoxedCode::create((void*)setRemove, NONE, 2, "set.remove")));
    set_cls->giveAttr("discard", new BoxedFunction(BoxedCode::create((void*)setDiscard, NONE, 2, "set.discard")));

    set_cls->giveAttr("clear", new BoxedFunction(BoxedCode::create((void*)setClear, NONE, 1, "set.clear")));
    set_cls->giveAttr("update",
                      new BoxedFunction(BoxedCode::create((void*)setUpdate, NONE, 1, true, false, "set.update")));
    set_cls->giveAttr("union",
                      new BoxedFunction(BoxedCode::create((void*)setUnion, UNKNOWN, 1, true, false, "set.union")));
    frozenset_cls->giveAttrBorrowed("union", set_cls->getattr(getStaticString("union")));
    set_cls->giveAttr("intersection", new BoxedFunction(BoxedCode::create((void*)setIntersection, UNKNOWN, 1, true,
                                                                          false, "set.intersection")));
    frozenset_cls->giveAttrBorrowed("intersection", set_cls->getattr(getStaticString("intersection")));
    set_cls->giveAttr("intersection_update",
                      new BoxedFunction(BoxedCode::create((void*)setIntersectionUpdate, UNKNOWN, 1, true, false,
                                                          "set.intersection_update")));
    set_cls->giveAttr("difference", new BoxedFunction(BoxedCode::create((void*)setDifference, UNKNOWN, 1, true, false,
                                                                        "set.difference")));
    frozenset_cls->giveAttrBorrowed("difference", set_cls->getattr(getStaticString("difference")));
    set_cls->giveAttr("difference_update", new BoxedFunction(BoxedCode::create((void*)setDifferenceUpdate, UNKNOWN, 1,
                                                                               true, false, "set.difference_update")));
    set_cls->giveAttr("symmetric_difference",
                      new BoxedFunction(BoxedCode::create((void*)setSymmetricDifference, UNKNOWN, 2, false, false,
                                                          "set.symmetric_difference")));
    frozenset_cls->giveAttrBorrowed("symmetric_difference", set_cls->getattr(getStaticString("symmetric_difference")));
    set_cls->giveAttr("symmetric_difference_update",
                      new BoxedFunction(BoxedCode::create((void*)setSymmetricDifferenceUpdate, UNKNOWN, 2, false, false,
                                                          "set.symmetric_difference_update")));
    set_cls->giveAttr("issubset", new BoxedFunction(BoxedCode::create((void*)setIssubset, UNKNOWN, 2, "set.issubset")));
    frozenset_cls->giveAttrBorrowed("issubset", set_cls->getattr(getStaticString("issubset")));
    set_cls->giveAttr("issuperset",
                      new BoxedFunction(BoxedCode::create((void*)setIssuperset, UNKNOWN, 2, "set.issuperset")));
    frozenset_cls->giveAttrBorrowed("issuperset", set_cls->getattr(getStaticString("issuperset")));
    set_cls->giveAttr("isdisjoint",
                      new BoxedFunction(BoxedCode::create((void*)setIsdisjoint, UNKNOWN, 2, "set.isdisjoint")));
    frozenset_cls->giveAttrBorrowed("isdisjoint", set_cls->getattr(getStaticString("isdisjoint")));

    set_cls->giveAttr("copy", new BoxedFunction(BoxedCode::create((void*)setCopy, UNKNOWN, 1, "set.copy")));
    frozenset_cls->giveAttr("copy", new BoxedFunction(BoxedCode::create((void*)frozensetCopy, UNKNOWN, 1, "set.copy")));
    set_cls->giveAttr("pop", new BoxedFunction(BoxedCode::create((void*)setPop, UNKNOWN, 1, "set.pop")));

    add_methods(set_cls, set_methods);
    add_methods(frozenset_cls, frozenset_methods);

    set_cls->freeze();
    frozenset_cls->freeze();

    frozenset_cls->tp_repr = set_cls->tp_repr = set_repr;
    frozenset_cls->tp_iter = set_cls->tp_iter = (decltype(set_cls->tp_iter))setIter;
}
}
