#pragma once


namespace ofbx
{


typedef unsigned char u8;
typedef unsigned short u16;
typedef unsigned int u32;
#if defined(_WIN32) || defined(__ANDROID__)
	typedef long long i64;
	typedef unsigned long long u64;
#else
	typedef long i64;
	typedef unsigned long u64;
#endif

static_assert(sizeof(u8) == 1, "u8 is not 1 byte");
static_assert(sizeof(u32) == 4, "u32 is not 4 bytes");
static_assert(sizeof(u64) == 8, "u64 is not 8 bytes");
static_assert(sizeof(i64) == 8, "i64 is not 8 bytes");

typedef decltype(sizeof(0)) usize;

using JobFunction = void (*)(void*);
using JobProcessor = void (*)(JobFunction, void*, void*, u32, u32);

// Ignoring certain nodes will only stop them from being processed not tokenised (i.e. they will still be in the tree)
enum class LoadFlags : u16
{
	NONE = 0,
	UNUSED = 1 << 0, // can be reused
	IGNORE_GEOMETRY = 1 << 1,
	IGNORE_BLEND_SHAPES = 1 << 2,
	IGNORE_CAMERAS = 1 << 3,
	IGNORE_LIGHTS = 1 << 4,
	IGNORE_TEXTURES = 1 << 5,
	IGNORE_SKIN = 1 << 6,
	IGNORE_BONES = 1 << 7,
	IGNORE_PIVOTS = 1 << 8,
	IGNORE_ANIMATIONS = 1 << 9,
	IGNORE_MATERIALS = 1 << 10,
	IGNORE_POSES = 1 << 11,
	IGNORE_VIDEOS = 1 << 12,
	IGNORE_LIMBS = 1 << 13,
	IGNORE_MESHES = 1 << 14,
	IGNORE_MODELS = 1 << 15,
};

constexpr LoadFlags operator|(LoadFlags lhs, LoadFlags rhs)
{
	return static_cast<LoadFlags>(static_cast<u16>(lhs) | static_cast<u16>(rhs));
}

inline LoadFlags& operator|=(LoadFlags& lhs, LoadFlags rhs)
{
	return lhs = lhs | rhs;
}

struct DVec2 { double x, y; };
struct DVec3 { double x, y, z; };
struct DVec4 { double x, y, z, w; };
struct DMatrix { double m[16]; /* last 4 are translation */ };
struct DQuat{ double x, y, z, w; };

struct FVec2 { float x, y; };
struct FVec3 { float x, y, z; };
struct FVec4 { float x, y, z, w; };
struct FMatrix { float m[16]; };
struct FQuat{ float x, y, z, w; };

#ifndef OFBX_DOUBLE_PRECISION
	// use floats for vertices, normals, uvs, ...
	using Vec2 = FVec2;
	using Vec3 = FVec3;
	using Vec4 = FVec4;
	using Matrix = FMatrix;
	using Quat = FQuat;
#else
	// use doubles for vertices, normals, uvs, ...
	using Vec2 = DVec2;
	using Vec3 = DVec3;
	using Vec4 = DVec4;
	using Matrix = DMatrix;
	using Quat = DQuat;
#endif

struct Color
{
	float r, g, b;
};


struct DataView
{
	const u8* begin = nullptr;
	const u8* end = nullptr;
	bool is_binary = true;

	bool operator!=(const char* rhs) const { return !(*this == rhs); }
	bool operator==(const char* rhs) const;

	u64 toU64() const;
	i64 toI64() const;
	int toInt() const;
	u32 toU32() const;
	bool toBool() const;
	double toDouble() const;
	float toFloat() const;

