/*
 * ***** 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * The Original Code is Copyright (C) 2013 Blender Foundation.
 * All rights reserved.
 *
 * Original Author: Joshua Leung
 * Contributor(s): None Yet
 *
 * ***** END GPL LICENSE BLOCK *****
 */

/** \file blender/depsgraph/intern/depsgraph_eval.cc
 *  \ingroup depsgraph
 *
 * Evaluation engine entrypoints for Depsgraph Engine.
 */

#include "MEM_guardedalloc.h"

#include "PIL_time.h"

extern "C" {
#include "BLI_utildefines.h"
#include "BLI_task.h"

#include "BKE_depsgraph.h"
#include "BKE_scene.h"

#include "DEG_depsgraph.h"
} /* extern "C" */

#include "atomic_ops.h"

#include "depsgraph.h"
#include "depsnode.h"
#include "depsnode_component.h"
#include "depsnode_operation.h"
#include "depsgraph_debug.h"

#ifdef WITH_LEGACY_DEPSGRAPH
static bool use_legacy_depsgraph = true;
#endif

bool DEG_depsgraph_use_legacy(void)
{
#ifdef DISABLE_NEW_DEPSGRAPH
	return true;
#elif defined(WITH_LEGACY_DEPSGRAPH)
	return use_legacy_depsgraph;
#else
	BLI_assert(!"Should not be used with new depsgraph");
	return false;
#endif
}

void DEG_depsgraph_switch_to_legacy(void)
{
#ifdef WITH_LEGACY_DEPSGRAPH
	use_legacy_depsgraph = true;
#else
	BLI_assert(!"Should not be used with new depsgraph");
#endif
}

void DEG_depsgraph_switch_to_new(void)
{
#ifdef WITH_LEGACY_DEPSGRAPH
	use_legacy_depsgraph = false;
#else
	BLI_assert(!"Should not be used with new depsgraph");
#endif
}

/* ****************** */
/* Evaluation Context */

/* Create new evaluation context. */
EvaluationContext *DEG_evaluation_context_new(int mode)
{
	EvaluationContext *eval_ctx =
		(EvaluationContext *)MEM_callocN(sizeof(EvaluationContext),
		                                 "EvaluationContext");
	eval_ctx->mode = mode;
	return eval_ctx;
}

/**
 * Initialize evaluation context.
 * Used by the areas which currently overrides the context or doesn't have
 * access to a proper one.
 */
void DEG_evaluation_context_init(EvaluationContext *eval_ctx, int mode)
{
	eval_ctx->mode = mode;
}

/* Free evaluation context. */
void DEG_evaluation_context_free(EvaluationContext *eval_ctx)
{
	MEM_freeN(eval_ctx);
}

/* ********************** */
/* Evaluation Entrypoints */

/* Forward declarations. */
static void schedule_children(TaskPool *pool,
                              Depsgraph *graph,
                              OperationDepsNode *node,
                              const int layers);

struct DepsgraphEvalState {
	EvaluationContext *eval_ctx;
	Depsgraph *graph;
	int layers;
};

static void deg_task_run_func(TaskPool *pool,
                              void *taskdata,
                              int UNUSED(threadid))
{
	DepsgraphEvalState *state = (DepsgraphEvalState *)BLI_task_pool_userdata(pool);
	OperationDepsNode *node = (OperationDepsNode *)taskdata;

	if (!node->is_noop()) {
		/* Get context. */
		// TODO: who initialises this? "Init" operations aren't able to initialise it!!!
		/* TODO(sergey): Wedon't use component contexts at this moment. */
		/* ComponentDepsNode *comp = node->owner; */
		BLI_assert(node->owner != NULL);

		/* Take note of current time. */
		double start_time = PIL_check_seconds_timer();
		DepsgraphDebug::task_started(state->graph, node);

		/* Should only be the case for NOOPs, which never get to this point. */
		BLI_assert(node->evaluate);

		/* Perform operation. */
		node->evaluate(state->eval_ctx);

		/* Note how long this took. */
		double end_time = PIL_check_seconds_timer();
		DepsgraphDebug::task_completed(state->graph,
		                               node,
		                               end_time - start_time);
	}

	schedule_children(pool, state->graph, node, state->layers);
}

