#include "pw/core/vector.hpp"
#include "pw/core/quaternion.hpp"
#include "pw/core/axisangle.hpp"
#include "pw/core/debug.hpp"
#include "pw/core/size.hpp"
#include "pw/core/point.hpp"
#include "pw/core/time.hpp"
#include "pw/core/geometry.hpp"
#include "pw/core/image.hpp"
#include "pw/core/matrix_transform.hpp"
#include "pw/core/rectangle.hpp"
#include "pw/core/color.hpp"

#include "runtime_lua.hpp"


// seems CRTP magic doesnt work with SOL
namespace sol {
template <> struct is_automagical<pw::matrix4x4> : std::false_type {};
template <> struct is_automagical<pw::vector3> : std::false_type {};
template <> struct is_automagical<pw::vector2> : std::false_type {};
template <> struct is_automagical<pw::vector4> : std::false_type {};
template <> struct is_automagical<pw::quaternion> : std::false_type {};
template <> struct is_automagical<pw::rectangle> : std::false_type {};
}

namespace pw {

vector3 table_to_vector3(sol::table t)
{
    return vector3(t[0],t[1],t[2]);
}

template <typename elementType>
std::vector<elementType> convert_sequence(sol::state& lua,sol::table t)
{
    const std::size_t sz = t.size();
    std::vector<elementType> res(sz);
    for (std::size_t i = 1; i <= sz; i++) {
        res[i - 1] = t[i];
    }
    return res;
}



void register_core_function(sol::state& lua,sol::table& ns)
{

    ns.set("pi",pw::pi<real_t>());



    ns.new_usertype<color>("color",
                           sol::call_constructor,sol::constructors<color(),color(real_t,real_t,real_t,real_t)>(),
                           "rgba",&color::rgba,
                           "data",sol::property([](color& c) { return std::ref(c.rgba.data);} ),
                           "table",sol::property([](const color& c){ return sol::as_table(std::array<vector4::value_type,4>{c.rgba.x(),c.rgba.y(),c.rgba.z(),c.rgba.w()}); },
                                                 [](color& c,const sol::table& t) { c = color((real_t)t[0],t[1],t[2],t[3]);})

                            );


    ns.new_usertype<matrix4x4>("matrix4x4",
                               sol::call_constructor,sol::constructors<matrix4x4()>(),
                               "row",&matrix4x4::row,
                               "column",&matrix4x4::column,
                               "inverse", sol::readonly_property(&matrix4x4::inverse),
                               "identity",sol::readonly_property(&matrix4x4::identity),
                               sol::meta_function::multiplication,[](const matrix4x4& a,const vector4& b) { return vector4(a * b); }
    );

    ns.new_usertype<vector2>("vector2",
                             sol::call_constructor,sol::constructors<vector2(),vector2(real_t,real_t)>(),
                             "x", sol::property(sol::resolve<const vector2::value_type&() const>(&vector2::x), [](vector2& v,vector2::value_type val){ v.x() = val;}),
                             "y", sol::property(sol::resolve<const vector2::value_type&() const>(&vector2::y), [](vector2& v,vector2::value_type val){ v.y() = val;}),
                             "dot",&vector2::dot,
                             sol::meta_function::addition,[](const vector2& a,const vector2& b){ return vector2(a + b); },
                             sol::meta_function::subtraction,[](const vector2& a,const vector2& b){ return vector2(a - b); },
                             "data",sol::property([](vector2& s) { return std::ref(s.data);} ),
                             "table",sol::property([](const vector2& s){ return sol::as_table(std::array<vector2::value_type,2>{s.x(),s.y()}); },
                                                   [](vector2& s,const sol::table& t) { s.data[0] = t[1]; s.data[1] = t[2];} )
                             );

    ns.new_usertype<vector3>("vector3"
                            ,sol::call_constructor,sol::constructors<vector3(),vector3(real_t,real_t,real_t)>()
                            ,"x", sol::property(sol::resolve<const vector3::value_type&() const>(&vector3::x), [](vector3& v,vector3::value_type val){ v.x() = val;})
                            ,"y", sol::property(sol::resolve<const vector3::value_type&() const>(&vector3::y), [](vector3& v,vector3::value_type val){ v.y() = val;})
                            ,"z", sol::property(sol::resolve<const vector3::value_type&() const>(&vector3::z), [](vector3& v,vector3::value_type val){ v.z() = val;})
                            ,"cross",&vector3::cross
                            ,"normalize",&vector3::normalize
                            ,"norm",sol::readonly_property(&vector3::norm)
                            ,"squared_norm",sol::readonly_property(&vector3::squared_norm)
                            ,"dot",&vector3::dot
                            ,"transposed",sol::readonly_property(&vector3::transposed)
                            ,"normalized",sol::readonly_property(&vector3::normalized)
                            ,"lerp",&vector3::lerp
                            ,"forward",sol::readonly_property(&vector3::forward)
                            ,"backward",sol::readonly_property(&vector3::backward)
                            ,"left",sol::readonly_property(&vector3::left)
                            ,"right",sol::readonly_property(&vector3::right)
                            ,"up",sol::readonly_property(&vector3::up)
                            ,"down",sol::readonly_property(&vector3::down)
                            ,sol::meta_function::addition,[](const vector3& a,const vector3& b){ return vector3(a + b); }
                            ,sol::meta_function::subtraction,[](const vector3& a,const vector3& b){ return vector3(a - b); }
                            ,"data",sol::property([](vector3& s) { return std::ref(s.data);} )
                            ,"table",sol::property([](const vector3& s){ return sol::as_table(std::array<vector3::value_type,3>{s.x(),s.y(),s.z()}); },
                                                   [](vector3& s,const sol::table& t) { s.data[0] = t[1]; s.data[1] = t[2]; s.data[2] = t[3];} )
                            );

    ns.new_usertype<vector4>("vector4"
                             ,sol::call_constructor,sol::constructors<vector4(),vector4(vector4::value_type,vector4::value_type,vector4::value_type,vector4::value_type)>()
                             ,"x", sol::property(sol::resolve<const vector4::value_type&() const>(&vector4::x), [](vector4& v,vector4::value_type val){ v.x() = val;})
                             ,"y", sol::property(sol::resolve<const vector4::value_type&() const>(&vector4::y), [](vector4& v,vector4::value_type val){ v.y() = val;})
                             ,"z", sol::property(sol::resolve<const vector4::value_type&() const>(&vector4::z), [](vector4& v,vector4::value_type val){ v.z() = val;})
                             ,"w", sol::property(sol::resolve<const vector4::value_type&() const>(&vector4::w), [](vector4& v,vector4::value_type val){ v.w() = val;})
                             ,"project",sol::readonly_property(&vector4::project)
                             ,sol::meta_function::multiplication,[](const matrix4x4& a,const vector4& b) { return vector4(a * b); }
                             );


    ns.new_usertype<quaternion>("quaternion",
                                sol::call_constructor,sol::constructors<quaternion(), quaternion(quaternion::value_type,quaternion::value_type,quaternion::value_type,quaternion::value_type)>(),
                                "x", sol::property(sol::resolve<const quaternion::value_type&() const>(&quaternion::x), [](quaternion& v,quaternion::value_type val){ v.x() = val;}),
                                "y", sol::property(sol::resolve<const quaternion::value_type&() const>(&quaternion::y), [](quaternion& v,quaternion::value_type val){ v.y() = val;}),
                                "z", sol::property(sol::resolve<const quaternion::value_type&() const>(&quaternion::z), [](quaternion& v,quaternion::value_type val){ v.z() = val;}),
                                "w", sol::property(sol::resolve<const quaternion::value_type&() const>(&quaternion::w), [](quaternion& v,quaternion::value_type val){ v.w() = val;}),
                                "identity",sol::readonly_property(&quaternion::identity),
                                "dot",&quaternion::dot,
                                "inverse",sol::readonly_property(&quaternion::inverse),
                                "normalized",sol::readonly_property([](const quaternion& q){ return quaternion{ q.normalized() };}),
                                "lerp",[](const quaternion& a,const quaternion& b,quaternion::value_type t) { return quaternion{ quaternion::lerp(a,b,t) }; },
                                "slerp",[](const quaternion& a,const quaternion& b,quaternion::value_type t) { return quaternion{ quaternion::slerp(a,b,t) }; },
                                "to_matrix",sol::readonly_property(&quaternion::to_matrix)
    );

    ns.new_usertype<axisangle>("axisangle",
             sol::call_constructor,sol::constructors<axisangle(), axisangle(vector3,axisangle::value_type)>(),
             "axis",sol::property(&axisangle::axis,&axisangle::axis),
             "angle",sol::property(&axisangle::angle,&axisangle::angle),
             "from_matrix",&axisangle::from_matrix,
             "to_matrix",&axisangle::to_matrix
             );


    ns.new_usertype<size>("size"
                          , sol::call_constructor, sol::constructors<size(),size(size::value_type,size::value_type)>()
                          , "width",&size::width
                          , "height",&size::height
                          , "cast_to_float",&size::cast<float>
                          );

    ns.new_usertype<sizef>("sizef"
                          , sol::call_constructor, sol::constructors<sizef(),sizef(sizef::value_type,sizef::value_type)>()
                           , "width",&sizef::width
                           , "height",&sizef::height
                           , "cast_to_int",&sizef::cast<int>
                           );

    ns.new_usertype<point>("point",
                           sol::call_constructor,sol::constructors<point(),point(point::value_type,point::value_type)>(),
                           "x",&point::x,
                           "y",&point::y
                           );


    ns.new_usertype<debug>("debug",
                           "new",sol::no_constructor,
                           "get",&debug::get,
                           "write",&debug::write
                           //						   "none",sol::debug::level::none
                           );


    ns.new_usertype<time>("time",
                          "now",sol::readonly_property(&time::now),
                          "elapsed",sol::readonly_property(&time::elapsed),
                          "reset",&time::reset
                          );

    ns.new_usertype<geometry>("geometry",
                              sol::call_constructor,sol::constructors<geometry(),geometry(geometry::primitive_topology_type,vector3_array,geometry::indices_t)>(),
                              "primitive_topology", sol::property(&geometry::primitive_topology,&geometry::set_primitive_topology),
                              "compute_normals", &geometry::compute_normals,
                              "change_count",sol::property(&geometry::change_count,&geometry::set_change_count),
                              "indices",sol::property( [](const geometry& g){ return sol::as_table(g.indices());},
                                  [](geometry& g,sol::table t){
                                  std::vector<uint32_t> vals;
                                  for (std::size_t i = 0; i < t.size();i++) vals.push_back(t[i+1]);
                                  g.set_indices(vals); }),
                                "vertices",sol::property( [](const geometry& g){ return sol::as_table(g.vertices());},
                                                        [](geometry &g,sol::nested<vector3_array> val) {
                                                            g.set_vertices(val.value());
                                                        }
                                  ),
                                "normals",sol::property( [](const geometry& g){ return sol::as_table(g.vertices());},
                                    [](geometry& g,sol::table t){
                                    vector3_array vals;
                                    for (std::size_t i = 0; i < t.size();i++) vals.push_back(t[i+1]);
                                    g.set_normals(vals); }
                                  ),
                                "texture_coordinates",sol::property(
                                        [](const geometry& g){ return sol::as_table(g.texture_coordinates());},
                                        [](geometry &g,sol::nested<std::vector<vector2_array>> val)
                                        {
                                            g.set_texture_coordinates(val.value());
                                        })
                                );


    ns.new_enum<false>("primitive_topology_type"
                      ,"point_list", geometry::primitive_topology_type::point_list
                      ,"line_list",  geometry::primitive_topology_type::line_list
                      ,"triangle_list", geometry::primitive_topology_type::triangle_list
                       );


    ns.new_usertype<matrix_transform<real_t>>("matrixtransform"
                                              ,"new",sol::no_constructor
                                              ,"look_at",&matrix_transform<real_t>::look_at
                                              ,"perspective_projection",&matrix_transform<real_t>::perspective_projection);
    // SOL3
//    geoom_type["type"] = lua.create_table_with(
//                  "points", geometry::topology_type::points
//                , "lines", geometry::topology_type::lines
//                , "line_strip", geometry::topology_type::line_strip
//                );



    auto image_type = ns.new_usertype<image>("image"
                         ,sol::call_constructor,sol::constructors<image(),image(const size&,image::pixel_layout pl,const image::data_t*)>()
                         ,"create", &image::create
                         ,"release", &image::release
                         ,"is_valid", sol::readonly_property(&image::is_valid)
                         ,"size", sol::readonly_property(&image::size)
                         ,"generate_noise", &image::generate_noise
                         ,"change_count", sol::property(&image::change_count,&image::set_change_count)
                         );

    ns.create_named("pixel_layout"
                    ,"rgb8", image::RGB8
                    ,"rgb32", image::RGBA8
                    ,"gray", image::LUM
                    );

    //    image_type["layout"] = lua.create_table_with(
    //                                  "rgb8", image::RGB8
    //                                 ,"rgb32", image::RGBA8
    //                                 ,"gray", image::LUM);


    ns.new_usertype<rectangle>("rectangle"
            ,sol::call_constructor,sol::constructors<rectangle(),rectangle(const point_<float>&,const size_<float>&)>()
                               );

    // some math function
    ns.create_named("mathf")
            .set_function("ping_pong",ping_pong<real_t>);

}


PW_REGISTER_LUA(core)

}
