# -*- coding: utf8 -*-
'''
BASIC MATRIX CLASSES
***********************************
*** 
*** BASIC MATRIX CLASSES
*** 
*** written by Markus Doering
***    BGBM, Berlin, 2003
*** 
***********************************
$RCSfile: matrix.py,v $
$Revision: 875 $
$Author: markus $
$Date: 2008-04-24 02:23:32 +0200 (Do, 24. Apr 2008) $

Module with a general basic matrix class.
'''


from string import join
import logging
log = logging.getLogger("pywrapper.graph.matrix")


# ERROR CLASSES
# ----------------------------------------------------------
class RowNotExistingError(Exception):
    """Exception raised when trying to reference a non existing row of the matrix."""
    def __init__(self):
        Exception.__init__(self)
class ColNotExistingError(Exception):
    """Exception raised when trying to reference a non existing column of the matrix."""
    def __init__(self):
        Exception.__init__(self)
class RowAlreadyExistingError(Exception):
    """Exception raised when trying to add an already existing row to the matrix."""
    def __init__(self):
        Exception.__init__(self)
class ColAlreadyExistingError(Exception):
    """Exception raised when trying to add an already existing column to the matrix."""
    def __init__(self):
        Exception.__init__(self)




# MATRIX CLASSES    
# ----------------------------------------------------------
class matrix:
    '''a general basic matrix'''
    def __init__(self, rows, cols):
        '''pass a list for rows and columns to create the matrix. Empty lists are allowed. All entries will be set to None.'''
        self.matrix = {}
        self._cols = {}
        for x in cols:
            self._cols[x] = None
        for x in rows:
            self.matrix[x] = self._cols.copy()
    
    def set(self, (n,m), x):
        '''sets one entry x in the matrix at tuple (n,m)'''
        if self.matrix.has_key(n):
            if self.matrix[n].has_key(m):
                self.matrix[n][m] = x
            else:
                raise ColNotExistingError()
        else:
            raise RowNotExistingError()
    
    def get(self, (n,m)):
        '''returns the entry of the matrix at tuple (n,m)'''
        return (self.matrix.get(n, None)).get(m, None)
        
    def getRow(self, col):
        '''returns the list of rows for column "col".'''
        try:
            cols = {}
            for row, rowDict in self.matrix.items():
                cols[row]=rowDict[col]
            return cols
        except:
            raise ColNotExistingError()
    
    def getCol(self, row):
        '''returns the list of columns for row "row" as a dictionary.'''
        try:
            return self.matrix[row]
        except:
            raise RowNotExistingError()

    def size(self):
        '''returns the size of the matrix as a tuple (n,m)'''
        return (len(self.matrix), len(self._cols))
        
    def listRows(self):
        '''returns the list of rows.'''
        return self.matrix.keys()
    
    def listCols(self):
        '''returns the list of columns.'''
        return self._cols.keys()
        
    def addRow(self, row):
        '''adds a new, empty row to the matrix.'''
        if self.matrix.has_key(row):
            raise RowAlreadyExistingError()
        else:
            self.matrix[row] = self._cols.copy()
    
    def addCol(self, col):
        '''adds a new, empty column to the matrix.'''
        if self._cols.has_key(col):
            raise ColAlreadyExistingError()
        else:
            self._cols[col] = None
            for row in self.matrix.keys():
                self.matrix[row][col] = None

    def delRow(self, row):
        '''removes a row of the matrix.'''
        if self.matrix.has_key(row):
            del self.matrix[row]
        else:
            raise RowNotExistingError()
    
    def delCol(self, col):
        '''removes a column of the matrix.'''
        if self._cols.has_key(col):
            del self._cols[col]
            for row in self.matrix.values():
                del row[col]
        else:
            raise ColNotExistingError()
            
    def __repr__(self):
        '''build matrix string.'''
        # get largest col and row entry.
        maxLength = max([len(val) for val in self.listCols()])
        for row in self.listRows():
            maxLength = max( max( [len(str(self.get( (row,col) ))) for col in self.listCols()] ), maxLength)
        # print per row:
        result = []
        # body
        for col in self.listCols():
            rows  = [ ("%"+str(maxLength)+"s") %(str(col)) ]
            rows += [ ("%"+str(maxLength)+"s") %( str(self.get( (row,col) )) ) for row in self.listRows() ]
            result.append( join(rows, '|') )
        # build result string
        rstring =     "SIZE: %s\n" %(str(self.size() ))
        # header
        rstring += join( [ ("%"+str(maxLength)+"s")%(val) for val in ['---']+self.listCols() ], "|")
        rstring += "\n"
        # body
        rstring += join(result, "\n")
        return rstring
        
            
class squareMatrix(matrix):
    '''matrix with n=m'''
    def __init__(self,n):
        matrix.__init__(self,n,n)

    def addRow(self, row):
        matrix.addRow(self, row)
        matrix.addCol(self, row)
    
    def addCol(self, col):
        matrix.addRow(self, row)
        matrix.addCol(self, row)

    def delRow(self, row):
        matrix.delRow(self, row)
        matrix.delCol(self, row)
    
    def delCol(self, col):
        matrix.delRow(self, row)
        matrix.delCol(self, row)


class symmetricMatrix(squareMatrix):
    '''Half of a square matrix. values for (x,y) and (y,x) are alqays the same.'''
    def __init__(self, n):
        squareMatrix.__init__(self,n)
    
    def set(self, (n,m), x):
        squareMatrix.set(self,(n,m), x)
        squareMatrix.set(self,(m,n), x)