static void calculate_pending_parents(Depsgraph *graph, int layers)
{
	for (Depsgraph::OperationNodes::const_iterator it_op = graph->operations.begin();
	     it_op != graph->operations.end();
	     ++it_op)
	{
		OperationDepsNode *node = *it_op;
		IDDepsNode *id_node = node->owner->owner;

		node->num_links_pending = 0;
		node->scheduled = false;

		/* count number of inputs that need updates */
		if ((id_node->layers & layers) != 0 &&
		    (node->flag & DEPSOP_FLAG_NEEDS_UPDATE) != 0)
		{
			for (OperationDepsNode::Relations::const_iterator it_rel = node->inlinks.begin();
			     it_rel != node->inlinks.end();
			     ++it_rel)
			{
				DepsRelation *rel = *it_rel;
				if (rel->from->type == DEPSNODE_TYPE_OPERATION &&
				    (rel->flag & DEPSREL_FLAG_CYCLIC) == 0)
				{
					OperationDepsNode *from = (OperationDepsNode *)rel->from;
					IDDepsNode *id_from_node = from->owner->owner;
					if ((id_from_node->layers & layers) != 0 &&
					    (from->flag & DEPSOP_FLAG_NEEDS_UPDATE) != 0)
					{
						++node->num_links_pending;
					}
				}
			}
		}
	}
}

static void calculate_eval_priority(OperationDepsNode *node)
{
	if (node->done) {
		return;
	}
	node->done = 1;

	if (node->flag & DEPSOP_FLAG_NEEDS_UPDATE) {
		/* XXX standard cost of a node, could be estimated somewhat later on */
		const float cost = 1.0f;
		/* NOOP nodes have no cost */
		node->eval_priority = node->is_noop() ? cost : 0.0f;

		for (OperationDepsNode::Relations::const_iterator it = node->outlinks.begin();
		     it != node->outlinks.end();
		     ++it)
		{
			DepsRelation *rel = *it;
			OperationDepsNode *to = (OperationDepsNode *)rel->to;
			BLI_assert(to->type == DEPSNODE_TYPE_OPERATION);
			calculate_eval_priority(to);
			node->eval_priority += to->eval_priority;
		}
	}
	else {
		node->eval_priority = 0.0f;
	}
}

static void schedule_graph(TaskPool *pool,
                           Depsgraph *graph,
                           const int layers)
{
	BLI_spin_lock(&graph->lock);
	for (Depsgraph::OperationNodes::const_iterator it = graph->operations.begin();
	     it != graph->operations.end();
	     ++it)
	{
		OperationDepsNode *node = *it;
		IDDepsNode *id_node = node->owner->owner;
		if ((node->flag & DEPSOP_FLAG_NEEDS_UPDATE) &&
		    node->num_links_pending == 0 &&
		    (id_node->layers & layers) != 0)
		{
			BLI_task_pool_push(pool, deg_task_run_func, node, false, TASK_PRIORITY_LOW);
			node->scheduled = true;
		}
	}
	BLI_spin_unlock(&graph->lock);
}

static void schedule_children(TaskPool *pool,
                              Depsgraph *graph,
                              OperationDepsNode *node,
                              const int layers)
{
	for (OperationDepsNode::Relations::const_iterator it = node->outlinks.begin();
	     it != node->outlinks.end();
	     ++it)
	{
		DepsRelation *rel = *it;
		OperationDepsNode *child = (OperationDepsNode *)rel->to;
		BLI_assert(child->type == DEPSNODE_TYPE_OPERATION);

		if (child->scheduled) {
			/* Happens when having cyclic dependencies. */
			continue;
		}

		IDDepsNode *id_child = child->owner->owner;
		if ((id_child->layers & layers) != 0 &&
		    (child->flag & DEPSOP_FLAG_NEEDS_UPDATE) != 0)
		{
			if ((rel->flag & DEPSREL_FLAG_CYCLIC) == 0) {
				BLI_assert(child->num_links_pending > 0);
				atomic_sub_uint32(&child->num_links_pending, 1);
			}

			if (child->num_links_pending == 0) {
				BLI_spin_lock(&graph->lock);
				bool need_schedule = !child->scheduled;
				child->scheduled = true;
				BLI_spin_unlock(&graph->lock);

				if (need_schedule) {
					BLI_task_pool_push(pool, deg_task_run_func, child, false, TASK_PRIORITY_LOW);
				}
			}
		}
	}
}

