/*
 * 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.
 *
 * Contributor: 
 *		Jeroen Bakker 
 *		Monique Dewanchand
 */

#ifndef _COM_ExecutionGroup_h
#define _COM_ExecutionGroup_h

#include "COM_Node.h"
#include "COM_NodeOperation.h"
#include <vector>
#include "BLI_rect.h"
#include "COM_MemoryProxy.h"
#include "COM_Device.h"
#include "COM_CompositorContext.h"

using std::vector;

class ExecutionSystem;
class MemoryProxy;
class ReadBufferOperation;
class Device;

/**
 * @brief the execution state of a chunk in an ExecutionGroup
 * @ingroup Execution
 */
typedef enum ChunkExecutionState {
	/**
	 * @brief chunk is not yet scheduled
	 */
	COM_ES_NOT_SCHEDULED = 0,
	/**
	 * @brief chunk is scheduled, but not yet executed
	 */
	COM_ES_SCHEDULED = 1,
	/**
	 * @brief chunk is executed.
	 */
	COM_ES_EXECUTED = 2
} ChunkExecutionState;

/**
 * @brief Class ExecutionGroup is a group of Operations that are executed as one.
 * This grouping is used to combine Operations that can be executed as one whole when multi-processing.
 * @ingroup Execution
 */
class ExecutionGroup {
public:
	 typedef std::vector<NodeOperation*> Operations;
	
private:
	// fields
	
	/**
	 * @brief list of operations in this ExecutionGroup
	 */
	Operations m_operations;
	
	/**
	 * @brief is this ExecutionGroup an input ExecutionGroup
	 * an input execution group is a group that is at the end of the calculation (the output is important for the user)
	 */
	int m_isOutput;
	
	/**
	 * @brief Width of the output
	 */
	unsigned int m_width;
	
	/**
	 * @brief Height of the output
	 */
	unsigned int m_height;
	
	/**
	 * @brief size of a single chunk, being Width or of height
	 * a chunk is always a square, except at the edges of the MemoryBuffer
	 */
	unsigned int m_chunkSize;
	
	/**
	 * @brief number of chunks in the x-axis
	 */
	unsigned int m_numberOfXChunks;
	
	/**
	 * @brief number of chunks in the y-axis
	 */
	unsigned int m_numberOfYChunks;
	
	/**
	 * @brief total number of chunks
	 */
	unsigned int m_numberOfChunks;
	
	/**
	 * @brief contains this ExecutionGroup a complex NodeOperation.
	 */
	bool m_complex;
	
	/**
	 * @brief can this ExecutionGroup be scheduled on an OpenCLDevice
	 */
	bool m_openCL;
	
	/**
	 * @brief Is this Execution group SingleThreaded
	 */
	bool m_singleThreaded;
	
	/**
	 * @brief what is the maximum number field of all ReadBufferOperation in this ExecutionGroup.
	 * @note this is used to construct the MemoryBuffers that will be passed during execution.
	 */
	unsigned int m_cachedMaxReadBufferOffset;
	
	/**
	 * @brief a cached vector of all read operations in the execution group.
	 */
	Operations m_cachedReadOperations;
	
	/**
	 * @brief reference to the original bNodeTree, this field is only set for the 'top' execution group.
	 * @note can only be used to call the callbacks for progress, status and break
	 */
	const bNodeTree *m_bTree;
	
	/**
	 * @brief total number of chunks that have been calculated for this ExecutionGroup
	 */
	unsigned int m_chunksFinished;
	
	/**
	 * @brief the chunkExecutionStates holds per chunk the execution state. this state can be
	 *   - COM_ES_NOT_SCHEDULED: not scheduled
	 *   - COM_ES_SCHEDULED: scheduled
	 *   - COM_ES_EXECUTED: executed
	 */
	ChunkExecutionState *m_chunkExecutionStates;
	
	/**
	 * @brief indicator when this ExecutionGroup has valid Operations in its vector for Execution
	 * @note When building the ExecutionGroup Operations are added via recursion. First a WriteBufferOperations is added, then the
	 * @note Operation containing the settings that is important for the ExecutiongGroup is added,
	 * @note When this occurs, these settings are copied over from the node to the ExecutionGroup
	 * @note and the Initialized flag is set to true.
	 * @see complex
	 * @see openCL
	 */
	bool m_initialized;