	template <int N>
	void toString(char(&out)[N]) const
	{
		char* cout = out;
		const u8* cin = begin;
		while (cin != end && cout - out < N - 1)
		{
			*cout = (char)*cin;
			++cin;
			++cout;
		}
		*cout = '\0';
	}
};


struct IElementProperty
{
	enum Type : unsigned char
	{
		LONG = 'L',
		INTEGER = 'I',
		STRING = 'S',
		FLOAT = 'F',
		DOUBLE = 'D',
		ARRAY_DOUBLE = 'd',
		ARRAY_INT = 'i',
		ARRAY_LONG = 'l',
		ARRAY_FLOAT = 'f',
		BINARY = 'R',
		NONE = ' '
	};
	virtual ~IElementProperty() {}
	virtual Type getType() const = 0;
	virtual IElementProperty* getNext() const = 0;
	virtual DataView getValue() const = 0;
	virtual int getCount() const = 0;
	virtual bool getValues(double* values, int max_size) const = 0;
	virtual bool getValues(int* values, int max_size) const = 0;
	virtual bool getValues(float* values, int max_size) const = 0;
	virtual bool getValues(u64* values, int max_size) const = 0;
	virtual bool getValues(i64* values, int max_size) const = 0;
};


struct IElement
{
    virtual ~IElement() = default;
	virtual IElement* getFirstChild() const = 0;
	virtual IElement* getSibling() const = 0;
	virtual DataView getID() const = 0;
	virtual IElementProperty* getFirstProperty() const = 0;
};


enum class RotationOrder
{
	EULER_XYZ,
	EULER_XZY,
	EULER_YZX,
	EULER_YXZ,
	EULER_ZXY,
	EULER_ZYX,
	SPHERIC_XYZ // Currently unsupported. Treated as EULER_XYZ.
};


struct AnimationCurveNode;
struct AnimationLayer;
struct Scene;
struct IScene;


struct Object
{
	enum class Type
	{
		ROOT,
		GEOMETRY,
		SHAPE,
		MATERIAL,
		MESH,
		TEXTURE,
		LIMB_NODE,
		NULL_NODE,
		CAMERA,
		LIGHT,
		NODE_ATTRIBUTE,
		CLUSTER,
		SKIN,
		BLEND_SHAPE,
		BLEND_SHAPE_CHANNEL,
		ANIMATION_STACK,
		ANIMATION_LAYER,
		ANIMATION_CURVE,
		ANIMATION_CURVE_NODE,
		POSE
	};

	Object(const Scene& _scene, const IElement& _element);

	virtual ~Object() {}
	virtual Type getType() const = 0;

	const IScene& getScene() const;
	Object* resolveObjectLink(int idx) const;
	Object* resolveObjectLink(Type type, const char* property, int idx) const;
	Object* resolveObjectLinkReverse(Type type) const;
	Object* getParent() const { return parent; }

	RotationOrder getRotationOrder() const;
	DVec3 getRotationOffset() const;
	DVec3 getRotationPivot() const;
	DVec3 getPostRotation() const;
	DVec3 getScalingOffset() const;
	DVec3 getScalingPivot() const;
	DVec3 getPreRotation() const;
	DVec3 getLocalTranslation() const;
	DVec3 getLocalRotation() const;
	DVec3 getLocalScaling() const;
	DMatrix getGlobalTransform() const;
	DMatrix getLocalTransform() const;
	DMatrix evalLocal(const DVec3& translation, const DVec3& rotation) const;
	DMatrix evalLocal(const DVec3& translation, const DVec3& rotation, const DVec3& scaling) const;
	bool isNode() const { return is_node; }


	template <typename T> T* resolveObjectLink(int idx) const
	{
		return static_cast<T*>(resolveObjectLink(T::s_type, nullptr, idx));
	}

	u64 id;
	u32 depth = 0xffFFffFF;
	Object* parent = nullptr;
	char name[128];
	const IElement& element;
	const Object* node_attribute;

protected:
	friend struct Scene;
	bool is_node;
	const Scene& scene;
};


struct Pose : Object {
	static const Type s_type = Type::POSE;
	Pose(const Scene& _scene, const IElement& _element);

	virtual DMatrix getMatrix() const = 0;
	virtual const Object* getNode() const = 0;
};


struct Texture : Object
{
	enum TextureType
	{
		DIFFUSE,
		NORMAL,
		SPECULAR,
        SHININESS,
        AMBIENT,
        EMISSIVE,
        REFLECTION,
		COUNT
	};

	static const Type s_type = Type::TEXTURE;

	Texture(const Scene& _scene, const IElement& _element);
	virtual DataView getFileName() const = 0;
	virtual DataView getRelativeFileName() const = 0;
	virtual DataView getEmbeddedData() const = 0;
};

struct Light : Object
{
public:
	enum class LightType
	{
		POINT,
		DIRECTIONAL,
		SPOT,
		AREA,
		VOLUME,
		COUNT
	};

