#pragma once
// #include <lights/light.h>
#include <sampling.hpp>
#include <vector>
#include <scenegraph/scenegraph.h>

#include "lights/ambient_light.h"
#include "lights/directional_light.h"
#include "lights/point_light.h"
#include "lights/quad_light.h"

namespace embree {
    struct Light;
}

using namespace embree;

struct Scene {
    Scene() {
    }

    ~Scene() {
    }

    void add(embree::Ref<embree::SceneGraph::GroupNode> group) {
        for (auto& node: group->children) {
            if (embree::Ref<embree::SceneGraph::LightNode> lightNode = node.dynamicCast<
                embree::SceneGraph::LightNode>()) {
                lights.push_back(lightNode);
                lightNode->id = unsigned(lights.size()-1);
            } else {
                geometries.push_back(node);
                if (embree::Ref<embree::SceneGraph::TriangleMeshNode> triangleMesh = node.dynamicCast<
                    embree::SceneGraph::TriangleMeshNode>()) {

                    if(triangleMesh->material)
                        materialID(triangleMesh->material);

                    if(triangleMesh->light)
                        lightID(triangleMesh->light);
                }

            }
        }
    }

    unsigned materialID(embree::Ref<embree::SceneGraph::MaterialNode> material) {
        if (!material)
            return unsigned(-1);

        if (material->id == -1) {
            materials.push_back(material);
            material->id = unsigned(materials.size() - 1);
        }
        return material->id;
    }

	unsigned lightID(embree::Ref<embree::SceneGraph::LightNode> light) {
		if (!light)
			return unsigned(-1);

		if (light->id == -1) {
			lights.push_back(light);
			light->id = unsigned(lights.size() - 1);
		}
		return light->id;
	}

    std::vector<embree::Ref<embree::SceneGraph::MaterialNode> > materials; //!< list of materials
    std::vector<embree::Ref<embree::SceneGraph::Node> > geometries; //!< list of geometries
    std::vector<embree::Ref<embree::SceneGraph::LightNode> > lights; //!< list of lights
};

enum Type { TRIANGLE_MESH, SUBDIV_MESH, CURVES, INSTANCE, INSTANCE_ARRAY, GROUP, QUAD_MESH, GRID_MESH /*, POINTS */}; // fast compilation fix!

struct Geometry {
    Geometry(Type type) : type(type), geometry(nullptr), materialID(-1), lightID(-1), visited(false) {
    }

    ~Geometry() { if (geometry) rtcReleaseGeometry(geometry); }
    Type type;
    RTCGeometry geometry;
    unsigned int materialID;
    unsigned int lightID;
    bool visited;
};

struct TriangleMesh {
    // ALIGNED_STRUCT_USM_(16);

    TriangleMesh(RTCDevice device, unsigned int numTriangles, unsigned int numPositions, bool hasNormals,
                 bool hasTexcoords): geom(TRIANGLE_MESH) {
        geom.geometry = rtcNewGeometry(device, RTC_GEOMETRY_TYPE_TRIANGLE);

        positions = std::vector<Vec3fa>(numPositions);

        if (hasNormals) {
            normals = std::vector<Vec3fa>(numPositions);
        }

        if (hasTexcoords)
            texcoords = std::vector<Vec2f>(numPositions);

        triangles = std::vector<SceneGraph::TriangleMeshNode::Triangle>(numTriangles);
    }

    TriangleMesh(RTCDevice device, Scene* scene_in, Ref<SceneGraph::TriangleMeshNode> in): geom(TRIANGLE_MESH) {
        geom.geometry = rtcNewGeometry(device, RTC_GEOMETRY_TYPE_TRIANGLE);

        positions = std::vector<Vec3fa>(0);
        for (auto& pos: in->positions[0])
            positions.push_back(pos);

        if (in->normals.size()) {
            normals = std::vector<Vec3fa>(0);
            for (auto& normal: in->normals[0])
                normals.push_back(normal);
        }

        texcoords = in->texcoords;

        geom.materialID = scene_in->materialID(in->material);
        geom.lightID = scene_in->lightID(in->light);

        triangles = in->triangles;
    }

