/*******************************************************************
 *
 *                M4RI: Linear Algebra over GF(2)
 *
 *    Copyright (C) 2011 Martin Albrecht <martinralbrecht@googlemail.com>
 *
 *  Distributed under the terms of the GNU General Public License (GPL)
 *  version 2 or higher.
 *
 *    This code 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.
 *
 *  The full text of the GPL is available at:
 *
 *                  http://www.gnu.org/licenses/
 *
 ********************************************************************/

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "m4ri_config.h"

#if __M4RI_HAVE_LIBPNG
#include <png.h>
#endif  //__M4RI_HAVE_LIBPNG

#include "echelonform.h"
#include "io.h"

void mzd_info(const mzd_t *A, int do_rank) {
  printf("nrows: %6d, ncols: %6d, density: %6.5f, hash: 0x%016zx", A->nrows, A->ncols,
         mzd_density(A, 1), mzd_hash(A));
  if (do_rank) {
    mzd_t *AA = mzd_copy(NULL, A);
    printf(", rank: %6zu\n", (size_t)mzd_echelonize(AA, 0));
    mzd_free(AA);
  } else {
    printf("\n");
  }
}

#define SAFECHAR (m4ri_radix + m4ri_radix / 4 + 1)

void mzd_fprint_row(FILE *stream, mzd_t const *M, const rci_t i) {
  char temp[SAFECHAR];
  fprintf(stream, "[");
  word const *row = mzd_row_const(M, i);
  for (wi_t j = 0; j < M->width - 1; ++j) {
    m4ri_word_to_str(temp, row[j], 1);
    fprintf(stream, "%s|", temp);
  }
  row            = row + M->width - 1;
  int const wide = (M->ncols % m4ri_radix) ? M->ncols % m4ri_radix : m4ri_radix;
  for (int j = 0; j < wide; ++j) {
    if (j != 0 && (j % 4) == 0) fprintf(stream, ":");
    if (__M4RI_GET_BIT(*row, j))
      fprintf(stream, "1");
    else
      fprintf(stream, " ");
  }
  fprintf(stream, "]\n");
}

#if __M4RI_HAVE_LIBPNG
#define PNGSIGSIZE 8

mzd_t *mzd_from_png(const char *fn, int verbose) {
  int retval = 0;
  mzd_t *A   = NULL;
  png_byte pngsig[PNGSIGSIZE];

  FILE *fh = fopen(fn, "rb");

  if (!fh) {
    if (verbose) printf("Could not open file '%s' for reading\n", fn);
    return NULL;
  };

  if (fread((char *)pngsig, PNGSIGSIZE, 1, fh) != 1) {
    if (verbose) printf("Could not read file '%s'\n", fn);
    retval = 1;
    goto from_png_close_fh;
  }

  if (png_sig_cmp(pngsig, 0, PNGSIGSIZE) != 0) {
    if (verbose) printf("'%s' is not a PNG file.\n", fn);
    retval = 2;
    goto from_png_close_fh;
  }

  png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);

  if (!png_ptr) {
    if (verbose) printf("failed to initialise PNG read struct.\n");
    retval = 3;
    goto from_png_close_fh;
  }
  png_set_user_limits(png_ptr, 0x7fffffffL, 0x7fffffffL);

  png_infop info_ptr = png_create_info_struct(png_ptr);

  if (!info_ptr) {
    if (verbose) printf("failed to initialise PNG info struct\n");
    retval = 3;
    goto from_png_destroy_read_struct;
  }

  png_init_io(png_ptr, fh);
  png_set_sig_bytes(png_ptr, PNGSIGSIZE);
  png_read_info(png_ptr, info_ptr);

  const png_uint_32 m                = png_get_image_height(png_ptr, info_ptr);
  const png_uint_32 n                = png_get_image_width(png_ptr, info_ptr);
  const png_uint_32 bit_depth        = png_get_bit_depth(png_ptr, info_ptr);
  const png_uint_32 channels         = png_get_channels(png_ptr, info_ptr);
  const png_uint_32 color_type       = png_get_color_type(png_ptr, info_ptr);
  const png_uint_32 compression_type = png_get_compression_type(png_ptr, info_ptr);
  const png_uint_32 interlace_type   = png_get_interlace_type(png_ptr, info_ptr);

  if (interlace_type != PNG_INTERLACE_NONE) {
    if (verbose) printf("interlaced images not supported\n");
    goto from_png_destroy_read_struct;
  };

  if (verbose)
    printf("reading %u x %u matrix (bit depth: %u, channels: %u, color type: %u, compression type: "
           "%u)\n",
           (unsigned int)m, (unsigned int)n, (unsigned int)bit_depth, (unsigned int)channels,
           (unsigned int)color_type, (unsigned int)compression_type);

  if (color_type != 0 && color_type != 3) {
    if (verbose) printf("only graycscale and palette colors are supported.\n");
    goto from_png_destroy_read_struct;
  }

  A                      = mzd_init(m, n);
  const word bitmask_end = A->high_bitmask;
  png_bytep row          = m4ri_mm_calloc(sizeof(char), n / 8 + 1);

  word tmp;
  wi_t j;

  png_set_packswap(png_ptr);
  // png_set_invert_mono(png_ptr);

  for (rci_t i = 0; i < m; i++) {
    word *rowa = mzd_row(A, i);
    png_read_row(png_ptr, row, NULL);
    for (j = 0; j < A->width - 1; j++) {
      tmp = ((word)row[8 * j + 7]) << 56 | ((word)row[8 * j + 6]) << 48 |
            ((word)row[8 * j + 5]) << 40 | ((word)row[8 * j + 4]) << 32 |
            ((word)row[8 * j + 3]) << 24 | ((word)row[8 * j + 2]) << 16 |
            ((word)row[8 * j + 1]) << 8 | ((word)row[8 * j + 0]) << 0;
      rowa[j] = ~tmp;
    }
    tmp = 0;
    switch ((n / 8 + ((n % 8) ? 1 : 0)) % 8) {
    case 0: tmp |= ((word)row[8 * j + 7]) << 56;
    case 7: tmp |= ((word)row[8 * j + 6]) << 48;
    case 6: tmp |= ((word)row[8 * j + 5]) << 40;
    case 5: tmp |= ((word)row[8 * j + 4]) << 32;
    case 4: tmp |= ((word)row[8 * j + 3]) << 24;
    case 3: tmp |= ((word)row[8 * j + 2]) << 16;
    case 2: tmp |= ((word)row[8 * j + 1]) << 8;
    case 1: tmp |= ((word)row[8 * j + 0]) << 0;
    };
    rowa[j] |= (~tmp & bitmask_end);
  }

  m4ri_mm_free(row);
  png_read_end(png_ptr, NULL);