	enum class DecayType
	{
		NO_DECAY,
		LINEAR,
		QUADRATIC,
		CUBIC,
		COUNT
	};

	Light(const Scene& _scene, const IElement& _element)
		: Object(_scene, _element)
	{
		// Initialize the light properties here
	}

	// Light type
	virtual LightType getLightType() const = 0;

	// Light properties
	virtual bool doesCastLight() const = 0;
	virtual bool doesDrawVolumetricLight() const = 0;
	virtual bool doesDrawGroundProjection() const = 0;
	virtual bool doesDrawFrontFacingVolumetricLight() const = 0;
	virtual Color getColor() const = 0;
	virtual double getIntensity() const = 0;
	virtual double getInnerAngle() const = 0;
	virtual double getOuterAngle() const = 0;
	virtual double getFog() const = 0;
	virtual DecayType getDecayType() const = 0;
	virtual double getDecayStart() const = 0;

	// Near attenuation
	virtual bool doesEnableNearAttenuation() const = 0;
	virtual double getNearAttenuationStart() const = 0;
	virtual double getNearAttenuationEnd() const = 0;

	// Far attenuation
	virtual bool doesEnableFarAttenuation() const = 0;
	virtual double getFarAttenuationStart() const = 0;
	virtual double getFarAttenuationEnd() const = 0;

	// Shadows
	virtual const Texture* getShadowTexture() const = 0;
	virtual bool doesCastShadows() const = 0;
	virtual Color getShadowColor() const = 0;
};

struct Camera : Object
{
	enum class ProjectionType
	{
		PERSPECTIVE,
		ORTHOGRAPHIC,
		COUNT
	};

	enum class ApertureMode // Used to determine how to calculate the FOV
	{
		HORIZANDVERT,
		HORIZONTAL,
		VERTICAL,
		FOCALLENGTH,
		COUNT
	};

	enum class GateFit
	{
		NONE,
		VERTICAL,
		HORIZONTAL,
		FILL,
		OVERSCAN,
		STRETCH,
		COUNT
	};

	static const Type s_type = Type::CAMERA;

	Camera(const Scene& _scene, const IElement& _element)
		: Object(_scene, _element)
	{
	}

	virtual Type getType() const { return Type::CAMERA; }
	virtual ProjectionType getProjectionType() const = 0;
	virtual ApertureMode getApertureMode() const = 0;

	virtual double getFilmHeight() const = 0;
	virtual double getFilmWidth() const = 0;

	virtual double getAspectHeight() const = 0;
	virtual double getAspectWidth() const = 0;

	virtual double getNearPlane() const = 0;
	virtual double getFarPlane() const = 0;
	virtual bool doesAutoComputeClipPanes() const = 0;

	virtual GateFit getGateFit() const = 0;
	virtual double getFilmAspectRatio() const = 0;
	virtual double getFocalLength() const = 0;
	virtual double getFocusDistance() const = 0;

	virtual DVec3 getBackgroundColor() const = 0;
	virtual DVec3 getInterestPosition() const = 0;
};

struct Material : Object
{
	static const Type s_type = Type::MATERIAL;

	Material(const Scene& _scene, const IElement& _element);

	virtual Color getDiffuseColor() const = 0;
	virtual Color getSpecularColor() const = 0;
    virtual Color getReflectionColor() const = 0;
    virtual Color getAmbientColor() const = 0;
    virtual Color getEmissiveColor() const = 0;

    virtual double getDiffuseFactor() const = 0;
    virtual double getSpecularFactor() const = 0;
    virtual double getReflectionFactor() const = 0;
    virtual double getShininess() const = 0;
    virtual double getShininessExponent() const = 0;
    virtual double getAmbientFactor() const = 0;
    virtual double getBumpFactor() const = 0;
    virtual double getEmissiveFactor() const = 0;

	virtual const Texture* getTexture(Texture::TextureType type) const = 0;
};


struct Cluster : Object
{
	static const Type s_type = Type::CLUSTER;

	Cluster(const Scene& _scene, const IElement& _element);

