#ifndef __LSGRM_CONTROLLER_TXX
#define __LSGRM_CONTROLLER_TXX

namespace lsgrm
{

	template<class TSegmenter>
	Controller<TSegmenter>::Controller()
	{
		m_Memory = 0;
		m_ImageDivisionActivated = true;
	}

	template<class TSegmenter>
	Controller<TSegmenter>::~Controller()
	{
	}

	template<class TSegmenter>
	void Controller<TSegmenter>::RunSegmentation()
	{
		// Rajouter un if pour vérifier si l'utilisateur a enclenché la procédure automatique
		if(m_Memory < 1)
		{
			this->GetAutomaticConfiguration();
			std::cout << m_Memory  << " bytes, tile dimension " << m_TileWidth << " X " << m_TileHeight
					  << ", margin" << m_Margin  << " niter " << m_NumberOfFirstIterations << std::endl;
		}
		
		// Divide the input image if necessary
		if(m_ImageDivisionActivated)
			SplitOTBImage<ImageType>(m_InputImage, m_TileDirectory, m_TileWidth,
									 m_TileHeight, m_Margin, m_NumberOfFirstIterations);
		
		// Retrieve the problem configuration
		RetrieveProblemConfiguration();
		
		// Print values
		std::cout << m_Memory  << " bytes, tile dimension " << m_TileWidth << " X " << m_TileHeight
				  << ", margin" << m_Margin  << " niter " << m_NumberOfFirstIterations << std::endl;
		
		// Boolean indicating if there are remaining fusions
		bool isFusion = false;
		
		// Run first partial segmentation
		auto accumulatedMemory = RunFirstPartialSegmentation<TSegmenter>(m_SpecificParameters,
																		 m_Threshold,
																		 m_NumberOfFirstIterations,
																		 3,
																		 m_Tiles,
																		 m_TileDirectory,
																		 m_NbTilesX,
																		 m_NbTilesY,
																		 m_Margin,
																		 m_TileWidth,
																		 m_TileHeight,
																		 m_ImageWidth,
																		 m_ImageHeight,
																		 m_TemporaryDirectory,
																		 isFusion);
		
		std::cout << "Accumulated memory " << accumulatedMemory << " bytes, there is fusion "<< isFusion << std::endl;

		while(accumulatedMemory > m_Memory && isFusion)
		{
		    isFusion = false;
			accumulatedMemory = RunPartialSegmentation<TSegmenter>(m_SpecificParameters,
																   m_Threshold,
																   3,
																   m_Tiles,
																   m_TemporaryDirectory,
																   m_NbTilesX,
																   m_NbTilesY,
																   m_TileWidth,
																   m_TileHeight,
																   m_ImageWidth,
																   m_ImageHeight,
																   m_ImageBands,
																   isFusion);
			std::cout << "Accumulated memory " << accumulatedMemory << " bytes, there is fusion "
					  << isFusion << std::endl;
		}

		if(accumulatedMemory <= m_Memory)
		{	
			// Merge all the graphs
			MergeAllGraphsAndAchieveSegmentation<TSegmenter>(m_SpecificParameters,
															 m_Threshold,
															 m_Tiles,
															 m_TemporaryDirectory,
															 m_NbTilesX,
															 m_NbTilesY,
															 m_TileWidth,
															 m_TileHeight,
															 m_ImageWidth,
															 m_ImageHeight,
															 m_ImageBands,
															 isFusion,
															 m_OutputGraphDirectory);
		}
		else
		{
			// That means there are no more possible fusions but we can not store the ouput graph
			// Todo do not clean up temporary directory before copying resulting graph to the output directory
			// In the output directory add an info file to give the number of tiles.
		}
	}
	
