class sdo_geometry(object):
    '''
    Process an Oracle SDO_GEOMETRY object as returned by cx_Oracle.
    '''
    # definitions
    geom_types = {'00': 'UNKNOWN_GEOMETRY', # UNKNOWN_GEOMETRY
                  '01': 'POINT', # POINT
                  '02': 'LINESTRING', # LINE or CURVE
                  '03': 'POLYGON', # POLYGON
                  '04': 'GEOMETRYCOLLECTION', # COLLECTION
                  '05': 'MULTIPOINT', # MULTIPOINT
                  '06': 'MULTILINESTRING', # MULTILINE or MULTICURVE
                  '07': 'MULTIPOLYGON'} # MULTIPOLYGON
    
    # SDO_ETYPES
    # first element of triplet in SDO_ELEM_INFO
    sdo_etype = {0: 'UNSUPPORTED_GEOMETRY_ETYPE',
                 1: 'POINT_ETYPE',
                 2: 'LINE_ETYPE', 
                 4: 'COMPOUND_LINESTRING_ETYPE', 
                 1003: 'EXTERIOR_CLOSED_SHAPE_ETYPE', 
                 2003: 'INTERIOR_CLOSED_SHAPE_ETYPE',
                 1005: 'COMPOUND_EXTERIOR_CLOSED_SHAPE_ETYPE', 
                 2005: 'COMPOUND_INTERIOR_CLOSED_SHAPE_ETYPE'}
    
    # SDO_INTERPRETATIONS
    # second element of triplet in SDO_ELEM_INFO
    # applies to points - sdo_etype 1
    sdo_interpretation_point = {0: 'ORIENTED_POINT', 
                                1: 'SIMPLE_POINT'
                                # n > 1: point cluster with n points
                                }
    
    # applies to lines - sdo_etype 2
    sdo_interpretation_line = {1: 'STRAIGHT_SEGMENTS', 
                               2: 'CURVED_SEGMENTS'}
    
    # applies to polygons - sdo_etypes 1003 and 2003            
    sdo_interpretation_multi = {1: 'SIMPLE_POLY', 
                                2: 'ARCS_POLY', 
                                3: 'RECTANGLE', 
                                4: 'CIRCLE'} 
    
    # complex geometries - sdo_etypes 4, 1005, 2005 always have n > 1
    # n is the number of contiguous subelements
    # subsequent subelements each define one element
    
    
        
    # init function
    def __init__(self, sdo_geometry_obj=None, debug=False, strict=False):
        '''
        Read the geometry from the sdo_geometry object.
        Debug - produce some debug output.
        Strict - if False (default), convert geometry to a supported type where possible,
        e.g. Oriented Point to Point
        '''
        # read the geometry from the sdo_geometry object
        self.geometry = sdo_geometry_obj
        try:
            self.g_type = str(int(self.geometry.__getattribute__('SDO_GTYPE')))
            self.g_srid = int(self.geometry.__getattribute__('SDO_SRID'))
            self.g_point = [self.geometry.__getattribute__('SDO_POINT').X, self.geometry.__getattribute__('SDO_POINT').Y, self.geometry.__getattribute__('SDO_POINT').Z]
            self.g_eleminfo_arr = self.geometry.__getattribute__('SDO_ELEM_INFO')
            self.g_ords_arr = self.geometry.__getattribute__('SDO_ORDINATES')
        except AttributeError:
            if debug:
                print 'Not a geometry'
            return None
        self.dims = self.get_dims()
        self.topology = self.has_topology()
        self.gtype = self.get_gtype()
        #self.wkb = self.get_wkb()
        self.valid = False
        self.wkt = self.get_wkt()
        #self.coord_dim = self.st_coorddim()
        #self.is_valid = self.st_isvalid()
    
    # functions
    def get_dims(self):
        '''
        Return dimensions of the geometry.
        This is extracted from the first character of the SDO_ETYPE value
        '''
        return int(self.g_type[0])
    
    def st_coorddim(self):
        '''
        Return dimensions of the geometry.
        This is extracted from the first character of the SDO_ETYPE value
        This function is a synonym of get_dims
        '''
        return self.get_dims()
    
    def has_topology(self):
        '''
        Return true if the geometry has topology, false if it doesn't, otherwise None
        This is extracted from the second character of the SDO_ETYPE value
        '''
        if 0 <= int(self.g_type[1]) <= 1:
            return int(self.g_type[1])
        else:
            return None
            
    def get_geometry_text(self):
        '''
        Return the type of geometry.
        This is extracted from the third and fourth characters of the SDO_ETYPE value
        '''
        return self.geom_types[self.g_type[2:4]]
        
    def get_gtype(self):
        '''
        Return the type of geometry.
        This is extracted from the third and fourth characters of the SDO_ETYPE value
        '''
        return int(self.g_type[2:4])
               
    def get_srid(self):
        '''
        Return the srid of the data.
        This is as defined in the database and may be an Oracle specific format (not EPSG).
        '''
        return self.g_srid    
    
    def get_num_elements(self):
        '''
        Return the total number of elements in the SDO_ORDINATES array.
        These may be used more than once (end and start adjacent elements).
        '''
        if self.g_eleminfo_arr:
            return len(self.g_eleminfo_arr)
        else:
            return None
            
    def get_etype(self):
        '''
        Return the SDO_ETYPE value, if it is defined.
        '''
        if not self.g_eleminfo_arr:
            return None
        else:
            return int(self.g_eleminfo_arr[1])

    def get_interpretation(self):
        '''
        Return the SDO_INTERPRETATION value, if it is defined.
        '''
        if not self.g_eleminfo_arr:
            return None
        else:
            return int(self.g_eleminfo_arr[2])
            
    def get_point_text(self, point):
        '''
        Convert a point (2d or 3d list) into WKT text.
        '''
        if self.dims == 2:
            return '%.12f %.12f' % (point[0], point[1])
        else:
            return '%.12f %.12f %.12f' % (point[0], point[1], point[2])
            
    def to_points(self, l,n):
        '''
        Convert a list l into a list of smaller lists of dimension n.
        '''
        return [l[i:i+n] for i in xrange(0, len(l), n)]
        
    def is_valid(self):
        '''
        Return True for valid geometry, False for invalid geometry, None for unsupported
        '''
        # Place holder for now.
        return None

    def get_wkt(self):
        '''
        Calculate the WKB for the geometry.
        Point geometry may require only SDO_POINT, all other geometries require the use of SDO_ELEM_INFO and SDO_ORDINATES.
        Geometry may be simple or complex.  Simple geometries are defined in one SDO_ELEM_INFO triplet, Complex geometries require multiple SDO_ELEM_INFO triplets.
        '''
        geom_type = self.get_geometry_text()
        if geom_type == 'UNKNOWN_GEOMETRY':
            return None
        
        elif geom_type == 'POINT':
            if self.g_point:
                #return 'SRID=%d:%s(%s)' % (get_srid(g_srid), geom_types[geometry_type], get_point_text(g_point, dims))
                point_text = '%s(%s)' % (geom_type, self.get_point_text(self.g_point))
                self.valid = True
                return 'SRID=%d:%s' % (self.get_srid(), point_text)
            else:
                pass # need to extract point from sdo_ordinates
                # case 1 - simple point
                # case 2 - oriented point
        
        elif geom_type == 'LINESTRING':
            # simple element, with a single SDO_ELEM_INFO triplet.
            # each point is listed sequentially in the SDO_ORDINATES
            # direct conversion to WKT
            
            # sanity check - may need to expand
            if self.get_etype() != 2 or len(self.g_eleminfo_arr) != 3:
                self.valid = False
                return None
            # straight segments
            if self.get_interpretation() == 1 or (self.get_interpretation() == 2 and strict == False):
                points = self.to_points(self.g_ords_arr, self.dims)
                ls_body = ''
                for point in points:
                    ls_body += self.get_point_text(point)+','
                ls_body = ls_body[:-1]
                ls_text = '%s(%s)' % (geom_type, ls_body)
                self.valid = True
                return 'SRID=%d:%s' % (self.get_srid(), ls_text)
            # curved segments
            elif self.get_interpretation() == 2 and strict == True:
                # to do
                return None
            
        elif geom_type == 'POLYGON':
            # simple element, with a single SDO_ELEM_INFO triplet.
            # the triple can have the following values
            # [0] - 1003 or 2003 (SDO_ETYPE)
            # [1] - 1, 2, 3, or 4 (SDO_INTERPRETATION)
            # [2] - 1
            # each point is listed sequentially in the SDO_ORDINATES
            # last point is the same as first point
            # direct conversion to WKT
            
            # sanity check - may need to expand
            if (self.get_etype() != 1003 and self.get_etype() != 2003) or len(self.g_eleminfo_arr) != 3:
                self.valid = False
                return None
            # straight segments
            if self.get_interpretation() == 1 or (self.get_interpretation() < 4 and strict == False):
                points = self.to_points(self.g_ords_arr, self.dims)
                poly_body = ''
                for point in points:
                    poly_body += self.get_point_text(point)+','
                poly_body = poly_body[:-1]
                poly_text = '%s(%s)' % (geom_type, poly_body)
                self.valid = True
                return 'SRID=%d:%s' % (self.get_srid(), poly_text)
            # curved segments
            #elif self.get_interpretation() == 2 and strict == True:
                # to do
            #    return None 
            
        elif geom_type == 'GEOMETRYCOLLECTION':
            pass 
            
        elif geom_type == 'MULTIPOINT':
            pass 
            
        elif geom_type == 'MULTILINESTRING':
            pass 
            
        elif geom_type == 'MULTIPOLYGON':
            pass 
        else:
            return None
    
