r"""
modul configdiff - provides config files-difference tools.

It contains class ConfigDiff.
"""

__all__=['ConfigDiff']

import re, misc

class ConfigDiff:
    r"""Class, which provides comparation of config files.

    Each file is read line by line and compared, but the line order doesn't matter.

    Output format is a ndiff format. Same lines begin with two spaces.
    Added lines begin with character + and spaces and deleted lines begin
    with character - and space.
    """
    def __init__(self, file1_name='',file2_name='',param=None):
        r"""Constructs ConfigDiff instance.

        Opens files `file1_name` and `file2_name` and reads lines from them.
        File1 is loaded to list of lines, file2 is loaded to dictionary of lines.

        parameters:
        file1_name - name of the first file
        file2_name - name of the second file
        param - options from optparse

        instance variables:
        self.file1_name - name of the first file
        self.file2_name - name of the second file
        self.file1 - file descriptor of the first file
        self.file2 - file descriptor of the second file
        self.lines1 - list of lines of the first file
        self.lines2 - dictionary of lines of the second file
        """
        self.file1_name=file1_name  #from file name
        self.file2_name=file2_name  #to file name
        self.param=param            #options from command line

        self.file1 = misc.open_file(self.file1_name)  #open files
        self.file2 = misc.open_file(self.file2_name)

        self.lines1 = self.__read_file_to_list(self.file1)  #read files
        self.lines2 = self.__read_file_to_dict(self.file2)

        self.file1.close()
        self.file2.close()


    def __read_file_to_dict(self,file):
        r"""
        Reads lines from `file` and returns dictionary content lines.

        File is read by lines. Comments (lines started with '#')
        and blank lines are ignored. In regular lines, white spaces are ignored.

        Function returns a dictionary. Key is a string - line without white spaces
        and value is a tuple (count,line_number,line). Count is a number, how many
        times the line is in a file.
        """
        lines={}
        n = 1
        for line in file:
            if line[0] == '#' or line[0] == '\n':
                n += 1
                continue

            if line in lines:
                lines[line][0] += 1
            else:
                k=re.sub(r'[ \t]+',r' ',line)
                if self.param.ignore_case:
                    k=k.lower()

                lines[k] = (1,n,line)
            n += 1

        return lines

    def __read_file_to_list(self,file):
        r"""
        Reads lines from `file` and returns list content lines.

        File is read by lines. Comments (lines started with '#')
        and blank lines are ignored. In regular lines, white spaces are ignored.

        Function returns a list. Value is a tuple (line_number,line).
        """
        lines=[]
        n=1
        for line in file:
            if line[0] == '#' or line[0] == '\n':
                n += 1
                continue

            lines.append((n,line))
            n += 1

        return lines

    def config_diff(self):
        r"""
        Compares two config files.

        We go through list of lines of file1 and for each line we find out, if line
        from file1 is in dictionary of file2 lines. We make three groups of
        lines: equal, inserted and deleted.

        In the end we return lines in ndiff output format.
        """
        equal=[]
        inserted=[]
        deleted=[]

        #goes through list of lines
        for line in self.lines1:
            line1_key = re.sub(r'[ \t]+',r' ',line[1])

            if self.param.ignore_case:
                line1_key=line1_key.lower()

            #line in file1 is also in file2
            if line1_key in self.lines2:
                equal.append((line[0],line[1]))
                if self.lines2[line1_key][0] > 1:
                    self.lines2[line1_key][0] -= 1
                else:
                    del self.lines2[line1_key]
            #line in file1 is not in file2
            else:
                deleted.append((line[0],line[1]))

        #remaining lines are inserted
        for k, v in self.lines2.items():
            inserted.append((v[1],v[2]))

        inserted.sort()

        #lines are returned in ndiff output format
        type=0  #0-started and Equal, 1-Inserted, 2-Deleted
        for list in equal,inserted,deleted:
            if type == 0:
                for n,line in list:
                    yield '  {0:<3} {1}'.format(n,line)
            elif type == 1:
                for n,line in list:
                    yield '+ {0:<3} {1}'.format(n,line)
            elif type == 2:
                for n,line in list:
                    yield '- {0:<3} {1}'.format(n,line)
            type +=1






