#include <obj_loader.hpp>
#include <mesh.hpp>
#include <uv.hpp>

#include <glm/glm.hpp>

#include <string>
#include <iostream>
#include <vector>

namespace gb
{

inline static uv4 read_point(std::istream& stream)
{
    float a, b, c;
    stream >> a >> b >> c;
    return uv4::make(glm::vec4 { a, b, c, 1.f });
}

inline static uv2 read_texture_coord(std::istream& stream)
{
    float a, b;
    stream >> a >> b;
    return uv2::make(glm::vec2 { a, b });
}

inline static uv4 read_vector(std::istream& stream)
{
    float a, b, c;
    stream >> a >> b >> c;
    return uv4::make(glm::vec4 { a, b, c, 0.f });
}

inline static Mesh read_face(std::istream& stream, const std::vector<uv4>& vs, const std::vector<uv2>& vts, const std::vector<uv4>& vns)
{
    int v1, v2, v3, t1, t2, t3, n1, n2, n3;
    stream >> v1;
    stream.ignore(1);
    stream >> t1;
    stream.ignore(1);
    stream >> n1;

    stream >> v2;
    stream.ignore(1);
    stream >> t2;
    stream.ignore(1);
    stream >> n2;

    stream >> v3;
    stream.ignore(1);
    stream >> t3;
    stream.ignore(1);
    stream >> n3;

    // since indices in the .obj file start from 1,
    // we subtract 1 from each components
    return Mesh (
                 vs[v1-1],
                 vs[v2-1],
                 vs[v3-1],
                 vts[t1-1],
                 vts[t2-1],
                 vts[t3-1],
                 vns[n1-1],
                 vns[n2-1],
                 vns[n3-1]
                 );
}

gb::Object obj_loader::load_object(std::istream& stream)
{
    // TODO: be aware of the potential memory leak here
    std::string object_name;
    std::vector<uv4> vertices;
    std::vector<uv2> vertex_texture_coords;
    std::vector<uv4> vertex_normals;
    std::vector<Mesh> meshes;

    std::string tmp;
    while (m_state != state::end)
    {
        switch (m_state)
        {
        case state::ignore_line:
            std::getline(stream, tmp);
            m_state = state::ready;
            continue;
        case state::ignore_once:
            stream.ignore();
            m_state = state::ready;
            continue;
        case state::ready:
            if (!stream.good()) {
                m_state = state::end;
                continue;
            }

            switch (stream.peek()) {
                case '#':
                // TODO: have no idea what the "s" is for
                // ignore for now
                case 's':
                    m_state = state::ignore_line;
                    continue;
                case 'o':
                    stream.ignore();
                    m_state = state::object_name;
                    continue;
                case 'f':
                    stream.ignore();
                    m_state = state::face;
                    continue;
            }

            stream >> tmp;
            if (tmp == "v") {
                m_state = state::vertex;
                continue;
            }
            if (tmp == "vt") {
                m_state = state::vertex_texture;
                continue;
            }
            if (tmp == "vn") {
                m_state = state::vertex_normal;
                continue;
            }
            // load texture
            // TODO: ignore for now
            if (tmp == "mtllib" || tmp == "usemtl") {
                m_state = state::ignore_line;
                continue;
            }
        case state::object_name:
            stream >> object_name;
            m_state = state::ignore_once;
            continue;
        case state::vertex:
            vertices.push_back(read_point(stream));
            m_state = state::ignore_once;
            continue;
        case state::vertex_texture:
            vertex_texture_coords.push_back(read_texture_coord(stream));
            m_state = state::ignore_once;
            continue;
        case state::vertex_normal:
            vertex_normals.push_back(read_vector(stream));
            m_state = state::ignore_once;
            continue;
        case state::face:
            meshes.push_back(read_face(stream, vertices, vertex_texture_coords, vertex_normals));
            m_state = state::ignore_once;
            continue;
        case state::end:
            break;
        }
    }
    m_state = state::ready;

    return gb::Object(std::move(meshes));
}

} // namespace gb
