--SPACE MODELS-----------------------------------------------------------------------------
function Cell( attrTab )
	local cObj = TeCell();
	local metaAttr = { 
	  cObj_ = cObj,  
	  past = {},
	  getNeighborhood = function(self, index)
		  return self.cObj_:getNeighborhood( index );
	  end,
	  synchronize = function( self ) 
		  self.past = {};
		  for k,v in pairs( self ) do if( k ~= "past") then self.past[k] = v; end end
	  end,
	  getPast = function(self)
	  	  return self.past;
	  end,
	  addNeighborhood = function( self, neigh )
		return self.cObj_:addNeighborhood( neigh.cObj_);
	  end
	};
	local metaTable = { __index = metaAttr };
	if( attrTab == nil ) then attrTab = {}; end
	setmetatable(attrTab, metaTable);
	cObj:setReference(attrTab);
	return attrTab;
end
--------------------------
function CellularSpace( attrTab )
	local cObj = TeCellularSpace();
	local metaAttr = { cells = {}, cObj_= cObj, minRow = 0, minCol = 0, maxRow = 0, maxCol = 0,
	  	--[[
	  	-- esta função não funciona somente pq o espaço celular não é regular
	  	index2Coord = function(self, index)
	  	  local x = 0; 
	  	  local y = 0;
	  	  local cols = (self.maxCol - self.minCol) + 1;
	  	  --local rows = (maxRow - minRow) + 1;
	  	  local term = math.floor( ( index - 1) / cols );
	  	  x = self.minCol + (( index - 1 ) - term*cols );
	  	  y = self.minRow + term;
	  	  print(index.." --> "..x..", "..y);
	  	  io.read(1);
	  	  return x, y;
	 	end, ]]--
	 	loadNeighborhood = function(self, filename)
	 	  self.cObj_:loadNeighborhood( filename );
		end,
		load = function(self) 
		  local x = 0;
		  local y = 0;
			self.cells, self.minCol, self.minRow, self.maxCol, self.maxRow = self.cObj_:load(); 
			self.cObj_:clear();
			for i,tab in pairs( self.cells )do 
				self.cObj_:addCell( tab.x, tab.y, tab.cObj_ );
			end
		end,
		save = function(self, time, outputTableName, attrNames) 
		  local erros = self.cObj_:save(time, outputTableName, attrNames, self.cells); 
		end,
		synchronize = function(self) 
			for i,cell in pairs( self.cells )do 
				  cell:synchronize();
			end
		end, 
		setDraw = function(self)
			self.cObj_:setDraw();
		end
	};
	local metaTable = { __index = metaAttr };
	if ( attrTab.dbType == nil ) then cObj:setDBType("ado"); else cObj:setDBType( string.lower(attrTab.dbType) ); end
	if ( attrTab.host == nil ) then cObj:setHostName("localhost"); else cObj:setHostName( attrTab.host ); end
  	if ( attrTab.database ~= nil) then cObj:setDBName( attrTab.database ); else print("Error: database name not defined!"); os.exit(); end
	if ( attrTab.user == nil ) then cObj:setUser(""); else cObj:setUser( attrTab.user ); end
	if ( attrTab.password == nil) then cObj:setPassword(""); else cObj:setPassword( attrTab.password ); end
  	if ( attrTab.layer ~= nil ) then cObj:setLayer(attrTab.layer); else print("Error: layer name not defined!"); os.exit(); end
	if ( attrTab.theme ~= nil ) then cObj:setTheme(attrTab.theme); else print("Error: theme name not defined!"); os.exit(); end
	if ( attrTab.select ~= nil ) then
		cObj:clearAttrName();
		for i in ipairs(attrTab.select) do
			cObj:addAttrName(attrTab.select[i]);
		end
	end;
	if( attrTab.where ~= nil ) then cObj:setWhereClause( attrTab.where ); end
	setmetatable(attrTab, metaTable);
	cObj:setReference(attrTab);
	
	return attrTab;
