'''
Name: srd_featureclass_tools.py

Overview: Provides methods and properties for working with feature class tables.

Description:
    
    
Notes:

Author: Doug Crane
        May, 2012

Modifications:

'''

__author__ = 'Doug Crane'
__version__ = '1.0'

import sys
import os
import pdb

import arcpy

from srd_exception import *
import srd_logging
    
__all__ = ['SRD_FeatureClassTools']


# ====================================================================
class SRD_FeatureClassTools(object):
    '''
    '''
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    def __init__(self):
        '''
        Initialize

        '''

        self._logChn = srd_logging.SRD_Log()
        
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    def addStaticJoin(self,
                      srcFC,
                      srcFieldName,
                      joinFC,
                      joinFieldName,
                      destFC):
        '''
        This will create a feature class by joining two other feature
        classes togeather. Unlike a normal join the joined fields in
        the destination feature class will be permanent. To distinguish
        the fields from the source feature class with those of the
        joined feature class each field is prefixed with the name of the
        feature class it came from. This is similar to a normal join where
        the table name is used to prefix each field so you can reference
        it in your code. Rather than using a . to seperate the table name
        from the feild name an _ is used in the static join. For instance
        if your joined table was named avi_att and the field was sp1 then
        the static joined field name would be avi_att_sp1.

        While a coverage and INFO table can be used to create a static
        joined feature class you cannot remove the join to obtain the same
        feature class you had prviously. ArcGIS appends its own prefix to
        the Shape, Length, and Area fields and these cannot be renamed
        when a remove static join is applied. You can use the static join
        with coverages but you may have difficulties if you wish to later
        remove the static join.

        Parameters:
            srcFC - path to the source feature class you wish to join to.

            srcFieldName - name of field in the srcFC that will be used
                           for the join.

            joinFC - path to fature class or table you wish to join to srcFC

            joinFieldName - name in joinFC that will be used to perform
                            the join.

            destFC - destination feature class that will become the static
                     join for the srcFC and joinFC.

        Examples:
        
        '''


        if not arcpy.Exists(srcFC):
            raise SRD_Exception('Source Feature Class: %s not found' % srcFC)

        if not arcpy.ListFields(srcFC, srcFieldName):
            raise SRD_Exception('Source Feature Class Join Field: %s not found in %s' % (srcFieldName, srcFC))

        if not arcpy.Exists(joinFC):
            raise SRD_Exception('Join Feature Class: %s not found' % joinFC)

        if not arcpy.ListFields(joinFC, joinFieldName):
            raise SRD_Exception('Join Feature Class Join Field: %s not found in %s' % (joinFieldName, joinFC))

        # Make sure we have a unique feature layer name.
        i = 1
        joinFL = 'join_fl'
        while arcpy.Exists(joinFL):
            i += 1
            joinFL = 'join_fl%s' % i

        arcpy.MakeFeatureLayer_management(srcFC, joinFL)
        arcpy.AddJoin_management(joinFL, srcFieldName, joinFC, joinFieldName)

        self._logChn.logMsg('\n*** Creating static join feature class')
        excludeFieldList = []
        self.copyFeatureClassSelectedFields(joinFL, destFC, excludeFieldList, True, True)

        # Clean-up
        if arcpy.Exists(joinFL):
            arcpy.Delete_management(joinFL)
        del joinFL

    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    def removeStaticJoin(self,
                         srcFC,
                         srcTableName, 
                         joinTableName,
                         destFC):
        '''
        This method will remove the static join that was previously set
        through the add static join. When a static join is removed all
        fields that are prefixed with the joinTableName are removed from
        the destination feature class. All fields that are prefixed with
        the srcTableName will have the prefix portion of the field name
        removed.

        While a coverage and INFO table can be used to create a static
        joined feature class you cannot remove the join to obtain the same
        feature class you had prviously. ArcGIS appends its own prefix to
        the Shape, Length, and Area fields and these cannot be renamed
        when a remove static join is applied. You can use the static join
        with coverages but you may have difficulties if you wish to later
        remove the static join.

        Parameters:
            srcFC - path to the source feature class you wish to remove join.

            srcTableName - table name of the srcFC that was prefixed to all
                           the source fields when the join was created. Any
                           attribute that is prefixed with this name will be
                           present in the destFC

            joinTableName - table name of the joined feature class that was prefixed to all
                            the joined fields when the join was created. Any
                            attribute that is prefixed with this name will be
                            dropped from the destFC

            destFC - destination feature class that will be created when
                     the join is removed.

        Examples:

        
        '''
        
        # When a static join is created the name of the table
        # is prefixed to each field. We will remove this prefix
        # when we remove the join.
        srcPrefix = '%s_' % srcTableName.upper()
        srcPrefixLen = len(srcPrefix)
        
        joinPrefix = '%s_' % joinTableName.upper()

        # Define empty string that will hold list of field
        # info translations.
        fieldInfo = ''

        fieldList = [field.name.upper() for field in arcpy.ListFields(srcFC)]
        
        for fieldName in fieldList:
            # If it is from the source feature class then we will
            # just drop the table name that was added when the static
            # join was created.
            if fieldName.startswith(srcPrefix):
                # Strip-off table name prefix.
                outFieldName = fieldName[srcPrefixLen:]
                fldMap = '%s %s %s' % (fieldName, outFieldName, 'VISIBLE')
            elif fieldName.startswith(joinPrefix):
                fldMap = '%s %s %s' % (fieldName, fieldName, 'HIDDEN')
            else:
                # If we cannot determine then just pass it through.
                fldMap = '%s %s %s' % (fieldName, fieldName, 'VISIBLE')
                
            if fieldInfo == '':
                fieldInfo = fldMap
            else:
                fieldInfo = fieldInfo + ';' + fldMap

        # Make sure we have a unique feature layer name.
        i = 1
        srcFL = 'src_fl'
        while arcpy.Exists(srcFL):
            i += 1
            srcFL = 'src_fl%s' % i

        # Define a feature layer with our field information.
        arcpy.MakeFeatureLayer_management(srcFC, srcFL, "", "", fieldInfo)

        if arcpy.Exists(destFC):
            arcpy.Delete_management(destFC)

        self._logChn.logMsg('\n*** Removing static join...')
        # Create destination feature class with desired structure.
        arcpy.CopyFeatures_management(srcFL, destFC)
        
        # Clean-up
        if arcpy.Exists(srcFL):
            arcpy.Delete_management(srcFL)
        del srcFL

    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    def copyFeatureClassGeometry(self,
                                 srcFC,
                                 destFC):
        '''
        Copies the geometry of a feature class to another feature class. No
        attributes will be copied from the source feature class.

        Parameters:
            srcFC - source feature class to copy

            destFC - path to feature class to create.
        '''

        fieldInfoArg = self.getFieldInfoList(srcFC, [], [], False, True)

        srcFL = 'copyFeatureClassGeometry_fl'
        arcpy.MakeFeatureLayer_management(srcFC, srcFL, '', '', fieldInfoArg)

        # Create destination feature class with desired structure.
        if arcpy.Exists(destFC):
            arcpy.Delete_management(destFC)
            
        arcpy.CopyFeatures_management(srcFL, destFC)

        # Clean-up
        if arcpy.Exists(srcFL):
            arcpy.Delete_management(srcFL)
        del srcFL
        
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    def copyFeatureClassSelectedFields(self,
                                       srcFC,
                                       destFC,
                                       fieldNameList,
                                       excludeFields = False,
                                       useTableName = False):
        '''
        Copies a feature class but only carries across a selected
        set of fields. The fields are indicated by the fieldNameList
        which needs to be a list of existing field names. This list
        can be either the list of fields to copy to the new feature
        class or the list of fields to exclude from the new feature
        class. The excludeField argument controls whether the fields
        will be used for inclusion or exclusion. By default this value
        is False which means the list will be set of fields to include
        in the new feature class.

        Note that you cannot copy across the OBJECTID field. If you wish
        to have access to this field create a new field defined as a LONG
        and calculate it to the OBJECTID prior to performing an export.

        Parameters:

            srcFC - source feature class to copy from

            destFC - destination feature class to copy to

            fieldNameList - a list of fields either to exclude or to include.

            excludeFields - if set to True the the field names in fieldNameList will
                      be excluded from the destination feature class. If set to
                      False (default) the the list of fields will be the only
                      ones that will be copied accros.

            useTableName - If set to True then the table name will be used
                           as part of the final field name. The field name
                           will be prefixed with the table name then seperated
                           with an _ from rest of field name. For example,
                           c4.sp1 would become c4_sp1.

        Example:

            srcFCArg = r'D:\testData\aviTest.gdb\avi_py'
            destFCArg = r'D:\testData\aviTest.gdb\avi_py_2'

            fcObj = UtilFeatureClassTools(GP)
            # Set of fields to copy across to new feature class.
            fieldNameListArg = ['poly_num', 'sp1', 'moist_reg']
            fcObj.copyFeatureClassSelectedFields(srcFCArg, destFCArg, fieldNameListArg)

            # --------------------------------------------------------------
            # In this example we will join the AVI attributes from an INFO
            # table to the polygons and create an FGDB feature class that
            # contains all the features and attributes togeather.
            aviFC = r'D:\testData\c4_avi\polygon'
            aviATT = r'D:\testData\c4_avi.att'
            destFCArg = r'D:\testData\aviTest.gdb\avi_c4'

            # Join the polygons to the ATT.            
            aviFL = 'avi_fl'
            arcpy.MakeFeatureLayer_management(aviFC, aviFL)
            arcpy.AddJoin_management(aviFL, 'POLY_NUM', aviATT, 'POLY_NUM')

            fcObj = UtilFeatureClassTools(GP)
            # We pass an empty field name list to the routine so that all
            # fields will be copied to the destination feature class.
            excludeFieldList = []
            fcObj.copyFeatureClassSelectedFields(aviFL, destFCArg, excludeFieldList, True)
        
        '''

        fieldInfoArg = self.getFieldInfoList(srcFC, fieldNameList, excludeFields, useTableName)

        # Create a feature layer with the field definitions that will
        # determine the destination feature class.
        srcFL = 'copyFeatureClass_fl'
        arcpy.MakeFeatureLayer_management(srcFC, srcFL, '', '', fieldInfoArg)

        # Create destination feature class with desired structure.
        arcpy.CopyFeatures_management(srcFL, destFC)

        # Clean-up
        if arcpy.Exists(srcFL):
            arcpy.Delete_management(srcFL)
        del srcFL
        
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    def exportAttributes(self,
                         fcPath,
                         attPath,
                         fieldNameList = [],
                         excludeFields = False):
        '''
        Exports the attributes of a feature class to a stand-alone
        table that does not contain any geometry. This can be used
        when you wish to split the attributes from the geometry and
        use a join when you wish to access the attributes and features
        togeather.

        Note that you cannot copy across the OBJECTID field. If you wish
        to have access to this field create a new field defined as a LONG
        and calculate it to the OBJECTID prior to performing an export.
        
        Parameters:
            fcPath - path to feature class you wish to export attributes for.

            attPath - path to table to create.

            fieldNameList - optional list of field names that will be used
                            to generate the attribute table. These names can
                            be either fields to copy or fields to exclude
                            depending on what you set for the excludeFields
                            argument.

            excludeFields - if set to True the the field names in fieldNameList will
                      be excluded from the destination table. If set to
                      False (default) the the list of fields will be the only
                      ones that will be copied across.
                            
        Example:

            fcObj = UtilFeatureClassTools(GP)
            srcFCArg = r'D:\testData\aviTest.gdb\avi_py'
            destTableArg = r'D:\testData\aviTest.gdb\avi_py_att'

            # List of fields to copy.
            fieldNameListArg = ['poly_num', 'sp1', 'moist_reg']
            fcObj.exportAttributes(srcFCArg, destTableArg, fieldNameListArg, False)
        
        '''

        fieldInfoArg = self.getFieldInfoList(fcPath, fieldNameList, excludeFields)

        # Create table view using selected attributes.
        tableViewName = 'tbl_view'
        arcpy.MakeTableView_management(fcPath, tableViewName, '', '', fieldInfoArg)

        # Copy using selected attributes.
        arcpy.CopyRows_management(tableViewName, attPath)

    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    def getFieldInfoList(self,
                         fcPath,
                         fieldNameList,
                         excludeFields,
                         useTableName = False,
                         hideAll = False):
        ''' 
        Build the field info list based on set of field names. If
        the excludeFields argument is True then the fieldNameList
        represents the set of fields to exclude and their visibility
        will be set to HIDDEN. Otherwise, the fieldNameList if the list
        of fields to include and their visibility will be set to VISIBLE.

        Because this routine will also be used with feature layers that
        are joined to another table there is the potential that there will
        be duplicated fields once the table identifier is dropped. In these
        cases any duplicated field will be given a numeric suffix to make
        it unique. For instance, if you have a field named SPECIES in the
        source and joined tables then the second one will be named SPECIES2.

        A field info list can be used to define a feature layer from a
        feature class where only certain fields from the feature class are
        visible in the feature layer. You can then copy this feature layer
        to another feature class and only those fields that are visible will
        show up in the created feature class.

        Returns the field information list which is string containing
        the name, new name, and visibility of the fields. Each set of
        field information is seperated by a semicolon. In the case of
        a joined table the name will include the table name while the
        new name will be just the name. Example, if the name of the field
        is c4_att.sp1 the field info would be 'c4_att.sp1 sp1 VISIBLE'
        
        Parameters:

            fcPath - path to feature class you wish to process.

            fieldNameList - list of field names that will be used
                            to generate the field info list from. These names can
                            be either fields to copy or fields to exclude
                            depending on what you set for the excludeFields
                            argument.

            excludeFields - if set to True the the field names in fieldNameList will
                             be HIDDEN from the destination table. If set to
                             False the the list of fields will be VISIBLE

            useTableName - If set to True then the table name will be used
                           as part of the final field name. The field name
                           will be prefixed with the table name then seperated
                           with an _ from rest of field name. For example,
                           c4.sp1 would become c4_sp1.

            hideAll - determines whether all the fields will be set to
                      HIDDEN. This will be used when just the geometry
                      is being copied.
        Example:

            srcFCArg = r'D:\testData\aviTest.gdb\avi_py'

            fcObj = UtilFeatureClassTools(GP)
            # Set of fields to make Visible.
            fieldNameListArg = ['poly_num', 'sp1', 'moist_reg']
            feidlInfo = fcObj.getFieldInfoList(srcFCArg, fieldNameListArg, False)
        
        '''

        # Make sure all the field names are in uppercase.
        fieldNames = []
        for fieldName in fieldNameList:
            fieldNames.append(fieldName.upper())

        # Define empty string that will hold list of field
        # info translations.
        fieldInfo = ''

        # Maintain list of field names that we already processed
        # so we do not get a duplicated field name.
        processedFieldNamesList = []

        fieldList = arcpy.ListFields(fcPath)

        for fld in fieldList:

            # Do not include the shape or OID fields as part of the field info list.
            if not fld.type.upper() in ('GEOMETRY', 'OID', 'BLOB'):
                fieldName = fld.name.upper()
                
                # With a coverage or INFO table the seperator between
                # the table and field name will be a ':' rather than a
                # '.' Must be able to handle both situations.
                if ':' in fieldName:
                    if useTableName:
                        baseFieldName = '%s_%s' % (fieldName.split(':')[0], fieldName.split(':')[1])
                    else:
                        baseFieldName = fieldName.split(':')[1]
                else:
                    if '.' in fieldName:
                        if useTableName:
                            baseFieldName = '%s_%s' % (fieldName.split('.')[0], fieldName.split('.')[1])
                        else:
                            baseFieldName = fieldName.split('.')[1]
                    else:
                        baseFieldName = fieldName

                # If user passed list of field names then determine
                # their visibility based on the exludeFields argument.
                if fieldNames:
                    if excludeFields:
                        if baseFieldName in fieldNames:
                            fieldVisibility = 'HIDDEN'
                        else:
                            fieldVisibility = 'VISIBLE'
                    else:
                        if baseFieldName in fieldNames:
                            fieldVisibility = 'VISIBLE'
                        else:
                            fieldVisibility = 'HIDDEN'

                else:
                    fieldVisibility = 'VISIBLE'
                                        
                # In case of duplicated fields we will generate a
                # unique name for the field by adding a number to
                # the end of the base field name. We only need to
                # do this if the field is going to show up in the
                # finale dataset.
                if fieldVisibility == 'VISIBLE':
                    i = 1
                    fieldNameModified = False
                    while baseFieldName in processedFieldNamesList:
                        i += 1
                        baseFieldName = '%s%s' % (baseFieldName, i)
                        fieldNameModified = True

                    if fieldNameModified:                    
                        # We need to also check this against the new name in case
                        # user wishes to drop the duplicated field. This is normally
                        # the case when considering a JOIN field used to join features
                        # to attributes.
                        if fieldNames:
                            if excludeFields:
                                if baseFieldName in fieldNames:
                                    fieldVisibility = 'HIDDEN'
                                else:
                                    fieldVisibility = 'VISIBLE'
                            else:
                                if baseFieldName in fieldNames:
                                    fieldVisibility = 'VISIBLE'
                                else:
                                    fieldVisibility = 'HIDDEN'

                        else:
                            fieldVisibility = 'VISIBLE'

                # Override if all fields are to be hidden.
                if hideAll:
                    fieldVisibility = 'HIDDEN'
                    
                processedFieldNamesList.append(baseFieldName)
                
                fldMap = '%s %s %s' % (fieldName, baseFieldName, fieldVisibility)
                if fieldInfo == '':
                    fieldInfo = fldMap
                else:
                    fieldInfo = fieldInfo + ';' + fldMap
        
        return fieldInfo