	virtual const int* getIndices() const = 0;
	virtual int getIndicesCount() const = 0;
	virtual const double* getWeights() const = 0;
	virtual int getWeightsCount() const = 0;
	virtual DMatrix getTransformMatrix() const = 0;
	virtual DMatrix getTransformLinkMatrix() const = 0;
	virtual const Object* getLink() const = 0;
};


struct Skin : Object
{
	static const Type s_type = Type::SKIN;

	Skin(const Scene& _scene, const IElement& _element);

	virtual int getClusterCount() const = 0;
	virtual const Cluster* getCluster(int idx) const = 0;
};


struct BlendShapeChannel : Object
{
	static const Type s_type = Type::BLEND_SHAPE_CHANNEL;

	BlendShapeChannel(const Scene& _scene, const IElement& _element);

	virtual double getDeformPercent() const = 0;
	virtual int getShapeCount() const = 0;
	virtual const struct Shape* getShape(int idx) const = 0;
};


struct BlendShape : Object
{
	static const Type s_type = Type::BLEND_SHAPE;

	BlendShape(const Scene& _scene, const IElement& _element);

	virtual int getBlendShapeChannelCount() const = 0;
	virtual const BlendShapeChannel* getBlendShapeChannel(int idx) const = 0;
};


struct NodeAttribute : Object
{
	static const Type s_type = Type::NODE_ATTRIBUTE;

	NodeAttribute(const Scene& _scene, const IElement& _element);

	virtual DataView getAttributeType() const = 0;
};


struct Vec2Attributes {
	const Vec2* values = nullptr;
	const int* indices = nullptr;
	int count = 0;

	Vec2Attributes() {}
	Vec2Attributes(const Vec2* v, const int* i, int c) : values(v), indices(i), count(c) {}

	Vec2 get(int i) const { return indices ? values[indices[i]] : values[i]; }
};

struct Vec3Attributes {
	const Vec3* values = nullptr;
	const int* indices = nullptr;
	int count = 0;
	int values_count = 0;

	Vec3Attributes() {}
	Vec3Attributes(const Vec3* v, const int* i, int c, int vc) : values(v), indices(i), count(c), values_count(vc) {}

	Vec3 get(int i) const { return indices ? values[indices[i]] : values[i]; }
};

struct Vec4Attributes {
	const Vec4* values = nullptr;
	const int* indices = nullptr;
	int count = 0;

	Vec4Attributes() {}
	Vec4Attributes(const Vec4* v, const int* i, int c) : values(v), indices(i), count(c) {}

	Vec4 get(int i) const { return indices ? values[indices[i]] : values[i]; }
};

// subset of polygons with same material
struct GeometryPartition {
	struct Polygon {
		const int from_vertex; // index into VecNAttributes::indices
		const int vertex_count;
	};
	const Polygon* polygons;
	const int polygon_count;
	const int max_polygon_triangles; // max triangles in single polygon, can be used for preallocation
	const int triangles_count; // number of triangles after polygon triangulation, can be used for preallocation
};

struct GeometryData {
	virtual ~GeometryData() {}
	
	virtual Vec3Attributes getPositions() const = 0;
	virtual Vec3Attributes getNormals() const = 0;
	virtual Vec2Attributes getUVs(int index = 0) const = 0;
	virtual Vec4Attributes getColors() const = 0;
	virtual Vec3Attributes getTangents() const = 0;
	virtual int getPartitionCount() const = 0;
	virtual GeometryPartition getPartition(int partition_index) const = 0;
};


struct Geometry : Object {
	static const Type s_type = Type::GEOMETRY;
	static const int s_uvs_max = 4;

	Geometry(const Scene& _scene, const IElement& _element);

	virtual const GeometryData& getGeometryData() const = 0;
	virtual const Skin* getSkin() const = 0;
	virtual const BlendShape* getBlendShape() const = 0;
};


struct Shape : Object
{
	static const Type s_type = Type::SHAPE;

	Shape(const Scene& _scene, const IElement& _element);

	virtual const Vec3* getVertices() const = 0;
	virtual int getVertexCount() const = 0;

	virtual const int* getIndices() const = 0;
	virtual int getIndexCount() const = 0;

