/**
 * $Id$
 *
 * ***** BEGIN GPL LICENSE BLOCK *****
 *
 * 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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV.
 * All rights reserved.
 *
 * Contributor(s): Blender Foundation (2008).
 *
 * ***** END GPL LICENSE BLOCK *****
 */

#include "MEM_guardedalloc.h"

#include "DNA_ID.h"
#include "DNA_listBase.h"
#include "DNA_scene_types.h"
#include "DNA_screen_types.h"
#include "DNA_space_types.h"
#include "DNA_view3d_types.h"
#include "DNA_windowmanager_types.h"
#include "DNA_object_types.h"

#include "RNA_access.h"

#include "BLI_listbase.h"
#include "BLI_string.h"

#include "BKE_context.h"
#include "BKE_main.h"
#include "BKE_screen.h"
#include "BKE_global.h"

#include <string.h>

/* struct */

struct bContext {
	int thread;

	/* windowmanager context */
	struct {
		struct wmWindowManager *manager;
		struct wmWindow *window;
		struct bScreen *screen;
		struct ScrArea *area;
		struct ARegion *region;
		struct ARegion *menu;
		struct bContextStore *store;
	} wm;
	
	/* data context */
	struct {
		struct Main *main;
		struct Scene *scene;

		int recursion;
		int py_init; /* true if python is initialized */
	} data;
	
	/* data evaluation */
	struct {
		int render;
	} eval;
};

/* context */

bContext *CTX_create()
{
	bContext *C;
	
	C= MEM_callocN(sizeof(bContext), "bContext");

	return C;
}

bContext *CTX_copy(const bContext *C)
{
	bContext *newC= MEM_dupallocN((void*)C);

	return newC;
}

void CTX_free(bContext *C)
{
	MEM_freeN(C);
}

/* store */

bContextStore *CTX_store_add(ListBase *contexts, char *name, PointerRNA *ptr)
{
	bContextStoreEntry *entry;
	bContextStore *ctx, *lastctx;

	/* ensure we have a context to put the entry in, if it was already used
	 * we have to copy the context to ensure */
	ctx= contexts->last;

	if(!ctx || ctx->used) {
		if(ctx) {
			lastctx= ctx;
			ctx= MEM_dupallocN(lastctx);
			BLI_duplicatelist(&ctx->entries, &lastctx->entries);
		}
		else
			ctx= MEM_callocN(sizeof(bContextStore), "bContextStore");

		BLI_addtail(contexts, ctx);
	}

	entry= MEM_callocN(sizeof(bContextStoreEntry), "bContextStoreEntry");
	BLI_strncpy(entry->name, name, sizeof(entry->name));
	entry->ptr= *ptr;

	BLI_addtail(&ctx->entries, entry);

	return ctx;
}

void CTX_store_set(bContext *C, bContextStore *store)
{
	C->wm.store= store;
}

bContextStore *CTX_store_copy(bContextStore *store)
{
	bContextStore *ctx;

	ctx= MEM_dupallocN(store);
	BLI_duplicatelist(&ctx->entries, &store->entries);

	return ctx;
}

void CTX_store_free(bContextStore *store)
{
	BLI_freelistN(&store->entries);
	MEM_freeN(store);
}

void CTX_store_free_list(ListBase *contexts)
{
	bContextStore *ctx;

	while((ctx= contexts->first)) {
		BLI_remlink(contexts, ctx);
		CTX_store_free(ctx);
	}
}

/* is python initialied? */
int CTX_py_init_get(bContext *C)
{
	return C->data.py_init;
}
void CTX_py_init_set(bContext *C, int value)
{
	C->data.py_init= value;
}

/* window manager context */

wmWindowManager *CTX_wm_manager(const bContext *C)
{
	return C->wm.manager;
}

wmWindow *CTX_wm_window(const bContext *C)
{
	return C->wm.window;
}

bScreen *CTX_wm_screen(const bContext *C)
{
	return C->wm.screen;
}

ScrArea *CTX_wm_area(const bContext *C)
{
	return C->wm.area;
}

SpaceLink *CTX_wm_space_data(const bContext *C)
{
	return (C->wm.area)? C->wm.area->spacedata.first: NULL;
}

ARegion *CTX_wm_region(const bContext *C)
{
	return C->wm.region;
}

void *CTX_wm_region_data(const bContext *C)
{
	return (C->wm.region)? C->wm.region->regiondata: NULL;
}

struct ARegion *CTX_wm_menu(const bContext *C)
{
	return C->wm.menu;
}