/**
 * Evaluate all nodes tagged for updating,
 * \warning This is usually done as part of main loop, but may also be
 * called from frame-change update.
 *
 * \note Time sources should be all valid!
 */
void DEG_evaluate_on_refresh_ex(EvaluationContext *eval_ctx,
                                Depsgraph *graph,
                                const int layers)
{
	/* Generate base evaluation context, upon which all the others are derived. */
	// TODO: this needs both main and scene access...

	/* Nothing to update, early out. */
	if (graph->entry_tags.size() == 0) {
		return;
	}

	/* Set time for the current graph evaluation context. */
	TimeSourceDepsNode *time_src = graph->find_time_source();
	eval_ctx->ctime = time_src->cfra;

	/* XXX could use a separate pool for each eval context */
	DepsgraphEvalState state;
	state.eval_ctx = eval_ctx;
	state.graph = graph;
	state.layers = layers;

	TaskScheduler *task_scheduler = BLI_task_scheduler_get();
	TaskPool *task_pool = BLI_task_pool_create(task_scheduler, &state);

	if (G.debug & G_DEBUG_DEPSGRAPH_NO_THREADS) {
		BLI_pool_set_num_threads(task_pool, 1);
	}

	calculate_pending_parents(graph, layers);

	/* Clear tags. */
	for (Depsgraph::OperationNodes::const_iterator it = graph->operations.begin();
	     it != graph->operations.end();
	     ++it)
	{
		OperationDepsNode *node = *it;
		node->done = 0;
	}

	/* Calculate priority for operation nodes. */
	for (Depsgraph::OperationNodes::const_iterator it = graph->operations.begin();
	     it != graph->operations.end();
	     ++it)
	{
		OperationDepsNode *node = *it;
		calculate_eval_priority(node);
	}

	DepsgraphDebug::eval_begin(eval_ctx);

	schedule_graph(task_pool, graph, layers);

	BLI_task_pool_work_and_wait(task_pool);
	BLI_task_pool_free(task_pool);

	DepsgraphDebug::eval_end(eval_ctx);

	/* Clear any uncleared tags - just in case. */
	DEG_graph_clear_tags(graph);
}

/* Evaluate all nodes tagged for updating. */
void DEG_evaluate_on_refresh(EvaluationContext *eval_ctx,
                             Depsgraph *graph,
                             Scene *scene)
{
	/* Update time on primary timesource. */
	TimeSourceDepsNode *tsrc = graph->find_time_source();
	tsrc->cfra = BKE_scene_frame_get(scene);

	DEG_evaluate_on_refresh_ex(eval_ctx, graph, graph->layers);
}

/* Frame-change happened for root scene that graph belongs to. */
void DEG_evaluate_on_framechange(EvaluationContext *eval_ctx,
                                 Main *bmain,
                                 Depsgraph *graph,
                                 float ctime,
                                 const int layers)
{
	/* Update time on primary timesource. */
	TimeSourceDepsNode *tsrc = graph->find_time_source();
	tsrc->cfra = ctime;

	tsrc->tag_update(graph);

	DEG_graph_flush_updates(bmain, graph);

	/* Perform recalculation updates. */
	DEG_evaluate_on_refresh_ex(eval_ctx, graph, layers);
}

bool DEG_needs_eval(Depsgraph *graph)
{
	return graph->entry_tags.size() != 0;
}