from_png_destroy_read_struct:
  png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)0);

from_png_close_fh:
  fclose(fh);

  if (retval != 0 && A) {
    mzd_free(A);
    return NULL;
  } else {
    return A;
  }
}

int mzd_to_png(const mzd_t *A, const char *fn, int compression_level, const char *comment,
               int verbose) {
  FILE *fh = fopen(fn, "wb");

  if (!fh) {
    if (verbose) printf("Could not open file '%s' for writing\n", fn);
    return 1;
  }

  png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);

  if (!png_ptr) {
    if (verbose) printf("failed to initialise PNG write struct.\n");
    fclose(fh);
    return 3;
  }
  png_set_user_limits(png_ptr, 0x7fffffffL, 0x7fffffffL);

  png_infop info_ptr = png_create_info_struct(png_ptr);

  if (!info_ptr) {
    if (verbose) printf("failed to initialise PNG info struct\n");
    png_destroy_write_struct(&png_ptr, &info_ptr);
    fclose(fh);
    return 3;
  }

  if (setjmp(png_jmpbuf(png_ptr))) {
    if (verbose) printf("error writing PNG file\n");
    png_destroy_write_struct(&png_ptr, &info_ptr);
    fclose(fh);
    return 1;
  }

  png_init_io(png_ptr, fh);
  png_set_compression_level(png_ptr, compression_level);

  png_set_IHDR(png_ptr, info_ptr, A->ncols, A->nrows, 1, PNG_COLOR_TYPE_GRAY, PNG_INTERLACE_NONE,
               PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);

  png_text txt_ptr[3];

  char pdate[21];
  time_t ptime     = time(NULL);
  struct tm *ltime = localtime(&ptime);
  sprintf(pdate, "%04d/%02d/%02d %02d:%02d:%02d", ltime->tm_year + 1900, ltime->tm_mon + 1,
          ltime->tm_mday, ltime->tm_hour, ltime->tm_min, ltime->tm_sec);

  txt_ptr[0].key         = "Software";
  txt_ptr[0].text        = "M4RI";
  txt_ptr[0].compression = PNG_TEXT_COMPRESSION_NONE;
  txt_ptr[1].key         = "Date";
  txt_ptr[1].text        = pdate;
  txt_ptr[1].compression = PNG_TEXT_COMPRESSION_NONE;
  txt_ptr[2].key         = "Comment";
  txt_ptr[2].text        = (char *)comment;
  txt_ptr[2].compression = PNG_TEXT_COMPRESSION_NONE;

  png_set_text(png_ptr, info_ptr, txt_ptr, 3);

  png_write_info(png_ptr, info_ptr);

  png_set_packswap(png_ptr);
  png_set_invert_mono(png_ptr);

  png_bytep row = m4ri_mm_calloc(sizeof(char), A->ncols / 8 + 8);

  wi_t j   = 0;
  word tmp = 0;
  for (rci_t i = 0; i < A->nrows; i++) {
    word const *rowptr = mzd_row_const(A, i);
    for (j = 0; j < A->width - 1; j++) {
      tmp            = rowptr[j];
      row[8 * j + 0] = (png_byte)((tmp >> 0) & 0xff);
      row[8 * j + 1] = (png_byte)((tmp >> 8) & 0xff);
      row[8 * j + 2] = (png_byte)((tmp >> 16) & 0xff);
      row[8 * j + 3] = (png_byte)((tmp >> 24) & 0xff);
      row[8 * j + 4] = (png_byte)((tmp >> 32) & 0xff);
      row[8 * j + 5] = (png_byte)((tmp >> 40) & 0xff);
      row[8 * j + 6] = (png_byte)((tmp >> 48) & 0xff);
      row[8 * j + 7] = (png_byte)((tmp >> 56) & 0xff);
    }
    tmp = rowptr[j];
    switch ((A->ncols / 8 + ((A->ncols % 8) ? 1 : 0)) % 8) {
    case 0: row[8 * j + 7] = (png_byte)((tmp >> 56) & 0xff);
    case 7: row[8 * j + 6] = (png_byte)((tmp >> 48) & 0xff);
    case 6: row[8 * j + 5] = (png_byte)((tmp >> 40) & 0xff);
    case 5: row[8 * j + 4] = (png_byte)((tmp >> 32) & 0xff);
    case 4: row[8 * j + 3] = (png_byte)((tmp >> 24) & 0xff);
    case 3: row[8 * j + 2] = (png_byte)((tmp >> 16) & 0xff);
    case 2: row[8 * j + 1] = (png_byte)((tmp >> 8) & 0xff);
    case 1: row[8 * j + 0] = (png_byte)((tmp >> 0) & 0xff);
    };
    png_write_row(png_ptr, row);
  }
  m4ri_mm_free(row);

  png_write_end(png_ptr, info_ptr);
  png_destroy_write_struct(&png_ptr, &info_ptr);
  fclose(fh);
  return 0;
}