struct ReportList *CTX_wm_reports(const bContext *C)
{
	if (C->wm.manager)
		return &(C->wm.manager->reports);

	return NULL;
}

View3D *CTX_wm_view3d(const bContext *C)
{
	if(C->wm.area && C->wm.area->spacetype==SPACE_VIEW3D)
		return C->wm.area->spacedata.first;
	return NULL;
}

RegionView3D *CTX_wm_region_view3d(const bContext *C)
{
	if(C->wm.area && C->wm.area->spacetype==SPACE_VIEW3D)
		if(C->wm.region)
			return C->wm.region->regiondata;
	return NULL;
}

struct SpaceText *CTX_wm_space_text(const bContext *C)
{
	if(C->wm.area && C->wm.area->spacetype==SPACE_TEXT)
		return C->wm.area->spacedata.first;
	return NULL;
}

struct SpaceConsole *CTX_wm_space_console(const bContext *C)
{
	if(C->wm.area && C->wm.area->spacetype==SPACE_CONSOLE)
		return C->wm.area->spacedata.first;
	return NULL;
}

struct SpaceImage *CTX_wm_space_image(const bContext *C)
{
	if(C->wm.area && C->wm.area->spacetype==SPACE_IMAGE)
		return C->wm.area->spacedata.first;
	return NULL;
}

struct SpaceButs *CTX_wm_space_buts(const bContext *C)
{
	if(C->wm.area && C->wm.area->spacetype==SPACE_BUTS)
		return C->wm.area->spacedata.first;
	return NULL;
}

struct SpaceFile *CTX_wm_space_file(const bContext *C)
{
	if(C->wm.area && C->wm.area->spacetype==SPACE_FILE)
		return C->wm.area->spacedata.first;
	return NULL;
}

struct SpaceSeq *CTX_wm_space_seq(const bContext *C)
{
	if(C->wm.area && C->wm.area->spacetype==SPACE_SEQ)
		return C->wm.area->spacedata.first;
	return NULL;
}

struct SpaceOops *CTX_wm_space_outliner(const bContext *C)
{
	if(C->wm.area && C->wm.area->spacetype==SPACE_OUTLINER)
		return C->wm.area->spacedata.first;
	return NULL;
}

struct SpaceNla *CTX_wm_space_nla(const bContext *C)
{
	if(C->wm.area && C->wm.area->spacetype==SPACE_NLA)
		return C->wm.area->spacedata.first;
	return NULL;
}

struct SpaceTime *CTX_wm_space_time(const bContext *C)
{
	if(C->wm.area && C->wm.area->spacetype==SPACE_TIME)
		return C->wm.area->spacedata.first;
	return NULL;
}

struct SpaceNode *CTX_wm_space_node(const bContext *C)
{
	if(C->wm.area && C->wm.area->spacetype==SPACE_NODE)
		return C->wm.area->spacedata.first;
	return NULL;
}

struct SpaceLogic *CTX_wm_space_logic(const bContext *C)
{
	if(C->wm.area && C->wm.area->spacetype==SPACE_LOGIC)
		return C->wm.area->spacedata.first;
	return NULL;
}

struct SpaceIpo *CTX_wm_space_graph(const bContext *C)
{
	if(C->wm.area && C->wm.area->spacetype==SPACE_IPO)
		return C->wm.area->spacedata.first;
	return NULL;
}

struct SpaceAction *CTX_wm_space_action(const bContext *C)
{
	if(C->wm.area && C->wm.area->spacetype==SPACE_ACTION)
		return C->wm.area->spacedata.first;
	return NULL;
}

struct SpaceInfo *CTX_wm_space_info(const bContext *C)
{
	if(C->wm.area && C->wm.area->spacetype==SPACE_INFO)
		return C->wm.area->spacedata.first;
	return NULL;
}

struct SpaceUserPref *CTX_wm_space_userpref(const bContext *C)
{
	if(C->wm.area && C->wm.area->spacetype==SPACE_USERPREF)
		return C->wm.area->spacedata.first;
	return NULL;
}

void CTX_wm_manager_set(bContext *C, wmWindowManager *wm)
{
	C->wm.manager= wm;
	C->wm.window= NULL;
	C->wm.screen= NULL;
	C->wm.area= NULL;
	C->wm.region= NULL;
}

void CTX_wm_window_set(bContext *C, wmWindow *win)
{
	C->wm.window= win;
	C->wm.screen= (win)? win->screen: NULL;
	C->data.scene= (C->wm.screen)? C->wm.screen->scene: NULL;
	C->wm.area= NULL;
	C->wm.region= NULL;
}