end
--------------------------
function Neighborhood( attrTab )
  if( attrTab == nil ) then attrTab = {}; end
  local cObj = TeNeighborhood(attrTab);
  local metaAttr = {
  	cObj_ = cObj,
  	addCell = function( self, index, cellularSpace, weight)
  		self.cObj_:addCell( index, cellularSpace.cObj_, weight); 
  	end,
  	eraseCell = function( self, index )
  		self.cObj_:eraseCell( index );
  	end,
	getCellWeight = function( self, index)
		return self.cObj_:getCellWeight( index );
	end,
	getCellNeighbor = function( self, index)
		return self.cObj_:getCellNeighbor( index );
	end,
	getWeight = function( self )
		return self.cObj_:getWeight();
	end,
	getNeighbor = function( self )
		return self.cObj_:getNeighbor();
	end,
	setCellWeight = function( self, index, weight )
		self.cObj_:setCellWeight(index, weight);
	end,
	setWeight = function( self, weight )
		self.cObj_:setWeight(weight);
	end,
	first = function( self )
		return self.cObj_:first();
	end,
	last  = function( self )
		return self.cObj_:last();
	end,
	next  = function( self ) 
		self.cObj_:next();
	end,
	getCoord = function( self )
		return self.cObj_:getCoord();		
	end,
	isEmpty = function( self )
		return self.cObj_:isEmpty();		
	end,
	clear = function( self )
		self.cObj_:clear();		
	end,
	size = function( self )
		return self.cObj_:size();		
	end
  };
  local metaTable = { __index = metaAttr };
  setmetatable(attrTab, metaTable);
  return attrTab;
end
--TIME MODELS-------------------------------------------------------------------------------
function Event( attrTab )
	local cObj = TeEvent();
	if ( attrTab.time ~= nil ) then 
		if ( attrTab.period == nil ) then cObj:config( attrTab.time, 0 );
		else cObj:config( attrTab.time, attrTab.period ); end
	else 
		print("Error: event time not defined!"); os.exit(); 
	end
	if(  attrTab.priority ~= nil ) then cObj:setPriority(attrTab.priority); end
	cObj:setReference(cObj);
	return cObj;
end
--------------------------
function Message( attrTab )
	local cObj = TeMessage();
	local metaAttr = { cObj_ = cObj };
	local metaTable = { __index = metaAttr };
	--if ( attrTab.execute ~= nil ) then 
		if ( attrTab.id ~= nil ) then cObj:config( attrTab.id );
		--else print("Error: the value of message 'id' must be name of message variable!"); os.exit(); 
		end
	--else 
	--	print("Error: message function not defined!"); os.exit(); 
	--end
	setmetatable(attrTab, metaTable);
	cObj:setReference(attrTab);
	return attrTab;
end
--------------------------
function Timer( attrTab )
	local cObj = TeTimer();
	local metaAttr = { 
	    cObj_ = cObj, 
		add = function (self, event, message) self.cObj_:add(event,message.cObj_); end,
		getTime = function(self) return self.cObj_:getTime(); end,
		execute = function(self) self.cObj_:execute(); end,
		reset = function(self) self.cObj_:reset(); end
	};
	if( attrTab == nil ) then attrTab = {}; end
	local metaTable = { __index = metaAttr };
	for i,ud in pairs( attrTab )do 
	    if( type( ud ) == "table" ) then cObj:add(ud.cObj_[1],ud.cObj_[2].cObj_); end
	    --if( type( ud ) == "userdata" ) then cObj:add(ud); end
	end
	setmetatable(attrTab, metaTable);
	
	return attrTab;
end
--Behavior MODEL-------------------------------------------------------------------------------
function Jump( attrTab )
	local cObj = TeJump();
	local metaAttr = { rule = cObj };
	local metaTable = { __index = metaAttr };
	if ( type(attrTab[1]) == "function" ) then 
	   	if( attrTab.target ~= nil ) then cObj:setTargetControlModeName( attrTab.target );
		else print("Error: target control mode not defined!"); os.exit(); end
	else 
		print("Error: the first jump condition attribute must be a function of the form\n       \"boolean = function(event, automaton, cell) end\""); os.exit(); 
	end
	setmetatable(attrTab, metaTable);
	cObj:setReference(attrTab);
	return cObj;