    ~TriangleMesh() {
        positions.clear();
        normals.clear();
        texcoords.clear();
        triangles.clear();
    }

    void commit() {
        RTCGeometry g = geom.geometry;

        rtcSetSharedGeometryBuffer(g, RTC_BUFFER_TYPE_VERTEX, 0, RTC_FORMAT_FLOAT3, positions.data(), 0,
                                   sizeof(SceneGraph::TriangleMeshNode::Vertex), positions.size());

        rtcSetSharedGeometryBuffer(g, RTC_BUFFER_TYPE_INDEX, 0, RTC_FORMAT_UINT3, triangles.data(), 0,
                                   sizeof(SceneGraph::TriangleMeshNode::Triangle), triangles.size());
        rtcSetGeometryUserData(g, this);

        rtcCommitGeometry(g);
    }

    Geometry geom;
    std::vector<Vec3fa> positions; //!< vertex position array
    std::vector<Vec3fa> normals; //!< vertex normal array
    std::vector<Vec2f> texcoords; //!< vertex texcoord array
    std::vector<SceneGraph::TriangleMeshNode::Triangle> triangles; //!< list of triangles
};


struct RenderScene {
    RenderScene(RTCDevice device, unsigned int numGeometries, unsigned int numMaterials,
                unsigned int numLights): scene(rtcNewScene(device)), dataScene(nullptr) {
        geometries = std::vector<Geometry *>(numGeometries);
        materials = std::vector<Material *>(numMaterials);
        lights = std::vector<Light *>(numLights);
    }

    RenderScene(RTCDevice device, Scene* in): scene(rtcNewScene(device)), dataScene(in) {

		lights = std::vector<Light*>(0);
		for (size_t i = 0; i < in->lights.size(); i++) {
			Light* light = convertLight(in->lights[i]->get(0.0f));
			if (light)
				lights.push_back(light);
		}
        geometries = std::vector<Geometry *>(in->geometries.size());
        for (size_t i = 0; i < in->geometries.size(); i++)
            geometries[i] = convertGeometry(device, in, in->geometries[i]);

        materials = std::vector<Material *>(in->materials.size());
        for (size_t i = 0; i < in->materials.size(); i++) {
            // auto test = in->materials[i].ptr;
            auto mat = (Material*)in->materials[i].ptr;
            auto material = in->materials[i]->material();
            mat->type = material->type;
            // auto align = material->align;
            // mat->align[0] = align[0];
            // mat->align[1] = align[1];
            // mat->align[2] = align[2];
            materials[i] = mat;
        }


        auto test = (OBJMaterial*) materials[0];


    }

    ~RenderScene() {
        geometries.clear();
        materials.clear();
        lights.clear();
    }

    void commit() {
        for (unsigned int geomID = 0; geomID < geometries.size(); geomID++) {
            Geometry* geometry = geometries[geomID];
            rtcAttachGeometryByID(scene, geometry->geometry, geomID);
        }

        rtcCommitScene(scene);
    }

    static Geometry* convertGeometry(RTCDevice device, Scene* scene, Ref<SceneGraph::Node> in) {
        Geometry* geom = nullptr;
        if (in->geometry)
            return (Geometry *) in->geometry;
        else if (Ref<SceneGraph::TriangleMeshNode> mesh = in.dynamicCast<SceneGraph::TriangleMeshNode>())
            geom = (Geometry *) new TriangleMesh(device, scene, mesh);
		
        else
            THROW_RUNTIME_ERROR("unknown geometry type");

        in->geometry = geom;
        return geom;
    }

    static Light* convertLight(Ref<SceneGraph::LightNode> in) {
        Light* l = createLight(in);
        updateLight(in->get(0.0f), l);
        return l;
    }

