/*=============================================================================

    This file is part of FLINT.

    FLINT is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    FLINT is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with FLINT; if not, write to the Free Software
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA

=============================================================================*/
/******************************************************************************

    Copyright (C) 2011, 2012, 2013 Sebastian Pancratz

******************************************************************************/

#ifndef PADIC_MAT_H
#define PADIC_MAT_H

#undef ulong
#define ulong ulongxx /* interferes with system includes */
#include <stdio.h>
#undef ulong
#include <gmp.h>
#define ulong mp_limb_t

#include "flint.h"
#include "fmpz.h"
#include "fmpz_mat.h"
#include "fmpq_mat.h"
#include "padic.h"

#ifdef __cplusplus
 extern "C" {
#endif


typedef struct
{
    fmpz_mat_struct mat;
    slong val;
    slong N;
} padic_mat_struct;

typedef padic_mat_struct padic_mat_t[1];

/* Macros  *******************************************************************/

#define padic_mat(A)              (&((A)->mat))
#define padic_mat_entry(A, i, j)  ((A)->mat.rows[i] + (j))
#define padic_mat_val(A)          ((A)->val)
#define padic_mat_prec(A)         ((A)->N)

#define padic_mat_nrows(A)        (((A)->mat).r)
#define padic_mat_ncols(A)        (((A)->mat).c)

/* Memory management  ********************************************************/

void padic_mat_init(padic_mat_t A, slong r, slong c);

void padic_mat_init2(padic_mat_t A, slong r, slong c, slong prec);

void padic_mat_clear(padic_mat_t A);

void _padic_mat_canonicalise(padic_mat_t A, const padic_ctx_t ctx);

void _padic_mat_reduce(padic_mat_t A, const padic_ctx_t ctx);

void padic_mat_reduce(padic_mat_t A, const padic_ctx_t ctx);

static __inline__ int
padic_mat_is_empty(const padic_mat_t A)
{
    return fmpz_mat_is_empty(padic_mat(A));
}

static __inline__ int
padic_mat_is_square(const padic_mat_t A)
{
    return fmpz_mat_is_square(padic_mat(A));
}

static __inline__ int 
padic_mat_is_canonical(const padic_mat_t A, const padic_ctx_t ctx)
{
    if (fmpz_mat_is_zero(padic_mat(A)))
    {
        return (padic_mat_val(A) == 0);
    }
    else
    {
        slong i, j;
        int canonical = 0;

        for (i = 0; i < padic_mat(A)->r; i++)
            for (j = 0; j < padic_mat(A)->c; j++)
                if (!fmpz_divisible(padic_mat_entry(A, i, j), ctx->p))
                    canonical = 1;
        return canonical;
    }
}

static __inline__ int 
padic_mat_is_reduced(const padic_mat_t A, const padic_ctx_t ctx)
{
    if (padic_mat_is_empty(A))
    {
        return 1;
    }
    else if (fmpz_mat_is_zero(padic_mat(A)))
    {
        return (padic_mat_val(A) == 0);
    }
    else if (padic_mat_is_canonical(A, ctx))
    {
        const slong v = padic_mat_val(A);
        const slong N = padic_mat_prec(A);

        if (v >= N)
        {
            return 0;
        }
        else
        {
            slong i, j;
            fmpz_t pN;
            int reduced = 1;
            int alloc = _padic_ctx_pow_ui(pN, N - v, ctx);

            for (i = 0; (i < padic_mat_nrows(A)) && reduced; i++)
                for (j = 0; (j < padic_mat_ncols(A)) && reduced; j++)
                    reduced = (fmpz_cmp(padic_mat_entry(A, i, j), pN) < 0);

            if (alloc)
                fmpz_clear(pN);

            return reduced;
        }
    }
    else
    {
        return 0;
    }
}

/* Basic assignment **********************************************************/

void padic_mat_set(padic_mat_t B, const padic_mat_t A, const padic_ctx_t ctx);

void padic_mat_swap(padic_mat_t A, padic_mat_t B);

void padic_mat_zero(padic_mat_t A);

void padic_mat_one(padic_mat_t A);

/* Conversions ***************************************************************/

void padic_mat_set_fmpq_mat(padic_mat_t B, 
                            const fmpq_mat_t A, const padic_ctx_t ctx);

void padic_mat_get_fmpq_mat(fmpq_mat_t B, 
                            const padic_mat_t A, const padic_ctx_t ctx);

/* Entries *******************************************************************/

void padic_mat_get_entry_padic(padic_t rop, 
                               const padic_mat_t op, slong i, slong j, 
                               const padic_ctx_t ctx);

void padic_mat_set_entry_padic(padic_mat_t rop, slong i, slong j, 
                               const padic_t op, const padic_ctx_t ctx);

/* Comparison ****************************************************************/

int padic_mat_equal(const padic_mat_t A, const padic_mat_t B);

int padic_mat_is_zero(const padic_mat_t A);

/* Input and output  *********************************************************/

int padic_mat_fprint(FILE * file, 
                     const padic_mat_t A, const padic_ctx_t ctx);

int padic_mat_fprint_pretty(FILE * file, const padic_mat_t A, 
                                         const padic_ctx_t ctx);

static __inline__
int padic_mat_print(const padic_mat_t A, const padic_ctx_t ctx)
{
    return padic_mat_fprint(stdout, A, ctx);
}

static __inline__
int padic_mat_print_pretty(const padic_mat_t A, const padic_ctx_t ctx)
{
    return padic_mat_fprint_pretty(stdout, A, ctx);
}

/* Random matrix generation  *************************************************/

void padic_mat_randtest(padic_mat_t mat, flint_rand_t state, 
                        const padic_ctx_t ctx);

/* Transpose *****************************************************************/

void padic_mat_transpose(padic_mat_t B, const padic_mat_t A);

/* Addition and subtraction **************************************************/

void _padic_mat_add(padic_mat_t C, const padic_mat_t A, const padic_mat_t B, 
                                   const padic_ctx_t ctx);
void padic_mat_add(padic_mat_t C, const padic_mat_t A, const padic_mat_t B, 
                                  const padic_ctx_t ctx);

void _padic_mat_sub(padic_mat_t C, const padic_mat_t A, const padic_mat_t B, 
                                   const padic_ctx_t ctx);
void padic_mat_sub(padic_mat_t C, const padic_mat_t A, const padic_mat_t B, 
                                  const padic_ctx_t ctx);

void _padic_mat_neg(padic_mat_t B, const padic_mat_t A);
void padic_mat_neg(padic_mat_t B, const padic_mat_t A, const padic_ctx_t ctx);

/* Scalar operations *********************************************************/

void _padic_mat_scalar_mul_padic(padic_mat_t B, 
                                 const padic_mat_t A, const padic_t c, 
                                 const padic_ctx_t ctx);
void padic_mat_scalar_mul_padic(padic_mat_t B, 
                                const padic_mat_t A, const padic_t c, 
                                const padic_ctx_t ctx);

void _padic_mat_scalar_mul_fmpz(padic_mat_t B, 
                                const padic_mat_t A, const fmpz_t c, 
                                const padic_ctx_t ctx);
void padic_mat_scalar_mul_fmpz(padic_mat_t B, 
                               const padic_mat_t A, const fmpz_t c, 
                               const padic_ctx_t ctx);

void padic_mat_scalar_div_fmpz(padic_mat_t B, 
                               const padic_mat_t A, const fmpz_t c, 
                               const padic_ctx_t ctx);

/* Multiplication ************************************************************/

void padic_mat_mul(padic_mat_t C, const padic_mat_t A, const padic_mat_t B, 
                                  const padic_ctx_t ctx);

#ifdef __cplusplus
}
#endif

#endif

