/*
 * Copyright 2011, Blender Foundation.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

#ifndef __BLENDER_UTIL_H__
#define __BLENDER_UTIL_H__

#include "util_map.h"
#include "util_path.h"
#include "util_set.h"
#include "util_transform.h"
#include "util_types.h"
#include "util_vector.h"

/* Hacks to hook into Blender API */

extern "C" {

struct RenderEngine;
struct RenderResult;

ID *rna_Object_to_mesh(void *_self, void *reports, void *scene, int apply_modifiers, int settings);
void rna_Main_meshes_remove(void *bmain, void *reports, void *mesh);
void rna_Object_create_duplilist(void *ob, void *reports, void *sce);
void rna_Object_free_duplilist(void *ob, void *reports);
void rna_RenderLayer_rect_set(PointerRNA *ptr, const float *values);
void rna_RenderPass_rect_set(PointerRNA *ptr, const float *values);
struct RenderResult *RE_engine_begin_result(struct RenderEngine *engine, int x, int y, int w, int h);
void RE_engine_update_result(struct RenderEngine *engine, struct RenderResult *result);
void RE_engine_end_result(struct RenderEngine *engine, struct RenderResult *result);
int RE_engine_test_break(struct RenderEngine *engine);
void RE_engine_update_stats(struct RenderEngine *engine, const char *stats, const char *info);
void RE_engine_update_progress(struct RenderEngine *engine, float progress);
void engine_tag_redraw(void *engine);
void engine_tag_update(void *engine);
int rna_Object_is_modified(void *ob, void *scene, int settings);
void BLI_timestr(double _time, char *str);

}

CCL_NAMESPACE_BEGIN

static inline BL::Mesh object_to_mesh(BL::Object self, BL::Scene scene, bool apply_modifiers, bool render)
{
	ID *data = rna_Object_to_mesh(self.ptr.data, NULL, scene.ptr.data, apply_modifiers, render);
	PointerRNA ptr;
	RNA_id_pointer_create(data, &ptr);
	return BL::Mesh(ptr);
}

static inline void object_remove_mesh(BL::BlendData data, BL::Mesh mesh)
{
	rna_Main_meshes_remove(data.ptr.data, NULL, mesh.ptr.data);
}

static inline void object_create_duplilist(BL::Object self, BL::Scene scene)
{
	rna_Object_create_duplilist(self.ptr.data, NULL, scene.ptr.data);
}

static inline void object_free_duplilist(BL::Object self)
{
	rna_Object_free_duplilist(self.ptr.data, NULL);
}

static inline bool object_is_modified(BL::Object self, BL::Scene scene, bool preview)
{
	return rna_Object_is_modified(self.ptr.data, scene.ptr.data, (preview)? (1<<0): (1<<1))? true: false;
}

/* Utilities */

static inline Transform get_transform(BL::Array<float, 16> array)
{
	Transform tfm;

	/* we assume both types to be just 16 floats, and transpose because blender
	   use column major matrix order while we use row major */
	memcpy(&tfm, &array, sizeof(float)*16);
	tfm = transform_transpose(tfm);

	return tfm;
}

static inline float2 get_float2(BL::Array<float, 2> array)
{
	return make_float2(array[0], array[1]);
}

static inline float3 get_float3(BL::Array<float, 2> array)
{
	return make_float3(array[0], array[1], 0.0f);
}

static inline float3 get_float3(BL::Array<float, 3> array)
{
	return make_float3(array[0], array[1], array[2]);
}

static inline float3 get_float3(BL::Array<float, 4> array)
{
	return make_float3(array[0], array[1], array[2]);
}

static inline int4 get_int4(BL::Array<int, 4> array)
{
	return make_int4(array[0], array[1], array[2], array[3]);
}

static inline uint get_layer(BL::Array<int, 20> array)
{
	uint layer = 0;

	for(uint i = 0; i < 20; i++)
		if(array[i])
			layer |= (1 << i);
	
	return layer;
}

/*static inline float3 get_float3(PointerRNA& ptr, const char *name)
{
	float3 f;
	RNA_float_get_array(&ptr, name, &f.x);
	return f;
}*/

static inline bool get_boolean(PointerRNA& ptr, const char *name)
{
	return RNA_boolean_get(&ptr, name)? true: false;
}

