"""A class to build directory diff tools on."""

import os

import dircache
import cmpcache
import statcache
from stat import *

class dircmp:
    """Directory comparison class."""

    def new(self, a, b):
        """Initialize."""
        self.a = a
        self.b = b
        # Properties that caller may change before calling self.run():
        self.hide = [os.curdir, os.pardir] # Names never to be shown
        self.ignore = ['RCS', 'tags'] # Names ignored in comparison

        return self

    def run(self):
        """Compare everything except common subdirectories."""
        self.a_list = filter(dircache.listdir(self.a), self.hide)
        self.b_list = filter(dircache.listdir(self.b), self.hide)
        self.a_list.sort()
        self.b_list.sort()
        self.phase1()
        self.phase2()
        self.phase3()

    def phase1(self):
        """Compute common names."""
        self.a_only = []
        self.common = []
        for x in self.a_list:
            if x in self.b_list:
                self.common.append(x)
            else:
                self.a_only.append(x)

        self.b_only = []
        for x in self.b_list:
            if x not in self.common:
                self.b_only.append(x)

    def phase2(self):
        """Distinguish files, directories, funnies."""
        self.common_dirs = []
        self.common_files = []
        self.common_funny = []

        for x in self.common:
            a_path = os.path.join(self.a, x)
            b_path = os.path.join(self.b, x)

            ok = 1
            try:
                a_stat = statcache.stat(a_path)
            except os.error, why:
                # print 'Can\'t stat', a_path, ':', why[1]
                ok = 0
            try:
                b_stat = statcache.stat(b_path)
            except os.error, why:
                # print 'Can\'t stat', b_path, ':', why[1]
                ok = 0

            if ok:
                a_type = S_IFMT(a_stat[ST_MODE])
                b_type = S_IFMT(b_stat[ST_MODE])
                if a_type <> b_type:
                    self.common_funny.append(x)
                elif S_ISDIR(a_type):
                    self.common_dirs.append(x)
                elif S_ISREG(a_type):
                    self.common_files.append(x)
                else:
                    self.common_funny.append(x)
            else:
                self.common_funny.append(x)

    def phase3(self):
        """Find out differences between common files."""
        xx = cmpfiles(self.a, self.b, self.common_files)
        self.same_files, self.diff_files, self.funny_files = xx

    def phase4(self):
        """Find out differences between common subdirectories.
        A new dircmp object is created for each common subdirectory,
        these are stored in a dictionary indexed by filename.
        The hide and ignore properties are inherited from the parent."""
        self.subdirs = {}
        for x in self.common_dirs:
            a_x = os.path.join(self.a, x)
            b_x = os.path.join(self.b, x)
            self.subdirs[x] = newdd = dircmp().new(a_x, b_x)
            newdd.hide = self.hide
            newdd.ignore = self.ignore
            newdd.run()

    def phase4_closure(self):
        """Recursively call phase4() on subdirectories."""
        self.phase4()
        for x in self.subdirs.keys():
            self.subdirs[x].phase4_closure()

    def report(self):
        """Print a report on the differences between a and b."""
        # Assume that phases 1 to 3 have been executed
        # Output format is purposely lousy
        print 'diff', self.a, self.b
        if self.a_only:
            print 'Only in', self.a, ':', self.a_only
        if self.b_only:
            print 'Only in', self.b, ':', self.b_only
        if self.same_files:
            print 'Identical files :', self.same_files
        if self.diff_files:
            print 'Differing files :', self.diff_files
        if self.funny_files:
            print 'Trouble with common files :', self.funny_files
        if self.common_dirs:
            print 'Common subdirectories :', self.common_dirs
        if self.common_funny:
            print 'Common funny cases :', self.common_funny

    def report_closure(self):
        """Print reports on self and on subdirs.
        If phase 4 hasn't been done, no subdir reports are printed."""
        self.report()
        try:
            x = self.subdirs
        except AttributeError:
            return # No subdirectories computed
        for x in self.subdirs.keys():
            print
            self.subdirs[x].report_closure()

    def report_phase4_closure(self):
        """Report and do phase 4 recursively."""
        self.report()
        self.phase4()
        for x in self.subdirs.keys():
            print
            self.subdirs[x].report_phase4_closure()


def cmpfiles(a, b, common):
    """Compare common files in two directories.
    Return:
        - files that compare equal
        - files that compare different
        - funny cases (can't stat etc.)"""

    res = ([], [], [])
    for x in common:
        res[cmp(os.path.join(a, x), os.path.join(b, x))].append(x)
    return res


def cmp(a, b):
    """Compare two files.
    Return:
        0 for equal
        1 for different
        2 for funny cases (can't stat, etc.)"""

    try:
        if cmpcache.cmp(a, b): return 0
        return 1
    except os.error:
        return 2


def filter(list, skip):
    """Return a copy with items that occur in skip removed."""

    result = []
    for item in list:
        if item not in skip: result.append(item)
    return result


def demo():
    """Demonstration and testing."""

    import sys
    import getopt
    options, args = getopt.getopt(sys.argv[1:], 'r')
    if len(args) <> 2: raise getopt.error, 'need exactly two args'
    dd = dircmp().new(args[0], args[1])
    dd.run()
    if ('-r', '') in options:
        dd.report_phase4_closure()
    else:
        dd.report()

if __name__ == "__main__":
    demo()
