#ifndef LATTICEENVIRONMENT_H
#define LATTICEENVIRONMENT_H

#include <vector>
#include <map>
#include <fstream>
#include <string>


#include "definition.h"
#include "ParameterManager.h"
#include "LatticeSite.h"
#include "Matrix.h"
#include "Point.h"
#include "functionLib.h"

extern int xDim, yDim, zDim, numLatticeSites, numEnvironments;

extern double TIMESCALE, SPACESCALE;

#define	LATTICE_XPLANE	0
#define LATTICE_YPLANE	1
#define LATTICE_ZPLANE	2
#define LATTICE_SOLID	3
#define LATTICE_HOLLOW	4
#define LATTICE_POINT	5

struct LatticeMacroFeature {
	int	featureType;
	float	x1, y1, z1, x2, y2, z2;
	int	envType;
	LatticeMacroFeature * next;
};

class LatticeEnvironment{

	// Static members
	
	// Does an Environment for the Lattice already exist
	static bool isInstantiated;
	// Pointer to the Environment for the Lattice
	static LatticeEnvironment * lEnv;
	

	public:
	
		// Get the Pointer to the Lattice Environment
		static LatticeEnvironment * getLatticeEnv();
	
		// Destructor for the Lattice Environment
		~LatticeEnvironment();

		// Check whether the molecule is compatible in the environment
		bool isCompatibleEnvironment(moltype, envtype);
		
		// Get the environment at the specified location
		int getEnvironment(Point &);

		// Display the lattice Environment
		void displayLatticeEnv();

		void diffuse();
		
		// Get the LatticeSite at specified location
		LatticeSite * getLatticeSite(Point &);
		
		// Get the list of available sites corresponding to the provided environment
		vector <Point *> * getAvailableSites(envtype);
		
		void reactWithMetabolite(Point &, int, int, int, float, float, float, float);

		Matrix * getStateMatrix();
		
		Matrix * getStatisticsMatrix();

		LatticeMacroFeature * getLatticeMacroFeatures();

		void displayState();
		
		Tuple * getCurStat();

		int firstEnv (moltype);

		void pumpInMetabolite(float amount_to_pump_in);
		void pumpOutMetabolite(float amount_to_pump_out);
	private:

		// Member Variables

		// The 3D Lattice Space (Pointers to Lattice Sites)
		LatticeSite ****arr;
		
		// Compatible Table (Molecule -> compatible envs)
		map <moltype, vector <envtype> > compatibleTable;
		
		// Compatible Table (Molecule -> compatible envs)
		map <moltype, vector <Point *> > compatiblePointsTable;
		
		// Environment Table contain all locations (env -> locations)
		map <envtype, vector <Point *> > envTable;


		///////////////////////////////////////////////////////////
		// Diffusion

		Matrix * stateMatrix;

		Matrix * updateMatrix;
		
		Matrix * statisticsMatrix;

		Tuple *** diffusionRateTable;

		Tuple ** reactionRateTable;
		Tuple ** equilibriumConstantTable;

		LatticeMacroFeature * latticeMacroFeatures;

		void updateStateMatrix();

		void populateStateMatrix();
		
		void displayStateMatrix();

		vector<LatticeSite *> getNeighbors(int);

		void buildNeighborList();

		void buildDiffusionRateTable();

		void buildReactionRateTable();

		void buildDiffusionPatterns();

		///////////////////////////////////////////////////////////


		// member Operations

		// Constructor for the Lattice Environment
		LatticeEnvironment();

		// Build a 3D array of lattice sites
		void buildLatticeSpace();

		// Initialize the Lattice Environment
		void buildLatticeEnvironment();

		void buildNucleus();

		// Called from buildLatticeEnvironment, initializes the inaccessible space
		void buildInaccessibleSpace();

		// Called from buildLatticeEnvironment, initializes the microtubules
		void buildMicrotubules(bool *** accessible_nucleus_lattice, int max_nucleus_to_block);

		int buildRadialCylinder(Point cell_membrane_end,
					double endpoint_distance_from_center,
					int radius, int environment, int max_cells_to_block,
					bool *** accessible_nucleus_lattice, int * max_nucleus_to_block_ptr);

		int buildCylinder(Point end1, Point end2,
				  int radius, int environment, int max_cells_to_block,
				  bool *** accessible_nucleus_lattice, int * max_nucleus_to_block_ptr);

		int buildCylinder_or_check(Point end1, Point end2,
					   int radius, int environment, int max_cells_to_block,
					   bool build, bool *** accessible_nucleus_lattice, int * max_nucleus_to_block_ptr);

		void buildPlasmaMembrane();

		Point pickRandomPointOnNucleus(double nucleus_radius);
		Point pickRandomPointOnCellMembrane();

		void countAndReportAccessibleNucleus();

		int countAndRecordAccessibleNucleus(Point * accessible_nucleus_points, bool *** accessible_nucleus_lattice);
		
		// Table of Lattice sites corresponding to different environments
		void buildEnvironmentTable();
		
		// Table of compatible environments corresponding to different molecules
		void buildMoleculeCompatibleEnvTable();
		
		// Determine whether the provided Point (location) is contained within the lattice dimensions
		bool withinCell(Point &);
		bool withinCell(int, int, int);
}
;

#endif