#endif  //__M4RI_HAVE_LIBPNG

mzd_t *mzd_from_jcf(const char *fn, int verbose) {
  int retval = 0;
  mzd_t *A   = NULL;
  FILE *fh   = fopen(fn, "r");

  rci_t m, n;
  long p       = 0;
  long nonzero = 0;

  if (!fh) {
    if (verbose) printf("Could not open file '%s' for reading\n", fn);
    return NULL;
  }

  if (fscanf(fh, "%d %d %ld\n%ld\n\n", &m, &n, &p, &nonzero) != 4) {
    if (verbose) printf("File '%s' does not seem to be in JCF format.", fn);
    retval = 1;
    goto from_jcf_close_fh;
  }

  if (p != 2) {
    if (verbose) printf("Expected p==2 but found p==%ld\n", p);
    retval = 1;
    goto from_jcf_close_fh;
  }

  if (verbose)
    printf("reading %lu x %lu matrix with at most %ld non-zero entries (density at most: %6.5f)\n",
           (unsigned long)m, (unsigned long)n, (unsigned long)nonzero,
           ((double)nonzero) / ((double)m * n));

  A = mzd_init(m, n);

  long i = -1;
  long j = 0;

  while (fscanf(fh, "%ld\n", &j) == 1) {
    if (j < 0) { i++, j = -j; }
    if (((j - 1) >= n) || (i >= m))
      m4ri_die("trying to write to (%ld,%ld) in %ld x %ld matrix\n", i, j - 1, m, n);
    mzd_write_bit(A, i, j - 1, 1);
  };

from_jcf_close_fh:
  fclose(fh);

  if (retval != 0 && A) {
    mzd_free(A);
    return NULL;
  } else {
    return A;
  }
}

mzd_t *mzd_from_str(rci_t m, rci_t n, const char *str) {
  int idx  = 0;
  mzd_t *A = mzd_init(m, n);
  for (rci_t i = 0; i < A->nrows; i++) {
    for (rci_t j = 0; j < A->ncols; j++) { mzd_write_bit(A, i, j, str[idx++] == '1'); }
  }
  return A;
}