    static Light* createLight(Ref<SceneGraph::LightNode> in) {
        switch (in->getType()) {
            case SceneGraph::LIGHT_AMBIENT: return (Light *) AmbientLight_create();
            case SceneGraph::LIGHT_DIRECTIONAL: return (Light *) DirectionalLight_create();
            case SceneGraph::LIGHT_DISTANT: return (Light *) DirectionalLight_create();
            case SceneGraph::LIGHT_POINT: return (Light *) PointLight_create();
            case SceneGraph::LIGHT_SPOT: return nullptr;
            case SceneGraph::LIGHT_TRIANGLE: return nullptr;
            case SceneGraph::LIGHT_QUAD: return (Light *) QuadLight_create();
            default: THROW_RUNTIME_ERROR("unknown light type");
        }
        return nullptr;
    }

    static void updateLight(const Ref<SceneGraph::LightNode>& in, Light* out) {
        if (auto light = in.dynamicCast<SceneGraph::LightNodeImpl<SceneGraph::AmbientLight> >())
            AmbientLight_set(out, light->light.L);
        else if (auto light = in.dynamicCast<SceneGraph::LightNodeImpl<SceneGraph::DirectionalLight> >())
            DirectionalLight_set(out, -normalize(light->light.D), light->light.E, 1.0f);
        else if (auto light = in.dynamicCast<SceneGraph::LightNodeImpl<SceneGraph::DistantLight> >())
            DirectionalLight_set(out, -normalize(light->light.D),
                                 light->light.L * rcp(uniformSampleConePDF(light->light.cosHalfAngle)),
                                 light->light.cosHalfAngle);
        else if (auto light = in.dynamicCast<SceneGraph::LightNodeImpl<SceneGraph::PointLight> >())
            PointLight_set(out, light->light.P, light->light.I, 0.f);
        else if (auto light = in.dynamicCast<SceneGraph::LightNodeImpl<SceneGraph::QuadLight> >())
            QuadLight_set(out, light->light.v0,light->light.v3-light->light.v0,light->light.v2-light->light.v0,light->light.L);
    }

    RTCScene scene;
    std::vector<Geometry *> geometries; //!< list of geometries
    std::vector<Material *> materials; //!< material list
    std::vector<Light *> lights; //!< list of lights
    void* dataScene;
};

inline void ConvertTriangleMesh(RTCDevice device, TriangleMesh* mesh, RTCBuildQuality quality, RTCSceneFlags flags,
                                RTCFeatureFlags& used_features) {
    if (mesh->geom.visited) return;

    used_features = RTC_FEATURE_FLAG_TRIANGLE;

    mesh->geom.visited = true;
    rtcSetGeometryBuildQuality(mesh->geom.geometry, quality);
    mesh->commit();
}

extern "C" inline RTCScene ConvertScene(RTCDevice g_device, RenderScene* scene_in, RTCBuildQuality quality,
                                        RTCSceneFlags flags, RTCFeatureFlags* used_features_out) {
    Scene* tutorial_scene = (Scene *) scene_in->dataScene;
    if (!tutorial_scene) return scene_in->scene;

    RTCFeatureFlags used_features = RTC_FEATURE_FLAG_NONE;

    RTCScene scene = scene_in->scene;
    rtcSetSceneFlags(scene, flags);

    for (unsigned int geomID = 0; geomID < scene_in->geometries.size(); geomID++) {
        Geometry* geometry = scene_in->geometries[geomID];
        if (geometry->type == TRIANGLE_MESH)
            ConvertTriangleMesh(g_device, (TriangleMesh *) geometry, quality, flags, used_features);
        else
            assert(false);

        rtcAttachGeometryByID(scene, geometry->geometry, geomID);
    }

    if (used_features_out)
        *used_features_out = used_features;

    return scene;
}
