/* -*- mode: C -*-  */
/* 
   IGraph library.
   Copyright (C) 2009-2012  Gabor Csardi <csardi.gabor@gmail.com>
   334 Harvard st, Cambridge MA, 02139 USA
   
   This program 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.
   
   This program 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 this program; if not, write to the Free Software
   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 
   02110-1301 USA

*/

#include <cs/cs.h>
#include <igraph.h>
#include <igraph_sparsemat.h>
#include <igraph_blas_internal.h>
#include <igraph_arpack_internal.h>

int igraph_matrix_dgemv(const igraph_matrix_t *m,
                        const igraph_vector_t *v,
                        igraph_vector_t *res,
                        igraph_real_t alpha,
                        igraph_real_t beta,
                        igraph_bool_t transpose_m) {
  
  int nrow=igraph_matrix_nrow(m);
  int ncol=igraph_matrix_ncol(m);
  long int vlen=igraph_vector_size(v);
  int one=1;
  char t = transpose_m ? 't' : 'n';
  long int input_len  = transpose_m ? nrow : ncol;
  long int output_len = transpose_m ? ncol : nrow;
  
  if (vlen != input_len) {
    IGRAPH_ERROR("Matrix and vector sizes are incompatible", IGRAPH_EINVAL);
  }
  
  if (beta != 0 && igraph_vector_size(res) != output_len) {
    IGRAPH_ERROR("Non-zero beta and bad `res' vector size, possible mistake",
                 IGRAPH_EINVAL);
  }
  
  IGRAPH_CHECK(igraph_vector_resize(res, output_len));
  
  igraphdgemv_(&t, &nrow, &ncol, &alpha, &MATRIX(*m,0,0), 
               &nrow, VECTOR(*v), &one, &beta, VECTOR(*res), &one);
  
  return 0;
}

int igraph_matrix_vector_prod(const igraph_matrix_t *m,
                              const igraph_vector_t *v,
                              igraph_vector_t *res) {
  return igraph_matrix_dgemv(m, v, res, 1.0, 0.0, /*transpose=*/ 0);
}

int my_dgemv(const igraph_matrix_t *m,
                       const igraph_vector_t *v,
                       igraph_vector_t *res,
                       igraph_real_t alpha,
                       igraph_real_t beta,
                       igraph_bool_t transpose_m) {
  
  int nrow=igraph_matrix_nrow(m);
  int ncol=igraph_matrix_ncol(m);
  long int vlen=igraph_vector_size(v);
  int one=1;
  char t = transpose_m ? 't' : 'n';
  long int input_len  = transpose_m ? nrow : ncol;
  long int output_len = transpose_m ? ncol : nrow;
  
  if (vlen != input_len) {
    IGRAPH_ERROR("Matrix and vector sizes are incompatible", IGRAPH_EINVAL);
  }
  
  if (beta != 0 && igraph_vector_size(res) != output_len) {
    IGRAPH_ERROR("Non-zero beta and bad `res' vector size, possible mistake",
                IGRAPH_EINVAL);
  }
  
  IGRAPH_CHECK(igraph_vector_resize(res, output_len));
  
  igraphdgemv_(&t, &nrow, &ncol, &alpha, &MATRIX(*m,0,0), 
              &nrow, VECTOR(*v), &one, &beta, VECTOR(*res), &one);
  
  return 0;
}

int my_gaxpy(const igraph_matrix_t *m,
	     const igraph_vector_t *v,
	     igraph_vector_t *res) {
  return my_dgemv(m, v, res, 1.0, 0.0, /*transpose=*/ 0);
}

int my_dgemm(const igraph_matrix_t *m1,
	     const igraph_matrix_t *m2,
	     igraph_matrix_t *res) {

  long int m1_r=igraph_matrix_nrow(m1);
  long int m1_c=igraph_matrix_ncol(m1);
  long int m2_r=igraph_matrix_nrow(m2);
  long int m2_c=igraph_matrix_ncol(m2);
  long int i, j, k;
  
  if (m1_c != m2_r) { 
    IGRAPH_ERROR("Cannot multiply matrices, invalid dimensions", IGRAPH_EINVAL);
  }
  
  IGRAPH_CHECK(igraph_matrix_resize(res, m1_r, m2_c));
  igraph_matrix_null(res);

  for (i=0; i<m1_r; i++) {
    for (j=0; j<m2_c; j++) {
      for (k=0; k<m1_c /* which is also m2_r*/; k++) {
	MATRIX(*res, i, j) += MATRIX(*m1, i, k) * MATRIX(*m2, k, j);
      }
    }
  }

  return 0;
}

