cdef class Matrix:
    # .................................
    cdef:
        object id
        object murge 
        object format 
        object n
        object dof 
        object edgenbr        
        object Initialized
        object OP 
        object OP2
        object SYM
        object MODE
        object localnnz
        object comm
    # .................................

    def __cinit__(self, murge, n, id=None, format='csr', sym=False, dof=1, base=1,
                 verbose=False, matrix_verification=True, epsilon=1.e-7,
                  op=None, op2=None, mode=None, localnnz=0, comm=None):
        global _nmatrices
        
        self.Initialized = False 
        self.id         = id 
        self.murge      = murge
        self.format     = str(format).upper()
        self.n          = n
        self.dof        = dof
        self.localnnz   = localnnz
        self.comm       = comm

        if id is None:
            print "nmatrices : ", _nmatrices
            self.id = _nmatrices
            _nmatrices += 1
            return

        if sym:
            self.SYM    = MURGE.Boolean.TRUE
        else:
            self.SYM    = MURGE.Boolean.FALSE

        if op is not None:
            self.OP     = op
        else:
            self.OP     = MURGE.AssemblyOp.OVW

        if op2 is not None:
            self.OP2    = op2
        else:
            self.OP2    = MURGE.AssemblyOp.OVW

        if mode is not None:
            self.MODE   = mode
        else:
            self.MODE   = MURGE.AssemblyMode.FOOL

        # ...
        self.murge.SetDefaultOptions(self.id, 0)
        # ...

        # ...
        self.set_verbose(verbose)
        self.set_matrix_verification(matrix_verification)
        self.set_dof(dof)
        self.set_sym(sym)
        self.set_base(base)

        self.set_epsilon(epsilon)
        # ...

        self.Initialized = True

    def __dealloc__(self):
        if self.Initialized:
            MURGE_Clean(self.id)
            self.Initialized = False 

    property id:
        def __get__(self):
            return self.id

    def set_dof(self, value):
        self.murge.SetOptionInt(self.id, MURGE.IParam.DOF, value)

    def set_base(self, value):
        self.murge.SetOptionInt(self.id, MURGE.IParam.BASEVAL, value)

    def set_epsilon(self, value):
        self.murge.SetOptionReal(self.id, MURGE.RParam.EPSILON_ERROR, value)

    def set_sym(self, flag):
        if flag:
            self.murge.SetOptionInt(self.id,
                                    MURGE.IParam.SYM,
                                    MURGE.Boolean.TRUE)
        else:
            self.murge.SetOptionInt(self.id,
                                    MURGE.IParam.SYM,
                                    MURGE.Boolean.FALSE)

    def set_verbose(self, flag):
        try:
            import pypastix
            if flag:
                self.murge.SetOptionInt(self.id, PASTIX.API.IPARM_VERBOSE,
                                        PASTIX.API.API_VERBOSE_YES)
            else:
                self.murge.SetOptionInt(self.id, PASTIX.API.IPARM_VERBOSE,
                                        PASTIX.API.API_VERBOSE_NOT)
        except Exception:
            print "could not set verbosity"

    def set_matrix_verification(self, flag):
        try:
            import pypastix
            if flag:
                self.murge.SetOptionInt(self.id, PASTIX.API.IPARM_MATRIX_VERIFICATION,
                                        PASTIX.API.API_YES)
            else:
                self.murge.SetOptionInt(self.id, PASTIX.API.IPARM_MATRIX_VERIFICATION,
                                        PASTIX.API.API_NO)
        except Exception:
            print "could not set matrix verification"

    def set_localnnz(self, nnz):
        self.localnnz = nnz

    def get_localnnz(self):
        return self.localnnz

    property localnnz:
        def __get__(self):
            return self.get_localnnz()
        def __set__(self, value):
            self.set_localnnz(value)

    def GraphBegin(self, edgenbr, rank=0):
        if rank == 0:
            self.edgenbr = edgenbr
            self.murge.GraphBegin(self.id, self.n, edgenbr)
        else:
            self.murge.GraphBegin(self.id, self.n, 0)

    def GraphEdge(self, i, j, rank=0):
        if rank == 0:
            self.murge.GraphEdge(self.id, i, j)
        else:
            pass

    def GraphEnd(self):
        self.murge.GraphEnd(self.id)

    def GraphGlobal(self, indptr=None, indices=None, rows=None, cols=None,
                    format=None, root=0):
        """
        indptr : of size = nR+1
        indices : of size = nnz
        """
        if format is not None:
            self.format     = str(format).upper()

        if self.format == 'CSR':
            self.murge.GraphGlobalCSR(self.id, indptr, indices, root=root)

        if self.format == 'CSC':
            self.murge.GraphGlobalCSC(self.id, indptr, indices, root=root)

        if self.format == 'IJV':
            self.murge.GraphGlobalIJV(self.id, self.n, rows, cols, root=root)

    def GetLocalNodeList(self):
        localnodenbr = np.zeros(1, dtype=np.int32)
        self.murge.GetLocalNodeNbr(self.id, localnodenbr)
        localnodenbr = localnodenbr[0]

        nodelist = np.zeros(localnodenbr, dtype=np.int32)
        self.murge.GetLocalNodeList(self.id, nodelist)

        return nodelist

    def AssemblyBegin(self, coefnbr=None, op=None, op2=None, mode=None):
        if op is not None:
            self.OP = op
        if op2 is not None:
            self.OP2 = op2
        if mode is not None:
            self.MODE = mode
        if coefnbr is not None:
            self.localnnz = coefnbr

        self.murge.AssemblyBegin(self.id, self.n, self.localnnz, self.OP, self.OP2,
                                 self.MODE, self.SYM)

    def AssemblySetValue(self, row, col, value):
        self.murge.AssemblySetValue(self.id, row, col, value)

    def AssemblySetNodeValues(self, row, col, values):
        self.murge.AssemblySetNodeValues(self.id, row, col, values)

    def AssemblySetBlockValues(self, rows, cols, values):
        self.murge.AssemblySetBlockValues(self.id, rows, cols, values)

    def AssemblyEnd(self):
        self.murge.AssemblyEnd(self.id)

    def Assembly(self, GetCoef, list_ineighbors=None):
        if list_ineighbors is None:
            raise("Assembly must be called with list_ineighbors")

        # Get Local nodes
        nodelist = self.GetLocalNodeList()

        self.AssemblyBegin()

        for i in nodelist:
            list_j = list_ineighbors[i-1]
            for j in list_j:
                val = GetCoef(i,j)
                self.AssemblySetValue(i, j, val)

        self.AssemblyEnd()

    def AssemblyRHS(self, GetRhs, local=False, mpi_type=None, mpi_op=None, root=0):
        from mpi4py import MPI
        if mpi_type is None:
            mpi_type = MPI.DOUBLE
        if mpi_op is None:
            mpi_op = MPI.SUM

        dof  = self.dof
        ndof = self.n * self.dof

        # Get Local nodes
        nodelist = self.GetLocalNodeList()        

        if not local:
            globrhs = np.zeros(ndof, dtype=np.double)
            for i in nodelist:
                globrhs[(i-1)*dof:(i-1)*dof+dof] = GetRhs(i)

            globrhs_recv = np.zeros(ndof, dtype=np.double)

            self.comm.Reduce( [globrhs, mpi_type] 
                        , [globrhs_recv, mpi_type]
                        , op=mpi_op, root=root)

            return globrhs_recv
        else:
            localnodenbr = len(nodelist)
            size = localnodenbr * dof
            lrhs    = np.zeros(size, dtype=np.double)
            k = 0
            for i in nodelist:
                lrhs[k:k+dof] = GetRhs(i)
                k = k + dof
            return lrhs

    def SetRHS(self, b, coefsidx=None, op=None, op2=None, mode=None, local=False, root=-1):
        if op is not None:
            self.OP = op
        if op2 is not None:
            self.OP2 = op2
        if mode is not None:
            self.MODE = mode

        if coefsidx is None:
            if not local:
                self.murge.SetGlobalRHS(self.id, b, root, self.OP)
            else:
                self.murge.SetLocalRHS(self.id, b, self.OP, self.OP2)
        else:
            self.murge.SetRHS(self.id, self.n, coefsidx, b, self.OP, self.OP2,
                              self.MODE)

    def GetSolution(self, coefsidx=None, mode=None, local=False, root=-1):
        if mode is not None:
            self.MODE = mode            

        x = np.empty(1, dtype=np.double)
        if coefsidx is None:
            if not local:
                ndof = self.n * self.dof
                x = np.zeros(ndof, dtype=np.double)
                self.murge.GetGlobalSolution(self.id, x, root)
            else:
                self.murge.GetLocalSolution(self.id, x)
        else:
            self.murge.GetSolution(self.id, self.n, coefsidx, x, self.MODE)

        return x

    def Solve(self, v, coefsidx=None, op=None, op2=None, mode=None, local=False, root=-1):
        """
        this routine always returns a global array
        """
        self.SetRHS(v, coefsidx=coefsidx, op=op, op2=op2, mode=mode, local=local, root=root)
        return self.GetSolution(coefsidx=coefsidx, mode=mode, local=False, root=root)
#        return GetSolution(self, coefsidx=coefsidx, mode=mode, local=local, root=root)

    def dot(self, v, coefsidx=None, op=None, op2=None, mode=None, local=False, root=-1):
        self.SetRHS(v, coefsidx=coefsidx, op=op, op2=op2, mode=mode, local=local, root=root)

        y = np.zeros_like(v)
        if not local:
            self.murge.GetGlobalProduct(self.id, y, root)
            return y 
        else:
            self.murge.GetLocalProduct(id, y)
            return y