end
--------------------------
function Flow( attrTab )
	local cObj = TeFlow();
	local metaAttr = { rule = cObj };
	local metaTable = { __index = metaAttr };
	if ( type(attrTab[1]) ~= "function" ) then 
	   	print("Error: the first flow condition attribute must be a function of the form\n      \"boolean = function(event, automaton, cell) end\""); os.exit(); 
	end
	setmetatable(attrTab, metaTable);
	cObj:setReference(attrTab);
	
	return cObj;
end
--------------------------
function State( attrTab )
	local cObj = TeState();
	--local metaAttr = { ctrlMode = cObj };
	--local metaTable = { __index = metaAttr };
	if ( attrTab.id ~= nil ) then 
		cObj:config(attrTab.id);
	else
	   	print("Error: control mode id not defined!"); os.exit(); 
	end
	for i,ud in pairs( attrTab )do 
	    if( type( ud ) == "table" ) then cObj:add(ud.cObj_); end
	    if( type( ud ) == "userdata" ) then cObj:add(ud); end
	end
	--setmetatable(attrTab, metaTable);
	--return attrTab;
	return cObj;
end
--------------------------
function LocalAutomaton( attrTab )
	local cObj = TeLocalAutomaton();
	local metaAttr = { cObj_ = cObj,
		add = function(self, state ) self.cObj_:add(state); end,
		getLatency = function(self ) return self.cObj_:getLatency(); end,
		execute = function(self, event ) self.cObj_:execute(event); end,
		build = function(self ) self.cObj_:build(); end,
		getStateName = function(self) return self.cObj_:getStateName(); end,
		setActionRegionStatus = function(self, status) self.cObj_:setActionRegionStatus( status ); end
	};
	local metaTable = { __index = metaAttr };
	setmetatable(attrTab, metaTable);
	cObj:setReference(attrTab);
	for i,ud in pairs( attrTab )do 
	    if( type( ud ) == "table" ) then cObj:add(ud.cObj_); end
	    if( type( ud ) == "userdata" ) then cObj:add(ud); end
	end
	
	return attrTab;
end
--------------------------
function GlobalAutomaton( attrTab )
	local cObj = TeGlobalAutomaton();
	local metaAttr = { cObj_ = cObj,
		add = function(self, objeto ) self.cObj_:add(objeto); end,
		getLatency = function(self ) return self.cObj_:getLatency(); end,
		execute = function(self, event )self.cObj_:execute(event); end,
		build = function(self, event) self.cObj_:build(); end,
		getStateName = function(self) return self.cObj_:getStateName(); end,
		setActionRegionStatus = function(self, status) self.cObj_:setActionRegionStatus( status ); end
	};
	local metaTable = { __index = metaAttr };
	setmetatable(attrTab, metaTable);
	cObj:setReference(attrTab);
	for i,ud in pairs( attrTab )do 
	    if( type( ud ) == "table" ) then cObj:add(ud.cObj_); end
	    if( type( ud ) == "userdata" ) then cObj:add(ud); end
	end
	
	return attrTab;
end
--Evironment MODEL-------------------------------------------------------------------------------
function Scale( attrTab )
  if( attrTab.id == nil ) then 
  	print("Error: Scale ID not defined!"); os.exit(); 
  end
  local cObj = TeScale(attrTab.id);
	local metaAttr = { 
	  	cObj_ = cObj, 
		config = function (self, finalTime) self.cObj_:config(finalTime); end,
		add = function (self, objeto ) return self.cObj_:add( objeto.cObj_ ); end,
		addTimer = function(self, time, timer) return self.cObj_:addTimer(time, timer.cObj_); end,
		addCellularSpace = function(self, cellularSpace) return self.cObj_:addCellularSpace( cellularSpace.cObj_ ); end,
		addGlobalAutomaton = function(self, automaton) return self.cObj_:addGlobalAutomaton( automaton.cObj_ ); end,
		addLocalAutomaton = function(self, automaton ) return self.cObj_:addLocalAutomaton( automaton.cObj_ ); end,
		execute = function(self) self.cObj_:execute(); end
	};
	local metaTable = { __index = metaAttr };
	setmetatable(attrTab, metaTable);
	for k,ud in pairs( attrTab )do 
		if( type( ud ) == "table" ) then cObj:add(ud.cObj_); end
	    if( type( ud ) == "userdata" ) then cObj:add(ud); end
	end
	
	return attrTab;