igraph_bool_t check_same(const igraph_sparsemat_t *A, 
			 const igraph_matrix_t *M) {
  
  long int nrow=igraph_sparsemat_nrow(A);
  long int ncol=igraph_sparsemat_ncol(A);
  long int j, p, nzero=0;
  
  if (nrow != igraph_matrix_nrow(M) ||
      ncol != igraph_matrix_ncol(M)) { 
    return 0; 
  }
  
  for (j=0; j<A->cs->n; j++) {
    for (p=A->cs->p[j]; p < A->cs->p[j+1]; p++) {
      long int to=A->cs->i[p];
      igraph_real_t value=A->cs->x[p];
      if (value != MATRIX(*M, to, j)) { return 0; }
      nzero += 1;
    }
  }

  for (j=0; j<nrow; j++) {
    for (p=0; p<ncol; p++) {
      if (MATRIX(*M, j, p) != 0) { nzero -= 1; }
    }
  }

  return nzero == 0;
}

int main() {
  
  igraph_sparsemat_t A, B, C, D;
  igraph_vector_t v, w, x, y;
  igraph_matrix_t M, N, O;
  long int i;

  srand(1);

  /* Matrix-vector product */
#define NROW 10
#define NCOL 5
#define EDGES NROW*NCOL/3
  igraph_matrix_init(&M, NROW, NCOL);
  igraph_sparsemat_init(&A, NROW, NCOL, EDGES);
  for (i=0; i<EDGES; i++) {
    long int r=RNG_INTEGER(0, NROW-1);
    long int c=RNG_INTEGER(0, NCOL-1);
    igraph_real_t value=RNG_INTEGER(1,5);
    MATRIX(M, r, c) = MATRIX(M, r, c) + value;
    igraph_sparsemat_entry(&A, r, c, value);
  }
  igraph_sparsemat_compress(&A, &B);
  igraph_sparsemat_destroy(&A);
  
  igraph_vector_init(&v, NCOL);
  igraph_vector_init(&w, NCOL);
  for (i=0; i<NCOL; i++) {
    VECTOR(v)[i] = VECTOR(w)[i] = RNG_INTEGER(1,5);
  }
  
  igraph_vector_init(&x, NROW);
  igraph_vector_init(&y, NROW);
  my_gaxpy(&M, &v, &x);
  igraph_vector_null(&y);
  igraph_sparsemat_gaxpy(&B, &w, &y);
  
  if (!igraph_vector_all_e(&x, &y)) { return 1; }

  igraph_vector_destroy(&x);
  igraph_vector_destroy(&y);
  igraph_vector_destroy(&v);
  igraph_vector_destroy(&w);
  igraph_sparsemat_destroy(&B);
  igraph_matrix_destroy(&M);

#undef NROW
#undef NCOL
#undef EDGES

  /* Matrix-matrix product */
#define NROW_A 10
#define NCOL_A 7
#define EDGES_A NROW_A*NCOL_A/3
#define NROW_B 7
#define NCOL_B 9
#define EDGES_B NROW_B*NCOL_B/3
  igraph_matrix_init(&M, NROW_A, NCOL_A);
  igraph_sparsemat_init(&A, NROW_A, NCOL_A, EDGES_A);
  for (i=0; i<EDGES_A; i++) {
    long int r=RNG_INTEGER(0, NROW_A-1);
    long int c=RNG_INTEGER(0, NCOL_A-1);
    igraph_real_t value=RNG_INTEGER(1,5);
    MATRIX(M, r, c) = MATRIX(M, r, c) + value;
    igraph_sparsemat_entry(&A, r, c, value);
  }
  igraph_sparsemat_compress(&A, &C);
  igraph_sparsemat_destroy(&A);

  igraph_matrix_init(&N, NROW_B, NCOL_B);
  igraph_sparsemat_init(&B, NROW_B, NCOL_B, EDGES_B);
  for (i=0; i<EDGES_B; i++) {
    long int r=RNG_INTEGER(0, NROW_B-1);
    long int c=RNG_INTEGER(0, NCOL_B-1);
    igraph_real_t value=RNG_INTEGER(1,5);
    MATRIX(N, r, c) = MATRIX(N, r, c) + value;
    igraph_sparsemat_entry(&B, r, c, value);
  }
  igraph_sparsemat_compress(&B, &D);
  igraph_sparsemat_destroy(&B);

  igraph_matrix_init(&O, 0, 0);
  my_dgemm(&M, &N, &O);
  igraph_sparsemat_multiply(&C, &D, &A);

  if (! check_same(&A, &O)) { return 2; }
  
  igraph_sparsemat_destroy(&C);
  igraph_sparsemat_destroy(&D);
  igraph_sparsemat_destroy(&A);
  igraph_matrix_destroy(&M);
  igraph_matrix_destroy(&N);
  igraph_matrix_destroy(&O);
  
  return 0;
}
