// Copyright (c) 2014 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/tuple.h"

#include <sstream>

#include "codegen/compvars.h"
#include "core/ast.h"
#include "core/common.h"
#include "core/stats.h"
#include "core/types.h"
#include "gc/collector.h"
#include "runtime/objmodel.h"
#include "runtime/types.h"
#include "runtime/util.h"

namespace pyston {

extern "C" Box* createTuple(int64_t nelts, Box** elts) {
    BoxedTuple::GCVector velts(elts, elts + nelts);
    return new BoxedTuple(std::move(velts));
}

Box* _tupleSlice(BoxedTuple* self, i64 start, i64 stop, i64 step) {

    i64 size = self->elts.size();
    assert(step != 0);
    if (step > 0) {
        assert(0 <= start);
        assert(stop <= size);
    } else {
        assert(start < size);
        assert(-1 <= stop);
    }

    // This is adapted from CPython's PySlice_GetIndicesEx.
    i64 slicelength;
    if (step < 0)
        slicelength = (stop - start + 1) / (step)+1;
    else
        slicelength = (stop - start - 1) / (step)+1;

    if (slicelength < 0)
        slicelength = 0;

    // FIXME: No need to initialize with 0.
    BoxedTuple::GCVector velts(slicelength, 0);

    i64 curr, i;
    for (curr = start, i = 0; i < slicelength; curr += step, i++)
        velts[i] = self->elts[curr];

    return new BoxedTuple(std::move(velts));
}

Box* tupleGetitemInt(BoxedTuple* self, BoxedInt* slice) {
    i64 n = slice->n;
    i64 size = self->elts.size();

    if (n < 0)
        n = size - n;
    if (n < 0 || n >= size) {
        fprintf(stderr, "IndexError: tuple index out of range\n");
        raiseExcHelper(IndexError, "");
    }

    Box* rtn = self->elts[n];
    return rtn;
}

Box* tupleGetitemSlice(BoxedTuple* self, BoxedSlice* slice) {
    assert(self->cls == tuple_cls);
    assert(slice->cls == slice_cls);

    i64 start, stop, step;
    parseSlice(slice, self->elts.size(), &start, &stop, &step);
    return _tupleSlice(self, start, stop, step);
}

Box* tupleGetitem(BoxedTuple* self, Box* slice) {
    assert(self->cls == tuple_cls);

    if (slice->cls == int_cls)
        return tupleGetitemInt(self, static_cast<BoxedInt*>(slice));
    else if (slice->cls == slice_cls)
        return tupleGetitemSlice(self, static_cast<BoxedSlice*>(slice));
    else
        raiseExcHelper(TypeError, "tuple indices must be integers, not %s", getTypeName(slice)->c_str());
}

Box* tupleAdd(BoxedTuple* self, Box* rhs) {
    if (rhs->cls != tuple_cls) {
        return NotImplemented;
    }

    BoxedTuple* _rhs = static_cast<BoxedTuple*>(rhs);
    BoxedTuple::GCVector velts;
    velts.insert(velts.end(), self->elts.begin(), self->elts.end());
    velts.insert(velts.end(), _rhs->elts.begin(), _rhs->elts.end());
    return new BoxedTuple(std::move(velts));
}

Box* tupleLen(BoxedTuple* t) {
    assert(t->cls == tuple_cls);
    return boxInt(t->elts.size());
}

Box* tupleRepr(BoxedTuple* t) {
    assert(t->cls == tuple_cls);

    std::ostringstream os("");
    os << "(";

    int n = t->elts.size();
    for (int i = 0; i < n; i++) {
        if (i)
            os << ", ";

        BoxedString* elt_repr = static_cast<BoxedString*>(repr(t->elts[i]));
        os << elt_repr->s;
    }
    if (n == 1)
        os << ",";
    os << ")";

    return boxString(os.str());
}

Box* _tupleCmp(BoxedTuple* lhs, BoxedTuple* rhs, AST_TYPE::AST_TYPE op_type) {
    int lsz = lhs->elts.size();
    int rsz = rhs->elts.size();

    bool is_order
        = (op_type == AST_TYPE::Lt || op_type == AST_TYPE::LtE || op_type == AST_TYPE::Gt || op_type == AST_TYPE::GtE);

    int n = std::min(lsz, rsz);
    for (int i = 0; i < n; i++) {
        Box* is_eq = compareInternal(lhs->elts[i], rhs->elts[i], AST_TYPE::Eq, NULL);
        bool bis_eq = nonzero(is_eq);

        if (bis_eq)
            continue;

        if (op_type == AST_TYPE::Eq) {
            return boxBool(false);
        } else if (op_type == AST_TYPE::NotEq) {
            return boxBool(true);
        } else {
            Box* r = compareInternal(lhs->elts[i], rhs->elts[i], op_type, NULL);
            return r;
        }
    }

    if (op_type == AST_TYPE::Lt)
        return boxBool(lsz < rsz);
    else if (op_type == AST_TYPE::LtE)
        return boxBool(lsz <= rsz);
    else if (op_type == AST_TYPE::Gt)
        return boxBool(lsz > rsz);
    else if (op_type == AST_TYPE::GtE)
        return boxBool(lsz >= rsz);
    else if (op_type == AST_TYPE::Eq)
        return boxBool(lsz == rsz);
    else if (op_type == AST_TYPE::NotEq)
        return boxBool(lsz != rsz);

    RELEASE_ASSERT(0, "%d", op_type);
}

Box* tupleLt(BoxedTuple* self, Box* rhs) {
    if (rhs->cls != tuple_cls) {
        return NotImplemented;
    }
    return _tupleCmp(self, static_cast<BoxedTuple*>(rhs), AST_TYPE::Lt);
}

Box* tupleLe(BoxedTuple* self, Box* rhs) {
    if (rhs->cls != tuple_cls) {
        return NotImplemented;
    }
    return _tupleCmp(self, static_cast<BoxedTuple*>(rhs), AST_TYPE::LtE);
}

Box* tupleGt(BoxedTuple* self, Box* rhs) {
    if (rhs->cls != tuple_cls) {
        return NotImplemented;
    }
    return _tupleCmp(self, static_cast<BoxedTuple*>(rhs), AST_TYPE::Gt);
}

Box* tupleGe(BoxedTuple* self, Box* rhs) {
    if (rhs->cls != tuple_cls) {
        return NotImplemented;
    }
    return _tupleCmp(self, static_cast<BoxedTuple*>(rhs), AST_TYPE::GtE);
}

Box* tupleEq(BoxedTuple* self, Box* rhs) {
    if (rhs->cls != tuple_cls) {
        return NotImplemented;
    }
    return _tupleCmp(self, static_cast<BoxedTuple*>(rhs), AST_TYPE::Eq);
}

Box* tupleNe(BoxedTuple* self, Box* rhs) {
    if (rhs->cls != tuple_cls) {
        return NotImplemented;
    }
    return _tupleCmp(self, static_cast<BoxedTuple*>(rhs), AST_TYPE::NotEq);
}

Box* tupleContains(BoxedTuple* self, Box* elt) {
    int size = self->elts.size();
    for (int i = 0; i < size; i++) {
        Box* e = self->elts[i];
        Box* cmp = compareInternal(e, elt, AST_TYPE::Eq, NULL);
        bool b = nonzero(cmp);
        if (b)
            return True;
    }
    return False;
}

Box* tupleHash(BoxedTuple* self) {
    assert(self->cls == tuple_cls);

    int64_t rtn = 3527539;
    for (Box* e : self->elts) {
        BoxedInt* h = hash(e);
        assert(h->cls == int_cls);
        rtn ^= h->n + 0x9e3779b9 + (rtn << 6) + (rtn >> 2);
    }

    return boxInt(rtn);
}

BoxedClass* tuple_iterator_cls = NULL;
extern "C" void tupleIteratorGCHandler(GCVisitor* v, Box* b) {
    boxGCHandler(v, b);
    BoxedTupleIterator* it = (BoxedTupleIterator*)b;
    v->visit(it->t);
}


void setupTuple() {
    tuple_iterator_cls = new BoxedClass(object_cls, &tupleIteratorGCHandler, 0, sizeof(BoxedTuple), false);

    tuple_cls->giveAttr("__name__", boxStrConstant("tuple"));

    CLFunction* getitem = createRTFunction(2, 0, 0, 0);
    addRTFunction(getitem, (void*)tupleGetitemInt, UNKNOWN,
                  std::vector<ConcreteCompilerType*>{ BOXED_TUPLE, BOXED_INT });
    addRTFunction(getitem, (void*)tupleGetitemSlice, SLICE, std::vector<ConcreteCompilerType*>{ BOXED_TUPLE, SLICE });
    addRTFunction(getitem, (void*)tupleGetitem, UNKNOWN, std::vector<ConcreteCompilerType*>{ BOXED_TUPLE, UNKNOWN });
    tuple_cls->giveAttr("__getitem__", new BoxedFunction(getitem));

    tuple_cls->giveAttr("__contains__", new BoxedFunction(boxRTFunction((void*)tupleContains, BOXED_BOOL, 2)));

    tuple_cls->giveAttr("__iter__",
                        new BoxedFunction(boxRTFunction((void*)tupleIter, typeFromClass(tuple_iterator_cls), 1)));


    tuple_cls->giveAttr("__lt__", new BoxedFunction(boxRTFunction((void*)tupleLt, UNKNOWN, 2)));
    tuple_cls->giveAttr("__le__", new BoxedFunction(boxRTFunction((void*)tupleLe, UNKNOWN, 2)));
    tuple_cls->giveAttr("__gt__", new BoxedFunction(boxRTFunction((void*)tupleGt, UNKNOWN, 2)));
    tuple_cls->giveAttr("__ge__", new BoxedFunction(boxRTFunction((void*)tupleGe, UNKNOWN, 2)));
    tuple_cls->giveAttr("__eq__", new BoxedFunction(boxRTFunction((void*)tupleEq, UNKNOWN, 2)));
    tuple_cls->giveAttr("__ne__", new BoxedFunction(boxRTFunction((void*)tupleNe, UNKNOWN, 2)));

    tuple_cls->giveAttr("__hash__", new BoxedFunction(boxRTFunction((void*)tupleHash, BOXED_INT, 1)));
    tuple_cls->giveAttr("__len__", new BoxedFunction(boxRTFunction((void*)tupleLen, BOXED_INT, 1)));
    tuple_cls->giveAttr("__repr__", new BoxedFunction(boxRTFunction((void*)tupleRepr, STR, 1)));
    tuple_cls->giveAttr("__str__", tuple_cls->getattr("__repr__"));
    tuple_cls->giveAttr("__add__", new BoxedFunction(boxRTFunction((void*)tupleAdd, BOXED_TUPLE, 2)));

    tuple_cls->freeze();

    gc::registerStaticRootObj(tuple_iterator_cls);
    tuple_iterator_cls->giveAttr("__name__", boxStrConstant("tupleiterator"));

    CLFunction* hasnext = boxRTFunction((void*)tupleiterHasnextUnboxed, BOOL, 1);
    addRTFunction(hasnext, (void*)tupleiterHasnext, BOXED_BOOL);
    tuple_iterator_cls->giveAttr("__hasnext__", new BoxedFunction(hasnext));
    tuple_iterator_cls->giveAttr(
        "__iter__", new BoxedFunction(boxRTFunction((void*)tupleIterIter, typeFromClass(tuple_iterator_cls), 1)));
    tuple_iterator_cls->giveAttr("next", new BoxedFunction(boxRTFunction((void*)tupleiterNext, UNKNOWN, 1)));

    tuple_iterator_cls->freeze();
}

void teardownTuple() {
    // TODO do clearattrs?
    // decref(tuple_iterator_cls);
}
}