	virtual const Vec3* getNormals() const = 0;
};


struct Mesh : Object {
	static const Type s_type = Type::MESH;

	Mesh(const Scene& _scene, const IElement& _element);

	virtual const Pose* getPose() const = 0;
	virtual const Geometry* getGeometry() const = 0;
	virtual DMatrix getGeometricMatrix() const = 0;
	virtual const Material* getMaterial(int idx) const = 0;
	virtual int getMaterialCount() const = 0;
	// this will use data from `Geometry` if available and from `Mesh` otherwise
	virtual const GeometryData& getGeometryData() const = 0;
	virtual const Skin* getSkin() const = 0;
	virtual const BlendShape* getBlendShape() const = 0;
};


struct AnimationStack : Object
{
	static const Type s_type = Type::ANIMATION_STACK;

	AnimationStack(const Scene& _scene, const IElement& _element);
	virtual const AnimationLayer* getLayer(int index) const = 0;
};


struct AnimationLayer : Object
{
	static const Type s_type = Type::ANIMATION_LAYER;

	AnimationLayer(const Scene& _scene, const IElement& _element);

	virtual const AnimationCurveNode* getCurveNode(int index) const = 0;
	virtual const AnimationCurveNode* getCurveNode(const Object& bone, const char* property) const = 0;
};


struct AnimationCurve : Object
{
	static const Type s_type = Type::ANIMATION_CURVE;

	AnimationCurve(const Scene& _scene, const IElement& _element);

	virtual int getKeyCount() const = 0;
	virtual const i64* getKeyTime() const = 0;
	virtual const float* getKeyValue() const = 0;
};


struct AnimationCurveNode : Object
{
	static const Type s_type = Type::ANIMATION_CURVE_NODE;

	AnimationCurveNode(const Scene& _scene, const IElement& _element);

	virtual DataView getBoneLinkProperty() const = 0;
	virtual const AnimationCurve* getCurve(int idx) const = 0; 
	virtual DVec3 getNodeLocalTransform(double time) const = 0;
	virtual const Object* getBone() const = 0;
};


struct TakeInfo
{
	DataView name;
	DataView filename;
	double local_time_from;
	double local_time_to;
	double reference_time_from;
	double reference_time_to;
};


// Specifies which canonical axis represents up in the system (typically Y or Z).
enum UpVector
{
	UpVector_AxisX = 1,
	UpVector_AxisY = 2,
	UpVector_AxisZ = 3
};


// Vector with origin at the screen pointing toward the camera.
enum FrontVector
{
	FrontVector_ParityEven = 1,
	FrontVector_ParityOdd = 2
};


// Specifies the third vector of the system.
enum CoordSystem
{
	CoordSystem_RightHanded = 0,
	CoordSystem_LeftHanded = 1
};


// http://docs.autodesk.com/FBX/2014/ENU/FBX-SDK-Documentation/index.html?url=cpp_ref/class_fbx_time.html,topicNumber=cpp_ref_class_fbx_time_html29087af6-8c2c-4e9d-aede-7dc5a1c2436c,hash=a837590fd5310ff5df56ffcf7c394787e
enum FrameRate
{
	FrameRate_DEFAULT = 0,
	FrameRate_120 = 1,
	FrameRate_100 = 2,
	FrameRate_60 = 3,
	FrameRate_50 = 4,
	FrameRate_48 = 5,
	FrameRate_30 = 6,
	FrameRate_30_DROP = 7,
	FrameRate_NTSC_DROP_FRAME = 8,
	FrameRate_NTSC_FULL_FRAME = 9,
	FrameRate_PAL = 10,
	FrameRate_CINEMA = 11,
	FrameRate_1000 = 12,
	FrameRate_CINEMA_ND = 13,
	FrameRate_CUSTOM = 14,
};


struct GlobalSettings
{
	UpVector UpAxis = UpVector_AxisY;
	int UpAxisSign = 1;
	// this seems to be 1-2 in Autodesk (odd/even parity), and 0-2 in Blender (axis as in UpAxis)
	// I recommend to ignore FrontAxis and use just UpVector
	int FrontAxis = 1; 
	int FrontAxisSign = 1;
	CoordSystem CoordAxis = CoordSystem_RightHanded;
	int CoordAxisSign = 1;
	int OriginalUpAxis = 0;
	int OriginalUpAxisSign = 1;
	float UnitScaleFactor = 1;
	float OriginalUnitScaleFactor = 1;
	double TimeSpanStart = 0L;
	double TimeSpanStop = 0L;
	FrameRate TimeMode = FrameRate_DEFAULT;
	float CustomFrameRate = -1.0f;
};


struct GlobalInfo
{
	char AppVendor[128];
	char AppName[128];
	char AppVersion[128];
};


struct IScene
{
	virtual void destroy() = 0;