void CTX_wm_screen_set(bContext *C, bScreen *screen)
{
	C->wm.screen= screen;
	C->data.scene= (C->wm.screen)? C->wm.screen->scene: NULL;
	C->wm.area= NULL;
	C->wm.region= NULL;
}

void CTX_wm_area_set(bContext *C, ScrArea *area)
{
	C->wm.area= area;
	C->wm.region= NULL;
}

void CTX_wm_region_set(bContext *C, ARegion *region)
{
	C->wm.region= region;
}

void CTX_wm_menu_set(bContext *C, ARegion *menu)
{
	C->wm.menu= menu;
}

/* data context utility functions */

struct bContextDataResult {
	PointerRNA ptr;
	ListBase list;
	const char **dir;
};

static int ctx_data_get(bContext *C, const char *member, bContextDataResult *result)
{
	int done= 0, recursion= C->data.recursion;

	memset(result, 0, sizeof(bContextDataResult));

	/* we check recursion to ensure that we do not get infinite
	 * loops requesting data from ourselfs in a context callback */
	if(!done && recursion < 1 && C->wm.store) {
		bContextStoreEntry *entry;

		C->data.recursion= 1;

		for(entry=C->wm.store->entries.first; entry; entry=entry->next) {
			if(strcmp(entry->name, member) == 0) {
				result->ptr= entry->ptr;
				done= 1;
			}
		}
	}
	if(!done && recursion < 2 && C->wm.region) {
		C->data.recursion= 2;
		if(C->wm.region->type && C->wm.region->type->context)
			done= C->wm.region->type->context(C, member, result);
	}
	if(!done && recursion < 3 && C->wm.area) {
		C->data.recursion= 3;
		if(C->wm.area->type && C->wm.area->type->context)
			done= C->wm.area->type->context(C, member, result);
	}
	if(!done && recursion < 4 && C->wm.screen) {
		bContextDataCallback cb= C->wm.screen->context;
		C->data.recursion= 4;
		if(cb)
			done= cb(C, member, result);
	}

	C->data.recursion= recursion;

	return done;
}

static void *ctx_data_pointer_get(const bContext *C, const char *member)
{
	bContextDataResult result;

	if(ctx_data_get((bContext*)C, member, &result))
		return result.ptr.data;

	return NULL;
}

static int ctx_data_pointer_verify(const bContext *C, const char *member, void **pointer)
{
	bContextDataResult result;

	if(ctx_data_get((bContext*)C, member, &result)) {
		*pointer= result.ptr.data;
		return 1;
	}
	else {
		*pointer= NULL;
		return 0;
	}
}

static int ctx_data_collection_get(const bContext *C, const char *member, ListBase *list)
{
	bContextDataResult result;

	if(ctx_data_get((bContext*)C, member, &result)) {
		*list= result.list;
		return 1;
	}

	list->first= NULL;
	list->last= NULL;

	return 0;
}

PointerRNA CTX_data_pointer_get(const bContext *C, const char *member)
{
	bContextDataResult result;

	if(ctx_data_get((bContext*)C, member, &result))
		return result.ptr;
	else
		return PointerRNA_NULL;
}

PointerRNA CTX_data_pointer_get_type(const bContext *C, const char *member, StructRNA *type)
{
	PointerRNA ptr = CTX_data_pointer_get(C, member);

	if(ptr.data && RNA_struct_is_a(ptr.type, type))
		return ptr;
	
	return PointerRNA_NULL;
}

ListBase CTX_data_collection_get(const bContext *C, const char *member)
{
	bContextDataResult result;

	if(ctx_data_get((bContext*)C, member, &result)) {
		return result.list;
	}
	else {
		ListBase list;
		memset(&list, 0, sizeof(list));
		return list;
	}
}

void CTX_data_get(const bContext *C, const char *member, PointerRNA *r_ptr, ListBase *r_lb)
{
	bContextDataResult result;

	if(ctx_data_get((bContext*)C, member, &result)) {
		*r_ptr= result.ptr;
		*r_lb= result.list;
	}
	else {
		memset(r_ptr, 0, sizeof(*r_ptr));
		memset(r_lb, 0, sizeof(*r_lb));
	}
}

static void data_dir_add(ListBase *lb, const char *member)
{
	LinkData *link;

	if(strcmp(member, "scene") == 0) /* exception */
		return;

	for(link=lb->first; link; link=link->next)
		if(strcmp(link->data, member) == 0)
			return;
	
	link= MEM_callocN(sizeof(LinkData), "LinkData");
	link->data= (void*)member;
	BLI_addtail(lb, link);
}