	template<class TSegmenter>
	void Controller<TSegmenter>::RetrieveProblemConfiguration()
	{
		// Open the lsgrm info file
		std::ifstream in(m_TileDirectory + "info.txt");
		assert(in.good());

	
		std::string line;
		std::vector<std::string> tokens;
	
		std::getline(in, line);
		boost::split(tokens, line, boost::is_any_of(":"));
		m_ImageWidth = static_cast<unsigned int>(atoi(tokens[1].c_str()));

		std::getline(in, line);
		boost::split(tokens, line, boost::is_any_of(":"));
		m_ImageHeight = static_cast<unsigned int>(atoi(tokens[1].c_str()));

		std::getline(in, line);
		boost::split(tokens, line, boost::is_any_of(":"));
		m_ImageBands = static_cast<unsigned int>(atoi(tokens[1].c_str()));

		std::getline(in, line);
		boost::split(tokens, line, boost::is_any_of(":"));
		m_NbTilesX = static_cast<unsigned int>(atoi(tokens[1].c_str()));

		std::getline(in, line);
		boost::split(tokens, line, boost::is_any_of(":"));
		m_NbTilesY = static_cast<unsigned int>(atoi(tokens[1].c_str()));

		std::getline(in, line);
		boost::split(tokens, line, boost::is_any_of(":"));
		m_TileWidth = static_cast<unsigned int>(atoi(tokens[1].c_str()));

		std::getline(in, line);
		boost::split(tokens, line, boost::is_any_of(":"));
		m_TileHeight = static_cast<unsigned int>(atoi(tokens[1].c_str()));

		std::getline(in, line);
		boost::split(tokens, line, boost::is_any_of(":"));
		m_Margin = static_cast<unsigned int>(atoi(tokens[1].c_str()));

		std::getline(in, line);
		boost::split(tokens, line, boost::is_any_of(":"));
		m_NumberOfFirstIterations = static_cast<unsigned int>(atoi(tokens[1].c_str()));

		m_Tiles.assign(m_NbTilesX * m_NbTilesY, ProcessingTile());
		std::vector<std::string> subtokens;
		unsigned int i = 0;
		for(unsigned int row = 0; row < m_NbTilesY; ++row)
		{
			for(unsigned int col = 0; col < m_NbTilesX; ++col)
			{
				std::getline(in, line);
				boost::split(tokens, line, boost::is_any_of(":"));
				boost::split(subtokens, tokens[1], boost::is_any_of(","));

				/* Margin at the top ? */
				if( row > 0 ) 
				{
					m_Tiles[i].rows[0] = row * m_TileHeight;
					m_Tiles[i].tileNeighbors[0] = i - m_NbTilesX;
					m_Tiles[i].margin[0] = true;
				}else
				{
					m_Tiles[i].rows[0] = 0;
					m_Tiles[i].tileNeighbors[0] = -1;
					m_Tiles[i].margin[0] = false;
				}

				/* Margin at the right ? */
				if( col < m_NbTilesX - 1 )
				{
					m_Tiles[i].columns[1] = col * m_TileWidth + static_cast<unsigned int>(atoi(subtokens[2].c_str())) - 1;
					m_Tiles[i].tileNeighbors[2] = i+1;
					m_Tiles[i].margin[1] = true;
				}
				else
				{
					m_Tiles[i].columns[1] = m_ImageWidth - 1;
					m_Tiles[i].tileNeighbors[2] = -1;
					m_Tiles[i].margin[1] = false;
				}

				/* Margin at the bottom */
				if( row < m_NbTilesY - 1)
				{
					m_Tiles[i].rows[1] = row * m_TileHeight + static_cast<unsigned int>(atoi(subtokens[3].c_str())) - 1;
					m_Tiles[i].tileNeighbors[4] = i + m_NbTilesX;
					m_Tiles[i].margin[2] = true;
				}
				else
				{
					m_Tiles[i].rows[1] = m_ImageHeight - 1;
					m_Tiles[i].tileNeighbors[4] = -1;
					m_Tiles[i].margin[2] = false;
				}

				/* Margin at the left */
				if( col > 0 )
				{
					m_Tiles[i].columns[0] = col * m_TileWidth;
					m_Tiles[i].tileNeighbors[6] = i-1;
					m_Tiles[i].margin[3] = true;	
				}
				else
				{
					m_Tiles[i].columns[0] = 0;
					m_Tiles[i].tileNeighbors[6] = -1;
					m_Tiles[i].margin[3] = false;
				}

				/* Is there a neighbor at the rop right */
				if(row > 0 && col < m_NbTilesX - 1)
					m_Tiles[i].tileNeighbors[1] = i - m_NbTilesX + 1;
				else
					m_Tiles[i].tileNeighbors[1] = -1;

				/* Is there a neighbor at the bottom right */
				if(col < m_NbTilesX - 1 && row < m_NbTilesY - 1)
					m_Tiles[i].tileNeighbors[3] = i + m_NbTilesX + 1;
				else
					m_Tiles[i].tileNeighbors[3] = -1;

				/* Is there a neighbor at the bottom left */
				if(row < m_NbTilesY - 1 && col > 0)
					m_Tiles[i].tileNeighbors[5] = i + m_NbTilesX - 1;
				else
					m_Tiles[i].tileNeighbors[5] = -1;

				/* Is there a neighbor at the top left */
				if(col > 0 && row > 0)
					m_Tiles[i].tileNeighbors[7] = i - m_NbTilesX - 1;
				else
					m_Tiles[i].tileNeighbors[7] = -1;
			
				i++;
			}
		}

		in.close();
	}