	/**
	 * @brief denotes boundary for border compositing
	 * @note measured in pixel space
	 */
	rcti m_viewerBorder;

	/**
	 * @brief start time of execution
	 */
	double m_executionStartTime;

	// methods
	/**
	 * @brief check whether parameter operation can be added to the execution group
	 * @param operation the operation to be added
	 */
	bool canContainOperation(NodeOperation *operation);
		
	/**
	 * @brief calculate the actual chunk size of this execution group.
	 * @note A chunk size is an unsigned int that is both the height and width of a chunk.
	 * @note The chunk size will not be stored in the chunkSize field. This needs to be done
	 * @note by the calling method.
	 */
	unsigned int determineChunkSize();
	
	
	/**
	 * @brief Determine the rect (minx, maxx, miny, maxy) of a chunk at a position.
	 * @note Only gives useful results ater the determination of the chunksize
	 * @see determineChunkSize()
	 */
	void determineChunkRect(rcti *rect, const unsigned int xChunk, const unsigned int yChunk) const;
	
	/**
	 * @brief determine the number of chunks, based on the chunkSize, width and height.
	 * @note The result are stored in the fields numberOfChunks, numberOfXChunks, numberOfYChunks
	 */
	void determineNumberOfChunks();
	
	/**
	 * @brief try to schedule a specific chunk.
	 * @note scheduling succeeds when all input requirements are met and the chunks hasn't been scheduled yet.
	 * @param graph
	 * @param xChunk
	 * @param yChunk
	 * @return [true:false]
	 * true: package(s) are scheduled
	 * false: scheduling is deferred (depending workpackages are scheduled)
	 */
	bool scheduleChunkWhenPossible(ExecutionSystem *graph, int xChunk, int yChunk);

	/**
	 * @brief try to schedule a specific area.
	 * @note Check if a certain area is available, when not available this are will be checked.
	 * @note This method is called from other ExecutionGroup's.
	 * @param graph
	 * @param rect
	 * @return [true:false]
	 * true: package(s) are scheduled
	 * false: scheduling is deferred (depending workpackages are scheduled)
	 */
	bool scheduleAreaWhenPossible(ExecutionSystem *graph, rcti *rect);

	/**
	 * @brief add a chunk to the WorkScheduler.
	 * @param chunknumber
	 */
	bool scheduleChunk(unsigned int chunkNumber);
	
	/**
	 * @brief determine the area of interest of a certain input area
	 * @note This method only evaluates a single ReadBufferOperation
	 * @param input the input area
	 * @param readOperation The ReadBufferOperation where the area needs to be evaluated
	 * @param output the area needed of the ReadBufferOperation. Result
	 */
	void determineDependingAreaOfInterest(rcti *input, ReadBufferOperation *readOperation, rcti *output);


public:
	// constructors
	ExecutionGroup();
	
	// methods
	/**
	 * @brief add an operation to this ExecutionGroup
	 * @note this method will add input of the operations recursively
	 * @note this method can create multiple ExecutionGroup's
	 * @param system
	 * @param operation
	 * @return True if the operation was successfully added
	 */
	bool addOperation(NodeOperation *operation);
	
	/**
	 * @brief is this ExecutionGroup an output ExecutionGroup
	 * @note An OutputExecution group are groups containing a
	 * @note ViewerOperation, CompositeOperation, PreviewOperation.
	 * @see NodeOperation.isOutputOperation
	 */
	const int isOutputExecutionGroup() const { return this->m_isOutput; }

	/**
	 * @brief set whether this ExecutionGroup is an output
	 * @param isOutput
	 */
	void setOutputExecutionGroup(int isOutput) { this->m_isOutput = isOutput; }

	/**
	 * @brief determine the resolution of this ExecutionGroup
	 * @param resolution
	 */
	void determineResolution(unsigned int resolution[2]);
	
	/**
	 * @brief set the resolution of this executiongroup
	 * @param resolution
	 */
	void setResolution(unsigned int resolution[2]) { this->m_width = resolution[0]; this->m_height = resolution[1]; }
	
	/**
	 * @brief get the width of this execution group
	 */
	unsigned int getWidth() const { return m_width; }
	
	/**
	 * @brief get the height of this execution group
	 */
	unsigned int getHeight() const { return m_height; }
	