ListBase CTX_data_dir_get(const bContext *C)
{
	bContextDataResult result;
	ListBase lb;
	int a;

	memset(&lb, 0, sizeof(lb));

	if(C->wm.store) {
		bContextStoreEntry *entry;

		for(entry=C->wm.store->entries.first; entry; entry=entry->next)
			data_dir_add(&lb, entry->name);
	}
	if(C->wm.region && C->wm.region->type && C->wm.region->type->context) {
		memset(&result, 0, sizeof(result));
		C->wm.region->type->context(C, "", &result);

		if(result.dir)
			for(a=0; result.dir[a]; a++)
				data_dir_add(&lb, result.dir[a]);
	}
	if(C->wm.area && C->wm.area->type && C->wm.area->type->context) {
		memset(&result, 0, sizeof(result));
		C->wm.area->type->context(C, "", &result);

		if(result.dir)
			for(a=0; result.dir[a]; a++)
				data_dir_add(&lb, result.dir[a]);
	}
	if(C->wm.screen && C->wm.screen->context) {
		bContextDataCallback cb= C->wm.screen->context;
		memset(&result, 0, sizeof(result));
		cb(C, "", &result);

		if(result.dir)
			for(a=0; result.dir[a]; a++)
				data_dir_add(&lb, result.dir[a]);
	}

	return lb;
}

int CTX_data_equals(const char *member, const char *str)
{
	return (strcmp(member, str) == 0);
}

int CTX_data_dir(const char *member)
{
	return (strcmp(member, "") == 0);
}

void CTX_data_id_pointer_set(bContextDataResult *result, ID *id)
{
	RNA_id_pointer_create(id, &result->ptr);
}

void CTX_data_pointer_set(bContextDataResult *result, ID *id, StructRNA *type, void *data)
{
	RNA_pointer_create(id, type, data, &result->ptr);
}

void CTX_data_id_list_add(bContextDataResult *result, ID *id)
{
	CollectionPointerLink *link;

	link= MEM_callocN(sizeof(CollectionPointerLink), "CTX_data_id_list_add");
	RNA_id_pointer_create(id, &link->ptr);

	BLI_addtail(&result->list, link);
}

void CTX_data_list_add(bContextDataResult *result, ID *id, StructRNA *type, void *data)
{
	CollectionPointerLink *link;

	link= MEM_callocN(sizeof(CollectionPointerLink), "CTX_data_list_add");
	RNA_pointer_create(id, type, data, &link->ptr);

	BLI_addtail(&result->list, link);
}

int ctx_data_list_count(const bContext *C, int (*func)(const bContext*, ListBase*))
{
	ListBase list;

	if(func(C, &list)) {
		int tot= BLI_countlist(&list);
		BLI_freelistN(&list);
		return tot;
	}
	else
		return 0;
}

void CTX_data_dir_set(bContextDataResult *result, const char **dir)
{
	result->dir= dir;
}

/* data context */

Main *CTX_data_main(const bContext *C)
{
	Main *bmain;

	if(ctx_data_pointer_verify(C, "main", (void*)&bmain))
		return bmain;
	else
		return C->data.main;
}

void CTX_data_main_set(bContext *C, Main *bmain)
{
	C->data.main= bmain;
}

Scene *CTX_data_scene(const bContext *C)
{
	Scene *scene;

	if(ctx_data_pointer_verify(C, "scene", (void*)&scene))
		return scene;
	else
		return C->data.scene;
}

int CTX_data_mode_enum(const bContext *C)
{
	Object *obedit= CTX_data_edit_object(C);

	if(obedit) {
		switch(obedit->type) {
			case OB_MESH:
				return CTX_MODE_EDIT_MESH;
			case OB_CURVE:
				return CTX_MODE_EDIT_CURVE;
			case OB_SURF:
				return CTX_MODE_EDIT_SURFACE;
			case OB_FONT:
				return CTX_MODE_EDIT_TEXT;
			case OB_ARMATURE:
				return CTX_MODE_EDIT_ARMATURE;
			case OB_MBALL:
				return CTX_MODE_EDIT_METABALL;
			case OB_LATTICE:
				return CTX_MODE_EDIT_LATTICE;
		}
	}
	else {
		Object *ob = CTX_data_active_object(C);

		if(ob) {
			if(ob->mode & OB_MODE_POSE) return CTX_MODE_POSE;
			else if(ob->mode & OB_MODE_SCULPT)  return CTX_MODE_SCULPT;
			else if(ob->mode & OB_MODE_WEIGHT_PAINT) return CTX_MODE_PAINT_WEIGHT;
			else if(ob->mode & OB_MODE_VERTEX_PAINT) return CTX_MODE_PAINT_VERTEX;
			else if(ob->mode & OB_MODE_TEXTURE_PAINT) return CTX_MODE_PAINT_TEXTURE;
			else if(ob->mode & OB_MODE_PARTICLE_EDIT) return CTX_MODE_PARTICLE;
		}
	}

	return CTX_MODE_OBJECT;
}