	// Root Node
	virtual const IElement* getRootElement() const = 0;
	virtual const Object* getRoot() const = 0;

	// Meshes
	virtual int getMeshCount() const = 0;
	virtual const Mesh* getMesh(int index) const = 0;

	// Geometry
	virtual int getGeometryCount() const = 0;
	virtual const Geometry* getGeometry(int index) const = 0;

	// Animations
	virtual int getAnimationStackCount() const = 0;
	virtual const AnimationStack* getAnimationStack(int index) const = 0;

	// Cameras
	virtual int getCameraCount() const = 0;
	virtual const Camera* getCamera(int index) const = 0;

	// Lights
	virtual int getLightCount() const = 0;
	virtual const Light* getLight(int index) const = 0;

	// Scene Objects (Everything in scene)
	virtual const Object* const* getAllObjects() const = 0;
	virtual int getAllObjectCount() const = 0;

	// Embedded files/Data
	virtual int getEmbeddedDataCount() const = 0;
	virtual DataView getEmbeddedData(int index) const = 0;
	virtual DataView getEmbeddedFilename(int index) const = 0;
	virtual bool isEmbeddedBase64(int index) const = 0;
	// data are encoded in returned property and all ->next properties
	virtual const IElementProperty* getEmbeddedBase64Data(int index) const = 0;

	// Scene Misc
	virtual const TakeInfo* getTakeInfo(const char* name) const = 0;
	virtual float getSceneFrameRate() const = 0;
	virtual const GlobalSettings* getGlobalSettings() const = 0;
	virtual const GlobalInfo* getGlobalInfo() const = 0;

	virtual ~IScene() {}
};


IScene* load(const u8* data, usize size, u16 flags, JobProcessor job_processor = nullptr, void* job_user_ptr = nullptr);
const char* getError();
double fbxTimeToSeconds(i64 value);
i64 secondsToFbxTime(double value);

// TODO nonconvex
inline u32 triangulate(const GeometryData& geom, const GeometryPartition::Polygon& polygon, int* tri_indices) {
	if (polygon.vertex_count < 3) return 0;
	if (polygon.vertex_count == 3) {
		tri_indices[0] = polygon.from_vertex;
		tri_indices[1] = polygon.from_vertex + 1;
		tri_indices[2] = polygon.from_vertex + 2;
		return 3;
	}
	else if (polygon.vertex_count == 4) {
		tri_indices[0] = polygon.from_vertex + 0;
		tri_indices[1] = polygon.from_vertex + 1;
		tri_indices[2] = polygon.from_vertex + 2;

		tri_indices[3] = polygon.from_vertex + 0;
		tri_indices[4] = polygon.from_vertex + 2;
		tri_indices[5] = polygon.from_vertex + 3;
		return 6;
	}
	
	for (int tri = 0; tri < polygon.vertex_count - 2; ++tri) {
		tri_indices[tri * 3 + 0] = polygon.from_vertex;
		tri_indices[tri * 3 + 1] = polygon.from_vertex + 1 + tri;
		tri_indices[tri * 3 + 2] = polygon.from_vertex + 2 + tri;
	}
	return 3 * (polygon.vertex_count - 2);
}


} // namespace ofbx

#ifdef OFBX_DEFAULT_DELETER
#include <memory>

template <> struct ::std::default_delete<ofbx::IScene>
{
	default_delete() = default;
	template <class U> constexpr default_delete(default_delete<U>) noexcept {}
	void operator()(ofbx::IScene* scene) const noexcept
	{
		if (scene)
		{
			scene->destroy();
		}
	}
};
#endif