	template<class TSegmenter>
	void Controller<TSegmenter>::GetAutomaticConfiguration()
	{
		m_Memory = getMemorySize();
		assert(m_Memory > 0);

		m_Memory /= 2; // For safety and can prevent out of memory troubles

		// Compute the size of an initial segment
		using NodeType = typename TSegmenter::NodeType;
		using NodePointer = typename TSegmenter::NodePointerType;
		using EdgeType = typename TSegmenter::EdgeType;
		
		long long unsigned int sizePerNode = sizeof(NodePointer) + sizeof(NodeType) + 1 + 4 *(sizeof(EdgeType) + sizeof(float)); // last term is specific to BS.
		long unsigned int maximumNumberOfNodes = std::ceil(m_Memory / sizePerNode);
		unsigned int tileDimension = std::sqrt(maximumNumberOfNodes);

		// Compute the stability margin. The naive strategy consider a margin value and a stable size equal.
		unsigned int niter = 1;
		unsigned int maxMargin = tileDimension/2;
		unsigned int currMargin = static_cast<unsigned int>(pow(2, niter + 1) - 2);
		unsigned int prevMargin = currMargin;

		while(currMargin < maxMargin)
		{
			prevMargin = currMargin;
			niter++;
			currMargin = static_cast<unsigned int>(pow(2, niter + 1) - 2);
		}

		m_TileWidth = tileDimension - prevMargin;
		m_TileHeight = m_TileWidth;
		m_Margin = prevMargin;
		m_NumberOfFirstIterations = niter - 1;
	}

	template <class TSegmenter>
	void Controller<TSegmenter>::SetInternalMemoryAvailable(long long unsigned int v) // expecting a value in Mbytes.
	{
		assert(v > 0);
		m_Memory = v * 1024ul * 1024ul;
	}
		
	template<class TSegmenter>
	void Controller<TSegmenter>::SetImageDivision(bool f)
	{
		m_ImageDivisionActivated = f;
	}
		
	template<class TSegmenter>
	void Controller<TSegmenter>::SetInputImage(const std::string& str)
	{
		m_InputImage = str;
	}

	template<class TSegmenter>
	void Controller<TSegmenter>::SetOutputGraphDirectory(const std::string& str)
	{
		m_OutputGraphDirectory = str;
	}

	template<class TSegmenter>
	void Controller<TSegmenter>::SetTileDirectory(const std::string& str)
	{
		m_TileDirectory = str;
	}

	template<class TSegmenter>
	void Controller<TSegmenter>::SetTemporaryDirectory(const std::string& str)
	{
		m_TemporaryDirectory = str;
	}

	template<class TSegmenter>
	void Controller<TSegmenter>::SetTileWidth(const unsigned int v)
	{
		m_TileWidth = v;
	}

	template<class TSegmenter>
	void Controller<TSegmenter>::SetTileHeight(const unsigned int v)
	{
		m_TileHeight = v;
	}

	template<class TSegmenter>
	void Controller<TSegmenter>::SetNumberOfFirstIterations(const unsigned int v)
	{
		m_NumberOfFirstIterations = v;
		m_Margin = static_cast<unsigned int>(pow(2, m_NumberOfFirstIterations + 1) - 2);// 2^{n+1}-2
	}

	template<class TSegmenter>
	void Controller<TSegmenter>::SetSpecificParameters(const SegmentationParameterType& params)
	{
		m_SpecificParameters = params;
	}

	template<class TSegmenter>
	void Controller<TSegmenter>::SetThreshold(const float& t)
	{
		m_Threshold = t;
	}
} // end of namespace lsgrm

#endif