##########################################################################################
# Interface class to communicate with BlueSpray
# Note: This new version does not work with Python 2.7
# Issues:
#  - What should be classes be?
#  - Should we wrap the java objects? Add overhead but could help with deleting rasters
#  - How much should we hide the java functions?  i.e. what should we provide Python code for
##########################################################################################

import os
import tkinter.messagebox
import tkinter # import the GUI package
from py4j.java_gateway import JavaGateway
from py4j.java_gateway import java_import

class STBlueSpray:
	@staticmethod
	def Initialize():
		"""Initializes the class.  Currently this is empty because the class has become static
		"""        
		x=12
		#if (BlueSpray is None):

			#gateway = JavaGateway(auto_field=True)

			#BlueSpray = gateway.jvm.com.SchoonerTurtles        

	#######################################################################################
	# Private File functions
	#######################################################################################
	@staticmethod
	def ErrorDialog(TheMessage,TheTitle="Error Occurred"):
		""" Displays an error dialog """
		# before showing the dialog box, we'll want to turn off the rest of the GUI
		root=tkinter.Tk() # get the root object of the GUI
		root.withdraw() # turn off the typical modeless window in the GUI

		tkinter.messagebox.showinfo(TheTitle,TheMessage) # show the dialog box   

	@staticmethod
	def DisplayException(TheException):
		""" Displays an error dialog based on a java exception """
		print("Sorry, an error has occurred:"+format(TheException))
		#exc_type, exc_value, exc_traceback =sys.exc_info()
		#print(exc_type)
		#print(exc_value)
		#traceback.print_tb(exc_traceback, limit=10)   

	#######################################################################################
	# Private File functions
	#######################################################################################
	@staticmethod
	def CheckIfFileExists(ThePath):
		Result=os.path.exists(ThePath)
		if (Result==False): STBlueSpray.ErrorDialog("Sorry, the file "+ThePath+" does not exist")
		return(Result)

	@staticmethod
	def CheckIfFileReadable(ThePath):
		Result=True
		if (STBlueSpray.CheckIfFileExists(ThePath)): # will generate an error if file does not exist
			if (os.access(ThePath, os.R_OK)==False):
				Result=False
				STBlueSpray.ErrorDialog("Sorry, the file "+ThePath+" is not currently readable")
		return(Result)

	#######################################################################################
	# File functions
	#######################################################################################
	#@staticmethod
	#def LoadRasterFromFile(ThePath):
		#""" Loads a raster from a file 
		#The file must contain a support raster type and be of a readable format
		#"""
		#STBlueSpray.Initialize()
		#if (STBlueSpray.CheckIfFileReadable(ThePath)):
		#try:
		#TheRaster=BlueSpray.Raster.STRaster() # Create the raster

		#BlueSpray.File.STFileManager.LoadFromFile(ThePath,TheRaster) # Load the raster from the file

		#except Exception as TheException: BlueSpray.DisplayException(TheException)

		#return(TheRaster)

	#@staticmethod
	#def LoadLayerFromFile(ThePath):
		#STBlueSpray.Initialize()

		#TheLayer=None

		#if (STBlueSpray.CheckIfFileReadable(ThePath)):

		#FileName, FileExtension = os.path.splitext(ThePath)
		#if (FileExtension==".shp"):
		#TheLayer=BlueSpray.Scene.STLayerRegions()

		#elif ((FileExtension==".tif") or (FileExtension==".img")):
		#TheLayer=BlueSpray.Scene.STLayerRaster()

		#if (TheLayer!=None):
		#BlueSpray.File.STFileManager.LoadFromFile(ThePath,TheLayer)

		#return(TheLayer)

	@staticmethod
	def LoadFromFile(FilePath):
		#TheLayer=BlueSpray.Scene.STLayer()

		TheFileHandler=BlueSpray.File.STFileManager.GetFileHandler(BlueSpray.Base.STIO.MODE.LOAD, FilePath, None);
		#TheFileHandler=BlueSpray.File.STFileManager.GetFileHandler(BlueSpray.Base.STIO.MODE.LOAD, FilePath, TheLayer.getClass());

		TheOutputClasses=TheFileHandler.GetOutputClasses(0);

		IsRaster=False
		IsPoly=False
		IsPoint=False
		Result=None
		TheObject=None
		for TheClass in TheOutputClasses:
			TheName=TheClass.getCanonicalName()

			if (TheName=="com.SchoonerTurtles.Raster.STRaster"): 
				IsRaster=True
				TheObject=BlueSpray.Raster.STRaster()
				Result=STRaster(TheObject)
			if (TheName=="com.SchoonerTurtles.Scene.STLayerRegions"): 
				IsPoly=True
				TheObject=BlueSpray.Scene.STLayerRegions()
				Result=STLayerVector(TheObject)
			if (TheName=="com.SchoonerTurtles.Scene.STLayerPoint"): 
				IsPoint=True
				TheObject=BlueSpray.Scene.STLayerPoint()
				Result=STLayerVector(TheObject)
			print(TheName)

		BlueSpray.File.STFileManager.LoadFromFile(FilePath,TheObject)

		return(Result)

	@staticmethod
	def SaveToFile(ThePath,TheContent):
		""" Saves the specified object to a file
		"""
		STBlueSpray.Initialize()

		#if (STBlueSpray.CheckIfFileExists(ThePath)):
		BlueSpray.File.STFileManager.SaveToFile(ThePath,TheContent)

	#######################################################################################
	# Raster functions
	#######################################################################################
	def CheckRaster(TheRaster):
		TheClass=TheRaster.getClass()
		TheClassName=TheClass.getName()
		if (TheClassName=="com.SchoonerTurtles.Scene.STLayerRaster"):
			TheRaster=TheRaster.GetRaster()
		return(TheRaster)

	@staticmethod
	def GetRasterInfo(TheRaster):
		TheRaster=CheckRaster(TheRaster)

		TheClass=TheRaster.getClass()

		Result={}
		Result['WidthInPixels']=TheRaster.GetWidthInPixels()
		Result['HeightInPixels']=TheRaster.GetHeightInPixels()

		TheSRS=TheRaster.GetRefSystem()	
		if (TheSRS!=None): Result['SRS']=TheSRS.toString()
		return(Result)

	#######################################################################################
	# Projection functions
	#######################################################################################
	@staticmethod
	def NewGoogleMercator():
		global BlueSpray
		TheSRS=BlueSpray.SRS.STSRS.NewGoogleMercator()
		return(TheSRS)

	@staticmethod
	def NewGeographic(TheGCS):
		global BlueSpray
		TheSRS=BlueSpray.SRS.STSRS.NewGeographic(TheGCS)
		return(TheSRS)

	@staticmethod
	def NewUTM(TheGCS,Zone,South=False):
		global BlueSpray
		TheSRS=BlueSpray.SRS.STSRS.NewUTM(TheGCS,Zone,South)
		return(TheSRS)

	@staticmethod
	def NewStatePlane(TheGCS,State,Zone):
		global BlueSpray
		TheSRS=BlueSpray.SRS.STSRS.NewUTM(TheGCS,State,Zone)
		return(TheSRS)

	@staticmethod
	def Project(TheContent,TheInputSRS=None,TheOutputSRS=None):
		global BlueSpray

		STBlueSpray.Initialize()

		Result=None

		TheProjectionManager=BlueSpray.SRS.STProjectorManager() # Create a new projection manager

		# setup the bounds finder as desired

		TheBoundsFinder=TheProjectionManager.GetProjectionBoundsFinder()
		TheBoundsFinder.SetAreaFactors(False,0.0,0.0)
		TheBoundsFinder.SetDistanceFactors(False,0.0,0.0)
		TheBoundsFinder.SetConformalFactors(False,0.0)

		if (TheInputSRS==None):
			TheInputSRS=TheContent.GetRefSystem()

		if (TheInputSRS==None): STBlueSpray.ErrorDialog("Sorry, either you must specify an 'InputSRS' or the content must have a defined SRS")
		else:
			if (type(TheContent) is list):
				Result=BlueSpray.SRS.STProjectorManager.ProjectCoordinate(TheContent[0],TheContent[1],TheInputSRS,TheOutputSRS)
			else:
				TheProjectionManager.SetSource(TheInputSRS)
				TheProjectionManager.SetDestin(TheOutputSRS)

				TheProjectionManager.SetInput(0,TheContent)

				TheProjectionManager.Process()

		return(Result)

	@staticmethod
	def ProjectToGoogleMercator(TheContent):
		STBlueSpray.Initialize()

		# Create the projector object from the GoogleMaps projector object
		TheOutputSRS=BlueSpray.SRS.STSRS.NewGoogleMercator()

		STBlueSpray.Project(TheContent,TheOutputSRS)

	#################################################################################################
	# Painting functions
	#################################################################################################
	@staticmethod
	def NewStyle(PenSize=1,PenColor="#000000",FillColor=None,FontSize=5):
		TheStyle=BlueSpray.Symbol.STStyle(PenSize,PenColor,FillColor)

		#def ToRaster(...):

	#################################################################################################
	#
	#################################################################################################

	@staticmethod
	def Tile(TheRaster,OutputFilePath,WidthInPixels=256,HeightInPixels=256,HighestResPixelWidthInMapUnits=-1, \
	         HighestResZoomLevel=-1,LowestResZoomLevel=-1,IgnoreEmptyTiles=True):
		""" Function to create tiles for display in a web browser
		"""
		Utility.Initialize()

		STTiler=BlueSpray.RasterTransform.STTiler

		TheTiler=STTiler()
		TheTiler.Mode=STTiler.TILE_PURPOSE.VIEWING

		TheTiler.SetTileWidthInPixels(WidthInPixels)
		TheTiler.SetTileHeightInPixels(HeightInPixels)

		TheBounds=TheRaster.GetBounds()
		XResolution=TheBounds.GetWidth()/TheRaster.GetWidthInPixels()

		if (HighestResPixelWidthInMapUnits==-1):
			HighestResPixelWidthInMapUnits=1
			if (XResolution>1):
				while (XResolution>HighestResPixelWidthInMapUnits):
					HighestResPixelWidthInMapUnits=HighestResPixelWidthInMapUnits*2
			else:
				while (XResolution<HighestResPixelWidthInMapUnits):
					HighestResPixelWidthInMapUnits=HighestResPixelWidthInMapUnits/2

		if (HighestResZoomLevel==-1):
			HighestResZoomLevel=STTiler.GetZoomLevelFromResolution(XResolution)

		if (LowestResZoomLevel==-1):
			LowestResZoomLevel=-18

		NumLevels=HighestResZoomLevel-LowestResZoomLevel+1;

		TheTiler.SetHighestResPixelWidthInMapUnits(float(HighestResPixelWidthInMapUnits))

		TheTiler.SetHighestResZoomLevel(HighestResZoomLevel)

		TheTiler.SetIgnoreEmptyTiles(IgnoreEmptyTiles)

		TheTiler.SetNumLevels(NumLevels)

		TheTiler.TileRaster(TheRaster,None,OutputFilePath)