end
--Spatial Iterator MODEL-------------------------------------------------------------------------------
function Trajectory( attrTab )
	local metaAttr = { cellularSpace = {},
					   cObj_ = {},
					   cells = {},
					   count = 0,
					   size = function(self) return self.count; end,
					   filter = function(self, f )
					    	self.cObj_= {};
					    	self.count = 0;
					    	for i, cell in ipairs( self.cellularSpace )do
					    		if( f( cell ) ) then 
					    			self.count = self.count + 1;
					    			table.insert(self.cObj_, cell ); 
					    		end
					    	end
					    	self.cells = self.cObj_;
					    end,
					   	sort = function(self, greaterThan )
					   		table.sort( self.cObj_, greaterThan ) 
					   	end,
					   	sortCoord = function(self )
					   		table.sort( self.cObj_,
					   					function(a,b) 
					   						if( a.x > b.x )then return 1; 
					   						else if( a.x < b.x ) then return 0; 
					   						     else return (a.y > b.y ); end
					   						end
					   					end
					   				   )
					   	end
					 }
	if( attrTab == nil ) then attrTab = {}; end
	local sizeAttrTab = table.getn( attrTab );
	if( sizeAttrTab < 1 ) then 
    	print("Error: the first attribute of a spatial iterator must be a cellular space!"); 
    	os.exit();	  
    end
	if( sizeAttrTab  < 2 ) then 
		print("Error: the second attribute of a spatial iterator must be a filter function of ");
		print("       form: bool = function( cell )");
	 	os.exit();	  
	end
    local metaTable = { __index = metaAttr };
	setmetatable(attrTab, metaTable);
	attrTab.cellularSpace = attrTab[1].cells;
	--attrTab.cObj_ = attrTab.cellularSpace;
	attrTab:filter( attrTab[2] );
	--attrTab.cells = attrTab.cObj_;
	if( sizeAttrTab > 2 ) then attrTab:sort( attrTab[3] ); end;
	return attrTab;
end
--Auxiliary constructors------------------------------------------------
function Pair( attrTab )
	local metaAttr = { cObj_ = attrTab };
	local metaTable = { __index = metaAttr };
    if( table.getn( attrTab ) ~= 2 )then 
    	print("Error: a pair must have two attributes!"); os.exit();
    end;
	local metaTable = { __index = metaAttr };
	setmetatable(attrTab, metaTable);
	
	return attrTab;
end

--Utilities functions---------------------------------------------------
function index2coord( idx, xMax ) 
	local term = math.floor( (idx-1)/xMax );
	local x = 1 + (idx-1) - xMax*term;
	local y = 1 + term;
	return x, y;
end

function coord2index( x, y, xMax )
	return (y - 1) * xMax + x ; 
end

-- Traverses the cellular space "cs" applying the "f(cell)" function
-- to each cell
function ForEachCell( cs, f)
	for i, cell in ipairs( cs.cells) do
		result = f(cell);
		if( result == nil) or (result) then else return false  end
	end
	return true;
end

-- Traverses the cellular spaces "cs1" and "cs2" applying the 
-- "f(cell1, cell2)" function to each correspondent cell pair.
-- "cell1" belongs to "cs1" and "cell2" belongs to "cs2".
-- The cellular spaces must have the same size.
function ForEachCellPair( cs1,cs2, f)
	for i, cell1 in ipairs( cs1.cells) do
		cell2 = cs2.cells[i];
		result = f(cell1, cell2);
		if( result == nil) or (result) then else return false  end
	end
	return true;
end

-- Transverse the neighborhood "index" from cell "cell" applying the
-- function "f( cell, neigh, weight )" to each neighbor
function ForEachNeighbor( cell, index, f )
	local neighborhood = cell:getNeighborhood(index);
	neighborhood:first();
	while( not neighborhood:last() )do
		neigh = neighborhood:getNeighbor();
		weight = neighborhood:getWeight();
		result = f( cell, neigh, weight );
		if( result == nil) or (result) then else return false  end
		neighborhood:next();
	end
	return true;
end

