#include "pw/core/geometry.hpp"
#include "pw/core/debug.hpp"
#include "pw/core/serialize.hpp"

namespace pw {


//struct testattrib
//{
//    enum attrib_name {

//    };

//    std::unordered_multimap<attrib_name,valuevar> _attribs;
//};

void geometry::compute_normals()
{
    // assumption is that we have some array as the vertices
    vector3_array new_normals(_vertices.size());

    if (_primitive_topology == primitive_topology_type::triangle_list)
    {

        // for indexed-faceset
        for (std::size_t i = 0; i < _indices.size();i += 3)
        {
            // translate to actual indices
            auto li = _indices[i  ];
            auto ci = _indices[i+1];
            auto ri = _indices[i+2];

            // calculate edges between vertices
            auto edgeLeft = vector3(  _vertices[li] - _vertices[ci] );
            auto edgeRight = vector3( _vertices[ri] - _vertices[ci] );

            // calculate counter clockwise and normalize
            auto N = vector3(vector3::cross(edgeRight,edgeLeft)).normalized();

            new_normals[li] = new_normals[ci] = new_normals[ri] = N.normalized();
        }
    }


    this->set_normals(new_normals);

}

void geometry::compute_tangent_space()
{
    switch (_primitive_topology) {
    case primitive_topology_type::triangle_list:
        for (size_t i = 0; i < _indices.size();i += 3) {

            const auto& v0 = _vertices[_indices[i + 0]];
            const auto& v1 = _vertices[_indices[i + 1]];
            const auto& v2 = _vertices[_indices[i + 2]];

            auto normal = vector3::cross(v1-v0,v2-v0);
            break;
        }

    default:
        debug::w() << __PRETTY_FUNCTION__ << " not implemented for this topology type";
    }

}

geometry::geometry(primitive_topology_type t
                   ,vector3_array v
                   ,indices_t i)
    : _primitive_topology(t)
    , _vertices(std::move(v))
    , _indices(std::move(i))
{
}

void geometry::set_vertices(vector3_array v)
{
    _change_count++;
    _vertices = v;
}

void geometry::set_indices(indices_t v)
{
    _change_count++;
    _indices = v;
}

const geometry::indices_t &geometry::indices() const { return _indices; }

void geometry::set_normals(vector3_array v)
{
    _change_count++;
    _normals = v;
}

const vector3_array &geometry::normals() const { return _normals; }

void geometry::set_texture_coordinates(std::vector<vector2_array> v)
{
    _change_count++;
    _texture_coords = v;
}


//void geometry::set_vertices(const geometry::vertex3array_t &v)
//{
//    // first set vertices
//    _vertices = v;

//    // update bounds
//    compute_bounds();
//}

//void geometry::set_normals(const geometry::vertex3array_t &v)
//{
//    _normals = v;
//}

//const geometry::vertex3array_t &geometry::normals() const
//{
//    return _normals;
//}

void geometry::transform(const matrix4x4 &m)
{
    // apply transformation to all vertices
    for (auto &v : _vertices)
        v = vector4(m * v.homogenous()).project();

    // apply to normals
    //    for(auto &n : _normals)
    //        n = vector4(m * n.homogenous(0)).xyz();

}

aabb geometry::bounds() const
{
    aabb b;

    // only do if there are vertices
    if (_vertices.size()) {

        // reset
        b.max = b.min = _vertices.front();

        // update
        for (auto const & v : _vertices) {
            b.max.x() = std::max(v.x(),b.max.x());
            b.max.y() = std::max(v.y(),b.max.y());
            b.max.z() = std::max(v.z(),b.max.z());
            b.min.x() = std::min(v.x(),b.min.x());
            b.min.y() = std::min(v.y(),b.min.y());
            b.min.z() = std::min(v.z(),b.min.z());
        }
    }

    return b;
}

}