#######################################################################################
# STBase Class
#######################################################################################
class STBase:
	""" Base class for all layer classes
	"""
	def __init__(self,TheObject):
		self.TheObject=TheObject

	def SaveToFile(self,ThePath):
		STBlueSpray.SaveToFile(ThePath,self.TheObject)

	# prjection functions

	def Project(self,TheOutputSRS=None,TheInputSRS=None):
		STBlueSpray.Project(self.TheObject,TheInputSRS,TheOutputSRS)
		#TheResult=STRaster(NewRaster)
		#return(TheResult)

#######################################################################################
# STRaster Class
#######################################################################################
class STRaster(STBase):
	""" Base class for all layer classes
	"""

	def GetInfo(self):
		""" Return information about the raster as a dictionary
		"""
		Result={}

		# Get the basic information

		NumBands=self.TheObject.GetNumBands()

		Result['DataType']=self.TheObject.GetDataType().toString()
		Result['HeightInPixels']=self.TheObject.GetHeightInPixels()
		Result['WidthInPixels']=self.TheObject.GetWidthInPixels()
		Result['HeightInPixels']=self.TheObject.GetHeightInPixels()
		Result['NumBands']=NumBands
		Result['SizeInBytes']=self.TheObject.GetSizeInBytes()

		# Get the SRS

		TheSRS=self.TheObject.GetRefSystem()	
		if (TheSRS!=None): Result['SRS']=TheSRS.toString()

		# Get the min/max values for each band

		MinMaxes=self.TheObject.GetMinMaxes()

		Count=0
		while (Count<NumBands):
			Result['MinValue_'+format(Count)]=MinMaxes[Count][0]
			Result['MaxValue_'+format(Count)]=MinMaxes[Count][1]
			Count=Count+1

		# Get the bounds

		TheBounds=self.TheObject.GetBounds()

		print("Bounds in reference units:")
		Result['MinX']=TheBounds.GetXMin()
		Result['MaxX']=TheBounds.GetXMax()
		Result['MinY']=TheBounds.GetYMin()
		Result['MaxY']=TheBounds.GetYMax()

		return(Result)

	# Data Type functions
	def ConvertToByte(self):
		NewRaster=BlueSpray.RasterTransform.STDataType.ConvertToByte(self.TheObject)
		return(STRaster(NewRaster))

	# Calculation functions

	def Compute(self,TheValue,Operation):
		""" Private function to perform all calculations with two operands
		""" 

		TheType=type(self)
		if (isinstance(TheValue,STRaster)): # Value is a raster
			TheOutput=BlueSpray.RasterTransform.STCalculator.Compute(self.TheObject,Operation,TheValue.TheObject);
		else: # Must be numeric
			TheOutput=BlueSpray.RasterTransform.STCalculator.Compute(self.TheObject,Operation,float(TheValue));

		Result=STRaster(TheOutput)

		return(Result)

	def Add(self,TheValue):
		""" Add the specified value to the raster's pixels  The value can be a number or another raster
		""" 
		Operation=BlueSpray.RasterTransform.STCalculator.FUNCTION.ADD

		Result=self.Compute(TheValue,Operation)

		return(Result)

	def Subtract(self,TheValue):
		""" Subtract the specified value from the raster's pixels.  The value can be a number or another raster
		""" 
		Operation=BlueSpray.RasterTransform.STCalculator.FUNCTION.SUBTRACT

		Result=self.Compute(TheValue,Operation)

		return(Result)

	def Multiply(self,TheValue):
		""" Multiply the specified value by the raster's pixels.  The value can be a number or another raster
		""" 
		Operation=BlueSpray.RasterTransform.STCalculator.FUNCTION.MULTIPLY

		Result=self.Compute(TheValue,Operation)

		return(Result)

	def Divide(self,TheValue):
		""" Divide the raster's pixels by the specified value.  The value can be a number or another raster
		""" 
		Operation=BlueSpray.RasterTransform.STCalculator.FUNCTION.DIVIDE

		Result=self.Compute(TheValue,Operation)

		return(Result)

	# Topography functions
	def Slope(self,ZFactor=1.0):
		""" Create a slope raster for the specified raster
		Z-Factor - factor to compentate for using different units for the vertical and horiziontal
		data (i.e. pixels vs. elevation).  Default is 1.0.
		""" 
		NewRaster=BlueSpray.RasterExtension.STTopography.Slope(self.TheObject,float(ZFactor))
		Result=STRaster(NewRaster)
		return(Result)

	def ConvertToRGB(self):
		""" Convert the raster from its current form to RGB.  Typicall the raster will
		be a grayscale or palette raster.
		""" 
		RGBRaster=BlueSpray.RasterTransform.STBandCopier.ConvertToRGB(self.TheObject)
		Result=STRaster(RGBRaster)
		return(Result)