-- Creates a Moore neighborhood for each cell
function CreateMooreNeighborhood( cs )
	for i, cell in ipairs( cs.cells ) do
		local neigh = Neighborhood();
		local lin = -1; 
		while ( lin <= 1 ) do
			local col = -1;
			while ( col <= 1 ) do 
				-- add nieghbor
				local index = TeCoord{ x = (cell.x + col), y = (cell.y + lin)};
				neigh:addCell( index, cs, 1/9 ); -- wheight = 0.111111 (9 neighbors)
			
				col = col + 1;
			end
			lin = lin + 1;
		end
		cell:addNeighborhood( neigh );
	end
end

-- Implements the Euler (Euler-Cauchy) Method for integrate ordinary differential equations
-- Parameters: 
-- 	df, is the differential equantion, 
--  initCond, is the initial condition which must be satisfied
--	[a,b[, is a close defined interval,
-- 	 delta, is the step of the independent variable
function integrationEuler( df, initCond, a, b, delta )
	local i = 0;
	local y = initCond;
	local x = a;
	local bb = b - delta;
	for x = a, bb, delta do
		y = y + delta * df( x, y );		
	end
	return y;
end


-- Implements the Heun (Euler Second Order) Method for integrate ordinary differential equations
-- Method of type Predictor-Corrector
-- Parameters: 
-- 	df, is the differential equantion, 
--  initCond, is the initial condition which must be satisfied
--	[a,b[, is a close defined interval,
-- 	 delta, is the step of the independent variable
function integrationHeun( df, initCond, a, b, delta )
	local i = 0;
	local x = a;
	local y = initCond;
	local y1 = 0; 
	local val = 0;
	local bb = b - delta;
	for x = a, bb, delta do
		val = df( x, y );
		y1 = y + delta * val;		
		y = y + 0.5 * delta * (val + df( x + delta, y1 ));
	end
	return y;
end


-- Implements the Runge-Kutta Method (Forth Order) for integrate ordinary differential equations
-- Parameters: 
-- 	df, is the differential equantion, 
--  initCond, is the initial condition which must be satisfied
--	[a,b[, is a close defined interval,
-- 	 delta, is the step of the independent variable
function integrationRungeKutta( df, initCond, a, b, delta )
	local i = 0;
	local x = a;
	local y = initCond;
	local y1 = 0; 
	local y2 = 0;
	local y3 = 0;
	local y4 = 0;
	local bb = b - delta;
	local midDelta = 0.5 * delta;
	for x = a, bb, delta do
		y1 = df( x, y );		
		y2 = df( x + midDelta, y + midDelta * y1 );
		y3 = df( x + midDelta, y + midDelta * y2 );
		y4 = df( x + delta, y + delta* y3 ); 
		y = y + delta * (y1 + 2 * y2 + 2 * y3 + y4 )/6;
	end
	return y;
end

-- Global constante to define the used integration method & step size
INTEGRATION_METHOD = "Euler";
DELTA = 0.2;

-- Constructor for a ordinary differntial equantion
function d( attrTab ) 
	local result = 0;
	local delta  = DELTA;

	if( attrTab == nil ) then attrTab = {}; end

	local sizeAttrTab = table.getn( attrTab );
	if( sizeAttrTab < 4 ) then 
    	print("Error: bad arguments in diferential equation constructor \"d{arguments}\"");
    	print(" - the first attribute of a differential equantion must be a function which return a number,"); 
    	print(" - the second one must be the initial condition value,");
    	print(" - the third one must be the lower integration limit value,");
    	print(" - the forth one must be the upper integration limit value, and");
    	print(" - the fifth, OPTIONAL, must be the integration incretement value(default = "..DELTA.." ).");
    	io.flush();
    	os.exit();	  
    end
	if( sizeAttrTab  == 5 ) then 
		delta = attrTab[5];
	end
	if ( INTEGRATION_METHOD == "RugeKutta") then 
		result = integrationRungeKutta( attrTab[1], attrTab[2], attrTab[3],
										attrTab[4],	delta );
	else
		if ( INTEGRATION_METHOD == "Heum") then 
			result = integrationHeun( attrTab[1], attrTab[2], attrTab[3],
									  attrTab[4], delta );
		else
			result = integrationEuler(  attrTab[1], attrTab[2], attrTab[3],
										attrTab[4],	delta );
		end
	end
	
	return result;
end