static inline float get_float(PointerRNA& ptr, const char *name)
{
	return RNA_float_get(&ptr, name);
}

static inline int get_int(PointerRNA& ptr, const char *name)
{
	return RNA_int_get(&ptr, name);
}

static inline int get_enum(PointerRNA& ptr, const char *name)
{
	return RNA_enum_get(&ptr, name);
}

static inline string get_enum_identifier(PointerRNA& ptr, const char *name)
{
	PropertyRNA *prop = RNA_struct_find_property(&ptr, name);
	const char *identifier = "";
	int value = RNA_property_enum_get(&ptr, prop);

	RNA_property_enum_identifier(NULL, &ptr, prop, value, &identifier);

	return string(identifier);
}

/* Relative Paths */

static inline string blender_absolute_path(BL::BlendData b_data, BL::ID b_id, const string& path)
{
	if(path.size() >= 2 && path[0] == '/' && path[1] == '/') {
		string dirname = (b_id.library())? b_id.library().filepath(): b_data.filepath();
		return path_join(path_dirname(dirname), path.substr(2));
	}

	return path;
}

/* ID Map
 *
 * Utility class to keep in sync with blender data.
 * Used for objects, meshes, lights and shaders. */

template<typename K, typename T>
class id_map {
public:
	id_map(vector<T*> *scene_data_)
	{
		scene_data = scene_data_;
	}

	T *find(BL::ID id)
	{
		return find(id.ptr.id.data);
	}

	T *find(const K& key)
	{
		if(b_map.find(key) != b_map.end()) {
			T *data = b_map[key];
			return data;
		}

		return NULL;
	}

	void set_recalc(BL::ID id)
	{
		b_recalc.insert(id.ptr.data);
	}

	bool has_recalc()
	{
		return !(b_recalc.empty());
	}

	void pre_sync()
	{
		used_set.clear();
	}

	bool sync(T **r_data, BL::ID id)
	{
		return sync(r_data, id, id, id.ptr.id.data);
	}

	bool sync(T **r_data, BL::ID id, BL::ID parent, const K& key)
	{
		T *data = find(key);
		bool recalc;

		if(!data) {
			/* add data if it didn't exist yet */
			data = new T();
			scene_data->push_back(data);
			b_map[key] = data;
			recalc = true;
		}
		else {
			recalc = (b_recalc.find(id.ptr.data) != b_recalc.end());
			if(parent.ptr.data)
				recalc = recalc || (b_recalc.find(parent.ptr.data) != b_recalc.end());
		}

		used(data);

		*r_data = data;
		return recalc;
	}

	void used(T *data)
	{
		/* tag data as still in use */
		used_set.insert(data);
	}

	void set_default(T *data)
	{
		b_map[NULL] = data;
	}

	bool post_sync(bool do_delete = true)
	{
		/* remove unused data */
		vector<T*> new_scene_data;
		typename vector<T*>::iterator it;
		bool deleted = false;

		for(it = scene_data->begin(); it != scene_data->end(); it++) {
			T *data = *it;

			if(do_delete && used_set.find(data) == used_set.end()) {
				delete data;
				deleted = true;
			}
			else
				new_scene_data.push_back(data);
		}

		*scene_data = new_scene_data;

		/* update mapping */
		map<K, T*> new_map;
		typedef pair<const K, T*> TMapPair;
		typename map<K, T*>::iterator jt;

		for(jt = b_map.begin(); jt != b_map.end(); jt++) {
			TMapPair& pair = *jt;

			if(used_set.find(pair.second) != used_set.end())
				new_map[pair.first] = pair.second;
		}

		used_set.clear();
		b_recalc.clear();
		b_map = new_map;

		return deleted;
	}

protected:
	vector<T*> *scene_data;
	map<K, T*> b_map;
	set<T*> used_set;
	set<void*> b_recalc;
};

/* Object Key */

struct ObjectKey {
	void *parent;
	int index;
	void *ob;

	ObjectKey(void *parent_, int index_, void *ob_)
	: parent(parent_), index(index_), ob(ob_) {}

	bool operator<(const ObjectKey& k) const
	{ return (parent < k.parent || (parent == k.parent && (index < k.index || (index == k.index && ob < k.ob)))); }
};

CCL_NAMESPACE_END

#endif /* __BLENDER_UTIL_H__ */