	/**
	 * @brief does this ExecutionGroup contains a complex NodeOperation
	 */
	bool isComplex() const { return m_complex; }
	
	
	/**
	 * @brief get the output operation of this ExecutionGroup
	 * @return NodeOperation *output operation
	 */
	NodeOperation *getOutputOperation() const;
	
	/**
	 * @brief compose multiple chunks into a single chunk
	 * @return Memorybuffer *consolidated chunk
	 */
	MemoryBuffer *constructConsolidatedMemoryBuffer(MemoryProxy *memoryProxy, rcti *output);
	
	/**
	 * @brief initExecution is called just before the execution of the whole graph will be done.
	 * @note The implementation will calculate the chunkSize of this execution group.
	 */
	void initExecution();
	
	/**
	 * @brief get all inputbuffers needed to calculate an chunk
	 * @note all inputbuffers must be executed
	 * @param chunkNumber the chunk to be calculated
	 * @return (MemoryBuffer **) the inputbuffers
	 */
	MemoryBuffer **getInputBuffersCPU();

	/**
	 * @brief get all inputbuffers needed to calculate an chunk
	 * @note all inputbuffers must be executed
	 * @param chunkNumber the chunk to be calculated
	 * @return (MemoryBuffer **) the inputbuffers
	 */
	MemoryBuffer **getInputBuffersOpenCL(int chunkNumber);

	/**
	 * @brief allocate the outputbuffer of a chunk
	 * @param chunkNumber the number of the chunk in the ExecutionGroup
	 * @param rect the rect of that chunk
	 * @see determineChunkRect
	 */
	MemoryBuffer *allocateOutputBuffer(int chunkNumber, rcti *rect);

	/**
	 * @brief print execution statistics to stdout when running in a background mode
	 */
	void printBackgroundStats(void);
	
	/**
	 * @brief after a chunk is executed the needed resources can be freed or unlocked.
	 * @param chunknumber
	 * @param memorybuffers
	 */
	void finalizeChunkExecution(int chunkNumber, MemoryBuffer **memoryBuffers);
	
	/**
	 * @brief deinitExecution is called just after execution the whole graph.
	 * @note It will release all needed resources
	 */
	void deinitExecution();
	
	
	/**
	 * @brief schedule an ExecutionGroup
	 * @note this method will return when all chunks have been calculated, or the execution has breaked (by user)
	 *
	 * first the order of the chunks will be determined. This is determined by finding the ViewerOperation and get the relevant information from it.
	 *   - ChunkOrdering
	 *   - CenterX
	 *   - CenterY
	 *
	 * After determining the order of the chunks the chunks will be scheduled
	 *
	 * @see ViewerOperation
	 * @param system
	 */
	void execute(ExecutionSystem *system);
	
	/**
	 * @brief this method determines the MemoryProxy's where this execution group depends on.
	 * @note After this method determineDependingAreaOfInterest can be called to determine
	 * @note the area of the MemoryProxy.creator that has to be executed.
	 * @param memoryProxies result
	 */
	void determineDependingMemoryProxies(vector<MemoryProxy *> *memoryProxies);
	
	/**
	 * @brief Determine the rect (minx, maxx, miny, maxy) of a chunk.
	 * @note Only gives useful results ater the determination of the chunksize
	 * @see determineChunkSize()
	 */
	void determineChunkRect(rcti *rect, const unsigned int chunkNumber) const;

	/**
	 * @brief can this ExecutionGroup be scheduled on an OpenCLDevice
	 * @see WorkScheduler.schedule
	 */
	bool isOpenCL();

	void setChunksize(int chunksize) { this->m_chunkSize = chunksize; }

	/**
	 * @brief get the Render priority of this ExecutionGroup
	 * @see ExecutionSystem.execute
	 */
	CompositorPriority getRenderPriotrity();

	/**
	 * @brief set border for viewer operation
	 * @note all the coordinates are assumed to be in normalized space
	 */
	void setViewerBorder(float xmin, float xmax, float ymin, float ymax);

	void setRenderBorder(float xmin, float xmax, float ymin, float ymax);

	/* allow the DebugInfo class to look at internals */
	friend class DebugInfo;

#ifdef WITH_CXX_GUARDEDALLOC
	MEM_CXX_CLASS_ALLOC_FUNCS("COM:ExecutionGroup")
#endif
};

#endif
