
import fnmatch
import os
import re
import string
import sys
import time

from dirwalker import DirWalker

class DiffWalker(DirWalker):
    def __init__(self, filePatterns, opts, dict):
        self.filePatterns = filePatterns
        self.opts = opts
        self.dict = dict
        self.dictLC = {}
        for key in self.dict.keys():
            self.dictLC[string.lower(key)] = string.lower(self.dict[key][0])

        self.lineSkipREs = [
            # Skip Compatibility #define's
            ##re.compile(r'^\s*\#\s*define\s+(\w*)HX(\w*)\s+\1(PN|RN|RMA)\2\s*(//.*)?$'),
            ##re.compile(r'^\s*\#\s*define\s+(\w*)(PN|RN|RMA)(\w*)\s+\1HX\2\s*(//.*)?$'),
            re.compile(r'^\s*\#\s*define\s+(\w*)HX(\w*)\s+("?)\1(PN|RN|RMA)\w*\3\s*(//.*)?$'),
            re.compile(r'^\s*\#\s*define\s+(\w*)(PN|RN|RMA)(\w*)\s+("?)\1HX\w*\3\s*(//.*)?$'),

            # Entries in RCS keywords
            #   *Id: chxelst.h,v ... *   (replaced $ with * to avoid RCS/CVS)
            #   *Log: chxelst.h,v *
            #   *	rename: crmaelst.h -> chxelst.h
            re.compile(r'.*\$(Id|Log).*\$'),
            re.compile(r'^\s*(?://|\*|#+)\s*rename:\s*(\w*)(pn|rn|rma)(\w*)\.(\w*)\s*\-\>\s*\1hx\w*\.\4\s*$')
            ]

        self.defineRE = re.compile(r'^\s*\#\s*define\s+(\w+)\s+(\w+)\s*(?://.*)?$')

        self.numContext = 3
        if opts.has_key('context'): self.numContext = int(opts['context'])

        self.fileStarted = None

        if opts.has_key('word-chars'):
            self.wordRE = re.compile(r'(.*?)((?:%s)+)' % opts['word-chars'])
        else:
            self.wordRE = re.compile(r'(.*?)([\w]+)')

    def FinishBlock(self, f, blockStartLine, blockNumLines, block):
        ## print "# FinishBlock(%s, %d, %d, %s)" % (f, blockStartLine, blockNumLines, block)
        fname = os.path.join(self.subDir, f)
        fpath = os.path.join(self.baseDir, self.subDir, f)

        # If this is just "walking" a single file, use that file's name
        if len(self.baseFile) > 0:
            fname = self.baseFile
            fpath = self.baseFile
            
        if not self.fileStarted:
            self.fileStarted = f
            if self.opts.has_key('diff'):
                oldTime = time.ctime(os.path.getmtime(fpath))
                newTime = time.ctime(time.time())
                
                print "Index: %s" % fname
                print "==================================================================="
                print "--- %s\t%s" % (fname, oldTime)
                print "+++ %s\t%s" % (fname, newTime)

        if self.opts.has_key('diff'):
            print "@@ -%d,%d +%d,%d @@" % (blockStartLine, blockNumLines,
                                           blockStartLine, blockNumLines)

        pending = {'OLD': [], 'NEW': []}
        for line in block:
            m = re.match(r'\[([\-\+])(\d+)\](.*)', line, re.DOTALL)
            if m:
                if m.group(1) == '-': lineType = 'OLD'
                else: lineType = 'NEW'
                lineNum = m.group(2)

                if self.opts.has_key('concise'):
                    pending[lineType].append("%s:%s:%s:%s" % \
                                             (fname, lineNum, lineType,
                                              m.group(3)))
                else:
                    pending[lineType].append("%s%s" % \
                                             (m.group(1), m.group(3)))
            else:
                if len(pending['OLD']) > 0:
                    sys.stdout.writelines(pending['OLD'])
                    pending['OLD'] = []
                if len(pending['NEW']) > 0:
                    sys.stdout.writelines(pending['NEW'])
                    pending['NEW'] = []
                if self.opts.has_key('diff'):
                    sys.stdout.write(line)

        if len(pending['OLD']) > 0:
            sys.stdout.writelines(pending['OLD'])
            pending['OLD'] = []
        if len(pending['NEW']) > 0:
            sys.stdout.writelines(pending['NEW'])
            pending['NEW'] = []
            
        
    def DoFile(self, f):
        if self.opts.has_key('verbose'): print "# DoFile(%s)" % f
        bDoEdit = self.opts.has_key('do-edits')

        ctxLines = []
        outLines = []
        block = []
        blockStartLine = 0
        blockNumDiffs = 0
        fileNumBlocks = 0
        self.fileStarted = None
        
        inFname = os.path.join(self.baseDir, self.subDir, f)
        try:
            inFile = open(inFname)
        except IOError, e:
            print "# ERROR: File error on %s" % inFname
            return

        line = inFile.readline()
        lineNum = 1
        while line:
            # Check to see if we should skip filtering on this line based
            # on skipRE regexp matching.
            bDoFilter = 1
            for skipRE in self.lineSkipREs:
                if skipRE.match(line):
                    if self.opts.has_key('verbose'):
                        print "# skipRE(%s): %s" % (skipRE.pattern, line)
                    bDoFilter = None
                    break

            # Check to see if this line is define'ing an old name to a new
            # name (e.g. in a compatibility header file:
            #    #define CPNString CHXString
            # ).
            if bDoFilter:
                m = self.defineRE.match(line)
                if m:
                    if self.dictLC.has_key(string.lower(m.group(1))) and \
                       (self.dictLC[string.lower(m.group(1))] ==
                        string.lower(m.group(2))):
                        if self.opts.has_key('verbose'):
                            print "# skip: define old name(%s) to new name(%s)" % \
                                  (m.group(1), m.group(2))
                        bDoFilter = None

            # Check to see if this line is define'ing a new name to an old
            # name (e.g. in a compatibility header file:
            #    #define HXCREATEINSTANCE RMACreateInstance
            # ).
            if bDoFilter:
                m = self.defineRE.match(line)
                if m:
                    if self.dictLC.has_key(string.lower(m.group(2))) and \
                       (self.dictLC[string.lower(m.group(2))] ==
                        string.lower(m.group(1))):
                        if self.opts.has_key('verbose'):
                            print "# skip: define new name(%s) to old name(%s)" % \
                                  (m.group(1), m.group(2))
                        bDoFilter = None

            if bDoFilter:
                newLine = ''

                pos = 0
                m = self.wordRE.match(line)
                while m:
                    preMatchStr = m.group(1)
                    matchStr = m.group(2)
                    newLine = newLine + preMatchStr
                    if self.dict.has_key(matchStr):
                        newLine = newLine + self.dict[matchStr][0]
                    else: newLine = newLine + matchStr

                    pos = m.end()
                    rest = line[pos:]       # debugger help...
                    m = self.wordRE.match(line, pos)

                if pos < len(line):
                    newLine = newLine + line[pos:]
            else: newLine = line

            if self.opts.has_key('do-edits'): outLines.append(newLine)

            if line == newLine:
                diffLine = ' ' + newLine
                ctxLines.append(diffLine)
                if blockStartLine <= 0:
                    # Maintain rolling context list betw diff blocks
                    ctxLines = ctxLines[-self.numContext:]
                elif len(ctxLines) > (self.numContext * 2):
                    # We've passed enough lines to close off diff
                    # block. (NOTE: Each diff puts 2 entries in the block
                    # line list, so subtract num of diffs to get number of
                    # source file lines.)
                    self.FinishBlock(f, blockStartLine, len(block)-blockNumDiffs, block)
                    blockStartLine = 0
                    fileNumBlocks = fileNumBlocks + 1
                    ctxLines = ctxLines[-self.numContext:]
                elif len(ctxLines) <= self.numContext:
                    # Either in the middle of a diff block or accumulating
                    # context lines at the end - put the line in the block.
                    block.append(diffLine)
                else:
                    # Maybe overlapping diff blocks, so save extra lines
                    pass
            else:
                if blockStartLine <= 0:
                    # Start diff block
                    block = ctxLines
                    blockStartLine = lineNum - len(ctxLines)
                    blockNumDiffs = 0
                elif len(ctxLines) > self.numContext:
                    # We have some extra lines that were being held in
                    # case 2 diff blocks needed merging
                    block = block + ctxLines[self.numContext:]

                ctxLines = []

                blockNumDiffs = blockNumDiffs + 1
                block.append('[-%d]%s' % (lineNum, line))
                block.append('[+%d]%s' % (lineNum, newLine))
                
            line = inFile.readline()
            lineNum = lineNum + 1

        if blockStartLine > 0:
            self.FinishBlock(f, blockStartLine, len(block)-blockNumDiffs, block)
            fileNumBlocks = fileNumBlocks + 1

        inFile.close()

        if self.opts.has_key('do-edits') and fileNumBlocks > 0:
            outFname = '%s.new' % inFname
            try:
                outFile = open(outFname, "w")
                outFile.writelines(outLines)
                outFile.close()
            except IOError, e:
                print "# ERROR: File error on %s" % outFname
                return

            if self.opts.has_key('orig-suffix'):
                suffix = self.opts['orig-suffix']
                if len(suffix) == 0: suffix = '.orig'
                done = None
                origCounter = 0
                origFname = inFname + suffix
                while not done:
                    try:
                        if self.opts.has_key('verbose'):
                            print "# rename(%s,%s)" % (inFname, origFname)
                        os.rename(inFname, origFname)
                        done = 1
                    except:
                        if origCounter >= 999: # arbitrary limit on retries
                            print "# ERROR: Couldn't rename to %s" % origFname
                            break
                        else:
                            origCounter = origCounter + 1
                            origFname = '%s%s-%03d' % (inFname, suffix, origCounter)
                if done:
                    try:
                        if self.opts.has_key('verbose'):
                            print "# rename(%s,%s)" % (outFname, inFname)
                        os.rename(outFname, inFname)
                    except:
                        print "# ERROR: Couldn't rename %s to %s" % (outFname, inFname)
            else:
                if self.opts.has_key('verbose'):
                    print "# remove(%s)" % inFname
                os.remove(inFname)

                if self.opts.has_key('verbose'):
                    print "# rename(%s, %s)" % (outFname, inFname)
                os.rename(outFname, inFname)
                

    def Visit(self, f):
        if self.opts.has_key('verbose'): print "# Visit(%s)" % f
        for patt in self.filePatterns:
            if fnmatch.fnmatchcase(f, patt):
                self.DoFile(f)
                break
    