/* would prefer if we can use the enum version below over this one - Campbell */
/* must be aligned with above enum  */
static char *data_mode_strings[] = {
	"mesh_edit",
	"curve_edit",
	"surface_edit",
	"text_edit",
	"armature_edit",
	"mball_edit",
	"lattice_edit",
	"posemode",
	"sculpt_mode",
	"weightpaint",
	"vertexpaint",
	"texturepaint",
	"particlemode",
	"objectmode",
	0
};
char *CTX_data_mode_string(const bContext *C)
{
	return data_mode_strings[CTX_data_mode_enum(C)];
}

void CTX_data_scene_set(bContext *C, Scene *scene)
{
	C->data.scene= scene;
}

ToolSettings *CTX_data_tool_settings(const bContext *C)
{
	Scene *scene = CTX_data_scene(C);

	if(scene)
		return scene->toolsettings;
	else
		return NULL;
}

int CTX_data_selected_nodes(const bContext *C, ListBase *list)
{
	return ctx_data_collection_get(C, "selected_nodes", list);
}

int CTX_data_selected_editable_objects(const bContext *C, ListBase *list)
{
	return ctx_data_collection_get(C, "selected_editable_objects", list);
}

int CTX_data_selected_editable_bases(const bContext *C, ListBase *list)
{
	return ctx_data_collection_get(C, "selected_editable_bases", list);
}

int CTX_data_selected_objects(const bContext *C, ListBase *list)
{
	return ctx_data_collection_get(C, "selected_objects", list);
}

int CTX_data_selected_bases(const bContext *C, ListBase *list)
{
	return ctx_data_collection_get(C, "selected_bases", list);
}

int CTX_data_visible_objects(const bContext *C, ListBase *list)
{
	return ctx_data_collection_get(C, "visible_objects", list);
}

int CTX_data_visible_bases(const bContext *C, ListBase *list)
{
	return ctx_data_collection_get(C, "visible_bases", list);
}

int CTX_data_selectable_objects(const bContext *C, ListBase *list)
{
	return ctx_data_collection_get(C, "selectable_objects", list);
}

int CTX_data_selectable_bases(const bContext *C, ListBase *list)
{
	return ctx_data_collection_get(C, "selectable_bases", list);
}

struct Object *CTX_data_active_object(const bContext *C)
{
	return ctx_data_pointer_get(C, "active_object");
}

struct Base *CTX_data_active_base(const bContext *C)
{
	return ctx_data_pointer_get(C, "active_base");
}

struct Object *CTX_data_edit_object(const bContext *C)
{
	return ctx_data_pointer_get(C, "edit_object");
}

struct Image *CTX_data_edit_image(const bContext *C)
{
	return ctx_data_pointer_get(C, "edit_image");
}

struct Text *CTX_data_edit_text(const bContext *C)
{
	return ctx_data_pointer_get(C, "edit_text");
}

struct EditBone *CTX_data_active_bone(const bContext *C)
{
	return ctx_data_pointer_get(C, "active_bone");
}

int CTX_data_selected_bones(const bContext *C, ListBase *list)
{
	return ctx_data_collection_get(C, "selected_bones", list);
}

int CTX_data_selected_editable_bones(const bContext *C, ListBase *list)
{
	return ctx_data_collection_get(C, "selected_editable_bones", list);
}

int CTX_data_visible_bones(const bContext *C, ListBase *list)
{
	return ctx_data_collection_get(C, "visible_bones", list);
}

int CTX_data_editable_bones(const bContext *C, ListBase *list)
{
	return ctx_data_collection_get(C, "editable_bones", list);
}

struct bPoseChannel *CTX_data_active_pchan(const bContext *C)
{
	return ctx_data_pointer_get(C, "active_pchan");
}

int CTX_data_selected_pchans(const bContext *C, ListBase *list)
{
	return ctx_data_collection_get(C, "selected_pchans", list);
}

int CTX_data_visible_pchans(const bContext *C, ListBase *list)
{
	return ctx_data_collection_get(C, "visible_pchans", list);
}