#######################################################################################
# STLayerVector Class
#######################################################################################
class STLayerVector(STBase):
	""" Base class for all layer classes
	"""

	def GetNumFeatures(self):
		Result=self.TheObject.GetNumFeatures()
		return(Result)

	def GetNumColumns(self):
		TheTable=self.TheObject.GetTable()
		NumColumns=TheTable.GetNumColumns()
		return(NumColumns)

	def GetHeadings(self):
		TheTable=self.TheObject.GetTable()
		TheHeadings=TheTable.GetHeadings()
		return(TheHeadings)

	def GetColumn(self,Index):
		Value=None
		TheTable=self.TheObject.GetTable()
		TheColumn=TheTable.GetColumn(Index)

		if (TheColumn.GetType()==BlueSpray.Table.STColumn.COLUMN_TYPE.STRING):
			Value=TheColumn.GetStringArray()
		if (TheColumn.GetType()==BlueSpray.Table.STColumn.COLUMN_TYPE.DOUBLE):
			Value=TheColumn.GetDoubleArray()
		if (TheColumn.GetType()==BlueSpray.Table.STColumn.COLUMN_TYPE.INTEGER):
			Value=TheColumn.GetLongArray()
		if (TheColumn.GetType()==BlueSpray.Table.STColumn.COLUMN_TYPE.DATE):
			Value=TheColumn.GetDateArray()

		return(Value)

	# selections

	def SelectFeature(self,FeatureIndex,Toggle=False,Extend=False):
		self.TheObject.SetSelectedRow(FeatureIndex,Toggle,Extend) # RowIndex,Toggle,Extend

	def CreateCopyFromSelected(self):
		TheCanadaLayer=BlueSpray.Scene.STLayerRegions()
		TheCanadaLayer.CopySelected(self.TheObject)
		Result=STLayerVector(TheCanadaLayer)
		return(Result)

	# transforms

	def Buffer(self,Distance,QuadrantSegments=8,EndCapStyle=None):
		"""Initializes the class.  Currently this is empty because the class has become static
		""" 
		global BlueSpray

		TheOutputObject=BlueSpray.Scene.STLayerRegions()

		if (EndCapStyle==None): EndCapStyle=BlueSpray.VectorTransform.STBuffer.END_CAPS.ROUND;

		Distance=float(Distance)

		STBuffer=BlueSpray.VectorTransform.STBuffer;

		STBuffer.Buffer(self.TheObject,TheOutputObject,Distance,QuadrantSegments,EndCapStyle)

		TheOutputLayer=STLayerVector(TheOutputObject)

		#TheOutputLayer.FixProblems()

		return(TheOutputLayer)

	def Intersect(self,TheTarget):
		"""Perform an intersection operation
		""" 
		global BlueSpray

		TheOutputObject=BlueSpray.Scene.STLayerRegions()

		BlueSpray.VectorTransform.STOverlayLayers.Operate(
		    self.TheObject,-1,False,
		            TheTarget.TheObject,-1,False,
		                        BlueSpray.VectorTransform.STOverlayLayers.OVERLAY_OPERATION.INTERSECTION,TheOutputObject)

		TheOutputLayer=STLayerVector(TheOutputObject)

		return(TheOutputLayer)

	def FixProblems(self):
		BlueSpray.VectorTransform.STFixProblems.Fix(self.TheObject)

#######################################################################################
# Initialization code
#######################################################################################


# Setup the gateway to BlueSpray (remember that BlueSpray must be running)

gateway = JavaGateway(auto_field=True)

java=gateway.jvm

BlueSpray=None

try:
	BlueSpray = gateway.jvm.com.SchoonerTurtles
except:
	BlueSprayPy.ErrorDialog("Sorry, we cannot communicate with BlueSpray.  Is it up and running on this computer?")

#java_import(gateway.jvm,'com.SchoonerTurtles.RasterTransform.*')

java_import(gateway.jvm,'com.SchoonerTurtles.SRS.*')

#######################################################################################
# Enumerated classes replicating values from BlueSpray
#######################################################################################

class STGCS:
	""" Enumerated class for GCS definitions
	"""
	WGS_84=java.STGCS.WGS_84
	NAD_83=java.STGCS.NAD83    
	NAD_27=java.STGCS.NAD27
