'''
Name: avi_attribut_differences.py

Overview: Compares an AVI feature class against its modified version.

Description:
    This tool will compare the AVI attributes of an original AVI feature
    class against the attributes in the modified AVI feature class. It is
    intended to be used to validate AVI polygons that have been updated.

    The process will build a string containing all the AVI attributes for
    the original and modified feature class. These two strings are then
    compared to flag any differences between them. An intersected feature
    class is created containing these two strings. This intersected feature
    class is then processed to generate a report that will displays all
    attributes that have been modified.
    
Notes:

Author: Doug Crane
        Jan, 2010

Modifications:

'''

import pdb
import os
import sys
import datetime
import textwrap

import arcpy

# General SRD utilities that may be needed.
from srd.srd_exception import *
import srd.srd_logging as srd_logging
import srd.srd_misc as srd_misc
import srd.srd_temp_ws as srd_temp_ws
import srd.srd_recordset as srd_recordset

import srd.srd_featureclass_tools as srd_featureclass_tools

from avi_record import *

__author__ = 'Doug Crane'
__version__ = '1.0'

__all__ = ['AVI_AttributeDifferences']

# ----------------------------------------------------------
class AVI_AttributeDifferences(object):
    '''
    '''

    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    def __init__(self):
        '''
        Parameters:
        '''

        self._logChn = srd_logging.SRD_Log()

        self.srcAttFieldName = 'src_avi'
        self.newAttFieldName = 'new_avi'
        
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    def generateDifferences(self, srcAVI, newAVI, destAVI):
        '''
        Generates a feature class that indicates the differences in AVI
        attributes from the original AVI feature class and the modified
        AVI feature class. An ascii AVI record is generated for both
        the original and modified AVI records and added to the resulting
        intersected feaure class. This differences feature class can be
        used to generate the comparison report.

        Parameters:
            srcAVI - original AVI feature class

            newAVI - modified AVI feature class

            destAVI - differences feature class generated.
        '''

        if not arcpy.Exists(srcAVI):
            raise SRD_Exception('Source AVI: %s not found' % srcAVI)
        
        if not arcpy.Exists(newAVI):
            raise SRD_Exception('Comparison AVI: %s not found' % newAVI)

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

        # Require temporary workspace to perform processing.
        tmpPrefix = 'tmp_ws'
        tmpFGDB = srd_temp_ws.SRD_TempWS(tmpPrefix)

        # Make copies of our source data so we can manipulate the way we wish.
        tmpSrcFC = os.path.join(tmpFGDB.FGDBPath, 'tmpSrcFC')
        arcpy.CopyFeatures_management(srcAVI, tmpSrcFC)
        
        # Add an attribute key field for both the source and comparison datasets.
        srcAttFieldName = self.srcAttFieldName
        if not arcpy.ListFields(tmpSrcFC, srcAttFieldName):
            arcpy.AddField_management(tmpSrcFC, srcAttFieldName, 'TEXT', '', '', 160)

        self.addComparisonKey(tmpSrcFC, srcAttFieldName)

        tmpNewFC = os.path.join(tmpFGDB.FGDBPath, 'tmpNewFC')
        arcpy.CopyFeatures_management(newAVI, tmpNewFC)

        newAttFieldName = self.newAttFieldName
        if not arcpy.ListFields(tmpNewFC, newAttFieldName):
            arcpy.AddField_management(tmpNewFC, newAttFieldName, 'TEXT', '', '', 160)

        self.addComparisonKey(tmpNewFC, newAttFieldName)

        # Perform the intersection of the source and new AVI feature classes.
        # We only need the comparison keys so we will create layers
        # with these fields.
        fcObj = srd_featureclass_tools.SRD_FeatureClassTools()

        # Carry accross the poly_num for the source polygons so
        # a reference ID can be used in final report.
        fldList = ['poly_num', srcAttFieldName]
        feidlInfo = fcObj.getFieldInfoList(tmpSrcFC, fldList, False)
        srcFL = 'src_fl'
        arcpy.MakeFeatureLayer_management(tmpSrcFC, srcFL, '', '', feidlInfo)

        fldList = [newAttFieldName]
        feidlInfo = fcObj.getFieldInfoList(tmpNewFC, fldList, False)
        newFL = 'new_fl'
        arcpy.MakeFeatureLayer_management(tmpNewFC, newFL, '', '', feidlInfo)

        self._logChn.logMsg('\n*** Performing intersection to create: %s' % destAVI)
        intFCs = '%s ; %s' % (srcFL, newFL)
        arcpy.Intersect_analysis(intFCs, destAVI)
        
        # Clean-up
        tmpFGDB.deleteAllTempWS(tmpPrefix)

    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    def addComparisonKey(self, fcPath, keyFieldName):
        '''
        Adds a comparison key by generating an ascii record using the contents
        of the individual AVI fields.

        Parameters:
            fcPath - path to feature class to add compaison key.

            keyFieldName - name of field in fcPath that comparison
                           key will be assigned to.

        '''

        # Populate the attribute comparison fields with attribute contents.
        aviRec = AVI_Record(fcPath)

        # Build list of fields that will be used in comparison.
        aviFieldNamesList = aviRec.overstoryFieldNames[:]
        aviFieldNamesList += aviRec.understoryFieldNames[:]
        
        # Following is list of fields to exclude from comparison.
        excludeFieldNameList = ['initials', 'uinitials']

        # Make sure the required fields are contained in the feature
        # class we are processing.
        fcFieldNameList = [field.name.lower() for field in arcpy.ListFields(fcPath)]
        errList = []
        for fieldName in aviFieldNamesList:
            if not fieldName in excludeFieldNameList:
                if not fieldName.lower() in fcFieldNameList:
                    errList.append(fieldName)

        if errList:
            for err in errList:
                self._logChn.logMsg('  %s is missing' % err)
            raise SRD_Exception('\n*** Required AVI fields missing from: %s' % fcPath)
        
        # Obtain information needed to build comparison strings.
        fieldDisplayLenDict = AVI_RecordUtils.fieldDisplayLength()
        blankAVIRecDict = AVI_RecordUtils.blankAVIRecord()

        self._logChn.logMsg('\n*** Adding comparison key to: %s' % fcPath)

        rsObj = srd_recordset.SRD_Recordset(fcPath)
        
        selCnt = int(arcpy.GetCount_management(fcPath).getOutput(0))
        arcpy.SetProgressor('step', 'Building Attribute key...', 0, selCnt, 1)

        fieldList = aviFieldNamesList[:]
        fieldList.append(keyFieldName)
        keyFieldNameIdx = fieldList.index(keyFieldName)
        with arcpy.da.UpdateCursor(fcPath, fieldList) as cursor:
            for row in cursor:

                # Get a dictionary or values to enable easy access.
                aviRec.fieldValues = rsObj.getRecord(row, fieldList, replaceNulls=True, stripBlanks=True)
                attKey = aviRec.buildFormattedRecString(aviFieldNamesList, fieldDisplayLenDict, blankAVIRecDict, excludeFieldNameList)
                
                row[keyFieldNameIdx] = attKey
                cursor.updateRow(row)
                
                arcpy.SetProgressorPosition()
                
        arcpy.ResetProgressor()

    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    def generateComparisonReport(self, srcFC, newFC, diffFC, reportPath, pctAreaMin):
        '''
        Takes the feature class generated from the intersection of the
        source AVI and modified AVI and generates a report logging differences
        in the AVI attributes.

        Parameters:
            srcFC - path to the source AVI feature class.

            newFC - path to the modified AVI feature class

            diffFC - path to the differences feature class generated through
                     intersection of srcFC and newFC

            reportPath - path to report file to create.

            pctAreaMin - sets a minimum threshold for polygons that are
                         reported. A difference polygon must at least this
                         percentage a large as the original source polygon
                         to be reported. If this value is 0 then all polygons
                         are reported.
        '''

        if not arcpy.Exists(diffFC):
            raise SRD_Exception('Differences AVI feature class: %s not found' % diffFC)

        # Required fields.
        srcAttFieldName = self.srcAttFieldName
        if not arcpy.ListFields(diffFC, srcAttFieldName):
            raise SRD_Exception('Differences AVI feature class: %s does not contain required field: %s' % (diffFC, srcAttFieldName))

        newAttFieldName = self.newAttFieldName
        if not arcpy.ListFields(diffFC, newAttFieldName):
            raise SRD_Exception('Differences AVI feature class: %s does not contain required field: %s' % (diffFC, newAttFieldName))

        polyNumFieldName = 'poly_num'
        if not arcpy.ListFields(diffFC, polyNumFieldName):
            raise SRD_Exception('Differences AVI feature class: %s does not contain required field: %s' % (diffFC, polyNumFieldName))

        # If there is a pctAreaMin greater than 0 then must build
        # dictionary of original source data polygon area.
        polyAreaDict = {}
        if pctAreaMin > 0:
            selCnt = int(arcpy.GetCount_management(srcFC).getOutput(0))
            arcpy.SetProgressor('step', 'Building AREA dictionary ...', 0, selCnt, 1)

            with arcpy.da.SearchCursor(srcFC, (polyNumFieldName, 'SHAPE@AREA' )) as cursor:
                for row in cursor:
                    polyNum = row[0]
                    polyArea = row[1]
                    polyAreaDict[polyNum] = polyArea
                    arcpy.SetProgressorPosition()                    
            arcpy.ResetProgressor()
            
        areaFieldName = 'polyArea'
        diffFieldName = 'diff_rec'

        tmpPrefix = 'tmp_ws'
        tmpFGDB = srd_temp_ws.SRD_TempWS(tmpPrefix)

        # Save results to temporary table so we can process individual polygons.
        tmpTblName = 'tmp_tbl'
        tmpTblPath = os.path.join(tmpFGDB.FGDBPath, tmpTblName)
        arcpy.CreateTable_management(tmpFGDB.FGDBPath, tmpTblName)

        arcpy.AddField_management(tmpTblPath, polyNumFieldName, 'LONG')
        arcpy.AddField_management(tmpTblPath, areaFieldName, 'DOUBLE')
        arcpy.AddField_management(tmpTblPath, srcAttFieldName, 'TEXT', '', '', 140)
        arcpy.AddField_management(tmpTblPath, newAttFieldName, 'TEXT', '', '', 140)
        arcpy.AddField_management(tmpTblPath, diffFieldName, 'TEXT', '', '', 140)

        self._logChn.logMsg('\n*** Building comparison list')

        rsObj = srd_recordset.SRD_Recordset(diffFC)
        
        selCnt = int(arcpy.GetCount_management(diffFC).getOutput(0))
        arcpy.SetProgressor('step', 'Building comparison list ...', 0, selCnt, 1)

        insertFieldList = [polyNumFieldName, areaFieldName, srcAttFieldName, newAttFieldName, diffFieldName]
        insertCursor = arcpy.da.InsertCursor(tmpTblPath, insertFieldList)
                
        searchFieldList = [srcAttFieldName, newAttFieldName, polyNumFieldName, 'shape@area' ]
        with arcpy.da.SearchCursor(diffFC, searchFieldList) as cursor:
            for row in cursor:

                # Get a dictionary or values to enable easy access.
                recDict = rsObj.getRecord(row, searchFieldList, replaceNulls=False, stripBlanks=False)

                srcRec = recDict[srcAttFieldName]
                newRec = recDict[newAttFieldName]
                polyNum = recDict[polyNumFieldName]
                polyArea = recDict['shape@area']
                diffRec = self.getDifferences(srcRec, newRec)

                if diffRec.strip():
                    insertList = [polyNum, polyArea, srcRec, newRec, diffRec]
                    insertCursor.insertRow(insertList)

                arcpy.SetProgressorPosition()

        del insertCursor
        arcpy.ResetProgressor()

        reportChn = open(reportPath, 'w')
        reportChn.writelines('\nAVI Attribute Differences\n')
        reportChn.writelines('Original AVI: %s\n' % srcFC)
        reportChn.writelines('Modified AVI: %s\n\n' % newFC)
        reportChn.writelines('Date: %s\n' % datetime.date.today())
        reportChn.writelines('Audit Run By: %s\n\n' % os.environ.get('USERNAME'))
        reportChn.writelines('Min Percent Area of Original Polygon set to: %s\n' % pctAreaMin)
        reportChn.writelines('(A Difference polygon must be at least this percent of original polygon before it is reported) \n\n')
        
        reportChn.writelines('First line is original AVI\n')
        reportChn.writelines('Second line is differences\n')
        reportChn.writelines('Last line is new AVI\n')
        reportChn.writelines('Any field with . represents field not used in comparison\n')
        reportChn.writelines('\n---------------------------------------------\n')

        selCnt = int(arcpy.GetCount_management(tmpTblPath).getOutput(0))
        arcpy.SetProgressor('step', 'Building differences report ...', 0, selCnt, 1)

        # Open cursor sorted by polygon number and area.
        # Because of need for sort option use traditional cursor.
        sortFields = '%s A;%s D' % (polyNumFieldName,areaFieldName)
        rows = arcpy.SearchCursor(tmpTblPath, '', '', '', sortFields)
        row = rows.next()
        lastPolyNum = -1
        
        while row:

            polyNum = row.getValue(polyNumFieldName)
            polyArea = row.getValue(areaFieldName)
            srcRec = row.getValue(srcAttFieldName)
            newRec = row.getValue(newAttFieldName)
            diffRec = row.getValue(diffFieldName)

            # A minimum percent area may have been requested by user
            # This means if the difference polygon is below this percent
            # then it will not be part of report.
            if pctAreaMin > 0:
                if polyNum in polyAreaDict:
                    srcPolyArea = polyAreaDict[polyNum]
                    pctArea = (polyArea / srcPolyArea) * 100.0
                    if pctArea < pctAreaMin:
                        writeRec = False
                    else:
                        writeRec = True
                else:
                    writeRec = True
            else:
                writeRec = True

            if writeRec:
                if polyNum != lastPolyNum:
                    reportChn.writelines('----------------------------------------\n*** POLY_NUM: %s\n\n' % polyNum)
                    lastPolyNum = polyNum

                reportChn.writelines('  Area: %.2f sq m\n' % polyArea)
                reportChn.writelines('    %s\n' % srcRec)
                reportChn.writelines('    %s\n' % diffRec)
                reportChn.writelines('    %s\n\n' % newRec)
            
            row = rows.next()

            arcpy.SetProgressorPosition()

        arcpy.ResetProgressor()
        
        del row
        del rows

        reportChn.close()
        
        # Clean-up
        tmpFGDB.deleteAllTempWS(tmpPrefix)

    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    def getDifferences(self, srcRec, newRec):
        '''
        Compares 2 strings and return a string indicating the differences
        detected. A character by character comparison is made and where a
        difference is detected an * is placed in the results string. Only the
        number of characters in the srcRec are compared to the newRec.

        Parameters:
            srcRec - original AVI record.

            newRec - modified AVI record.

        Returns:
            diffRec - string with * character indicating location of
                      differences.
        '''

        # Compare each character of srcRec with newRec to find any differences.
        diffRec = ''
        for i in range(0, len(srcRec)):
            if srcRec[i] == newRec[i]:
                diffRec += ' '
            else:
                diffRec += '*'
                
        return diffRec
    
# ======================================================================
if __name__ == '__main__':
    

    srcFCArg = r'D:\testData\aviDifferences\srcAVI.gdb\AVI'
    newFCArg = r'D:\testData\aviDifferences\S10_Evaluation.gdb\AVI'

    ##srcFCArg = r'D:\testData\aviDifferences\srcAVI.gdb\srcTst1'
    ##newFCArg = r'D:\testData\aviDifferences\S10_Evaluation.gdb\diffTest1'

    destFCArg = r'D:\testData\aviDifferences\aviDiff.gdb\s10Diff'
    reportPathArg = r'D:\testData\aviDifferences\diff.log'
    
    aviDiffObj = AVI_AttributeDifferences()

    aviDiffObj.generateDifferences(srcFCArg, newFCArg, destFCArg)

    minAreaPctArg = 25
    aviDiffObj.generateComparisonReport(srcFCArg, newFCArg, destFCArg, reportPathArg, minAreaPctArg)
            

