#include <cassert>
#include <QDebugStateSaver>

#include "triangle.h"

Triangle::Triangle() {}

Triangle::Triangle(const Triangle &other) {
    this->vertex_buffer_ = other.vertex_buffer_;
    this->u_ = other.u_;
    this->v_ = other.v_;
    this->w_ = other.w_;
}

Triangle::Triangle(const QVector<Vertex> *vertex_buffer, unsigned int u, unsigned int v, unsigned int w) {
    this->vertex_buffer_ = vertex_buffer;
    this->u_ = u;
    this->v_ = v;
    this->w_ = w;
}

Vertex Triangle::u() const {
    return (*vertex_buffer_)[u_];
}

Vertex Triangle::v() const {
    return (*vertex_buffer_)[v_];
}

Vertex Triangle::w() const {
    return (*vertex_buffer_)[w_];
}

unsigned int Triangle::u_idx() const {
    return u_;
}

unsigned int Triangle::v_idx() const {
    return v_;
}

unsigned int Triangle::w_idx() const {
    return w_;
}

bool Triangle::get_shared_edge(Triangle other, Edge &edge_a, Edge &edge_b) const {
    if (get_shared_edge_(other, edge_a, edge_b)) {
        return true;
    }

    other.rotate_indices();
    if (get_shared_edge_(other, edge_a, edge_b)) {
        edge_b.name = rotate_edge_name(edge_b.name);
        return true;
    }

    other.rotate_indices();
    if (get_shared_edge_(other, edge_a, edge_b)) {
        edge_b.name = rotate_edge_name(rotate_edge_name(edge_b.name));
        return true;
    }

    return false;
}

bool Triangle::hasSharedEdge(Triangle other){
    Edge a,b;
    return get_shared_edge(other,a,b);
}

bool Triangle::get_shared_edge_(const Triangle &other, Edge &edge_a, Edge &edge_b) const {
    if (u().samePos(other.u())) {
        if (v().samePos(other.w())) {
            edge_a.name = Triangle::Edge::Name::uv;
            edge_a.a = u_idx();
            edge_a.b = v_idx();
            edge_a.c = w_idx();

            edge_b.name = Triangle::Edge::Name::wu;
            edge_b.a = other.w_idx();
            edge_b.b = other.u_idx();
            edge_b.c = other.v_idx();

            return true;
        } else if (w().samePos(other.v())) {
            edge_a.name = Triangle::Edge::Name::wu;
            edge_a.a = w_idx();
            edge_a.b = u_idx();
            edge_a.c = v_idx();

            edge_b.name = Triangle::Edge::Name::uv;
            edge_b.a = other.u_idx();
            edge_b.b = other.v_idx();
            edge_b.c = other.w_idx();

            return true;
        }
    } else if (v().samePos(other.u()) && w().samePos(other.w())) {
        edge_a.name = Triangle::Edge::Name::vw;
        edge_a.a = v_idx();
        edge_a.b = w_idx();
        edge_a.c = u_idx();

        edge_b.name = Triangle::Edge::Name::wu;
        edge_b.a = other.w_idx();
        edge_b.b = other.u_idx();
        edge_b.c = other.v_idx();

        return true;
    }

    return false;
}

//e.g. first.name = uv, next.name = vw
bool Triangle::next_ctrclockwise(Edge first, Edge next) const{
    return (first.name == Triangle::Edge::Name::uv &&
                next.name == Triangle::Edge::Name::vw)
            ||(first.name == Triangle::Edge::Name::vw &&
                next.name == Triangle::Edge::Name::wu)
            ||(first.name == Triangle::Edge::Name::wu &&
                next.name == Triangle::Edge::Name::uv);
}

//e.g. first.name = vw, next.name = uv
bool Triangle::next_clockwise(Edge first, Edge next) const{
    return next_ctrclockwise(next,first);
}

Triangle &Triangle::operator=(const Triangle &other) {
    this->vertex_buffer_ = other.vertex_buffer_;
    this->u_ = other.u_;
    this->v_ = other.v_;
    this->w_ = other.w_;
    return *this;
}

bool Triangle::operator==(const Triangle &other) const {
    return u_ == other.u_ && v_ == other.v_ && w_ == other.w_;
}

bool Triangle::operator!=(const Triangle &other) const {
    return !(*this == other);
}

bool Triangle::operator<(const Triangle &other) const {
    if (u_ < other.u_) {
        return true;
    } else if (u_ > other.u_) {
        return false;
    }

    if (v_ < other.v_) {
        return true;
    } else if (v_ > other.v_) {
        return false;
    }

    if (w_ < other.w_) {
        return true;
    } else if (w_ > other.w_) {
        return false;
    }

    return false;
}

void Triangle::rotate_indices() {
    unsigned int a = u_;
    u_ = v_;
    v_ = w_;
    w_ = a;
}

Triangle::Edge::Name rotate_edge_name(Triangle::Edge::Name edge) {
    switch (edge) {
        case Triangle::Edge::Name::uv:
            return Triangle::Edge::Name::vw;
        case Triangle::Edge::Name::vw:
            return Triangle::Edge::Name::wu;
        case Triangle::Edge::Name::wu:
            return Triangle::Edge::Name::uv;
        default:
            qWarning() << "Default case should be unreachable";
            return Triangle::Edge::Name::uv;
    }
}

void ibToTriangles(QVector<Vertex> *vb, QVector<unsigned int> &ib, QVector<Triangle> &triangles) {
    for (int i = 0; i < ib.length(); i+=3) {
        triangles.push_back(Triangle(vb, ib[i], ib[i+1], ib[i+2]));
    }
}

void trianglesToIB(const QVector<Triangle> &triangles, QVector<unsigned int> &ib) {
    ib.reserve(ib.length() + triangles.length() * 3);
    QVectorIterator<Triangle> it(triangles);
    while (it.hasNext()) {
        Triangle triangle = it.next();
        ib.push_back(triangle.u_idx());
        ib.push_back(triangle.v_idx());
        ib.push_back(triangle.w_idx());
    }
}

Triangle::Neighbor Triangle::Neighbors::get_neighbor(Triangle::Edge::Name name) const {
    switch (name) {
        case Triangle::Edge::Name::uv:
            return this->uv;
        case Triangle::Edge::Name::vw:
            return this->vw;
        case Triangle::Edge::Name::wu:
            return this->wu;
        default:
            assert(false);
    }
}

void insert_neighbor(QMap<Triangle, Triangle::Neighbors> &neighbors, const Triangle &triangle_a, const Triangle::Edge &edge_a, Triangle *triangle_b, const Triangle::Edge &edge_b) {
    if (!neighbors.contains(triangle_a)) {
        neighbors.insert(triangle_a, Triangle::Neighbors());
    }
    Triangle::Neighbors &ns = neighbors[triangle_a];
    Triangle::Neighbor neighbor = { triangle_b, edge_b };
    switch (edge_a.name) {
    case Triangle::Edge::Name::uv:
        ns.uv = neighbor;
        break;
    case Triangle::Edge::Name::vw:
        ns.vw = neighbor;
        break;
    case Triangle::Edge::Name::wu:
        ns.wu = neighbor;
        break;
    }
    neighbors.insert(triangle_a, ns);
}

QDebug operator<<(QDebug d, const Triangle &triangle) {
    QDebugStateSaver saver(d);
    d.nospace() << "Triangle(" << triangle.u_idx() << ", " << triangle.v_idx() << ", " << triangle.w_idx() << "; " << triangle.u().pos << ", " << triangle.v().pos << ", " << triangle.w().pos << ")";
    return d;
}