# --------------------------------------------------------------------
def testJoinCopy():


    tmpDissolveFC = r'D:\testData\AVI\AVI_REC.gdb\tmpDissolve'
    attArg = r'D:\testData\AVI\AVI_REC.gdb\tmpAtt'
    outFC = r'D:\testData\AVI\AVI_REC.gdb\tmpWithAtt'
    
    srcFL = 'joinAttribues_fl'
    arcpy.MakeFeatureLayer_management(tmpDissolveFC, srcFL)
    arcpy.AddJoin_management(srcFL, 'att_key_id', attArg, 'att_key_id')

    # We pass an empty field name list to the routine so that all
    # fields will be copied to the destination feature class. 
    excludeFieldList = []
    myObj.copyFeatureClassSelectedFields(srcFL, outFC, excludeFieldList, True)
    

# --------------------------------------------------------------------
if __name__ == '__main__':

    srcFC = r'D:\testData\AVI\AVI_REC.gdb\avi_src'
    outFC = r'D:\testData\AVI\AVI_REC.gdb\avi_out'

    arcpy.env.overwriteOutput = True
    
    arcpy.CopyFeatures_management(srcFC, outFC)
    
    myObj = SRD_FeatureClassTools()

    fieldNameListArg = ('poly_num', 'sp1', 'sp1_per')
    newFC = r'D:\testData\AVI\AVI_REC.gdb\avi_new'
    ##myObj.copyFeatureClassSelectedFields(outFC, newFC, fieldNameListArg)
    ##myObj.copyFeatureClassSelectedFields(outFC, newFC, fieldNameListArg, excludeFields=True)

    ##myObj.exportAttributes(outFC, newFC, fieldNameListArg)
    ##myObj.exportAttributes(outFC, newFC, fieldNameListArg, excludeFields=True)

    ##myObj.copyFeatureClassGeometry(outFC, newFC)

    srcFC = r'D:\testData\AVI\AVI_REC.gdb\sj_src'
    joinFC = r'D:\testData\AVI\AVI_REC.gdb\sj_atts'
    destFC = r'D:\testData\AVI\AVI_REC.gdb\sj_out'
    noJoinFC = r'D:\testData\AVI\AVI_REC.gdb\sj_no_join'
             
            
    ##myObj.addStaticJoin(srcFC, 'poly_num', joinFC, 'poly_num', destFC)

    ##myObj.removeStaticJoin(destFC, 'sj_src', 'sj_atts', noJoinFC)
    
    testJoinCopy()
