/* ReSCAL - CSP Format
 *
 * Copyright (C) 2011
 *
 * Author: Olivier Rozier <rozier@ipgp.fr>
 *
 * This file is part of ReSCAL.
 *
 * ReSCAL 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <assert.h>

#include "defs.h"
#include "macros.h"
#include "format.h"
#include "cells.h"

#define READ_INT(_CH) (int)(*(int*)(_CH))

extern int H, L, P, HL, HLP;       // les dimensions de la terre
extern Cell  *TE;	           // la 'terre'

char header[2048];  //metadata header
int csp_cell_size=0;   //size of cells in a CSP file
int csp_header_size=0; //size of CSP header
int csp_L=0;
int csp_H=0;
int csp_P=0;
int bounds=0;   //width of the boundary walls
unsigned char decomp_flag=0;
unsigned char check_model=1;

unsigned char little_endian()
{
  int n=1;

  // return 1 if litlle endian, 0 otherwise
  return *(char*)&n;
}

void csp_set_warning(unsigned char check)
{
  check_model = check;
}

void csp_set_bounds(int val)
{
  bounds = val;
  if (bounds){
    csp_L = L-2;
    csp_H = H-2;
    csp_P = P-2;
  }
  else{
    csp_L = L;
    csp_H = H;
    csp_P = P;
  }
}

void decompress(char *filename)
{
  decomp_flag = 0;
  if (strstr(filename,".gz")){
    char command[100];
    int ret;
    LogPrintf("decompressing CSP file\n");
    sprintf(command, "gunzip %s", filename);
    ret = system(command);
    if (ret){
      ErrPrintf("ERROR: impossible to uncompress %s (ret=%d)\n", filename, ret);
      exit(-1);
    }
    filename[strlen(filename)-3] = 0;
    decomp_flag = 1;
  }
}

void compress(char *filename, char force)
{
  if (decomp_flag || force){
    char command[100];
    int ret;
    sprintf(command, "gzip %s &", filename);
    ret = system(command);
    if (ret){
      ErrPrintf("ERROR: impossible to compress %s (ret=%d)\n", filename, ret);
      exit(-1);
    }
    decomp_flag = 0;
  }
}

int read_csp_header(char *filename)
{
  int chunk, cksize, mdsize, offset, n;
  FILE *fp;

  decompress(filename);

  if (!strstr(filename,".csp")){
    LogPrintf("not a CSP file\n");
    csp_header_size = 0;
    return 0;
  }

  assert(sizeof(int)==4);

  LogPrintf("reading CSP metadata\n");

  if( (fp = fopen(filename,"r")) == NULL ) {
    ErrPrintf("ERROR: cannot open file %s\n",filename);
    exit(-4);
  }

  // reset metadata
  memset(header,0,sizeof(header));
  offset = 0;

  // magic number
  n = fread(header, 4, 1, fp);
  //LogPrintf("magic=%s   n=%d\n", header, n);
  if ((n!=1) || strcmp(header, CSP_MAGIC_NUM)){
    ErrPrintf("ERROR: %s not in CSP format (bad magic number)\n", filename);
    exit(-1);
  }
  offset += 4;

  // endianness
  n = fread(header+offset, 4, 1, fp);
  if ((n!=1) || (header[offset+2]!=(little_endian() ? '_' : 'b'))){
    ErrPrintf("ERROR: bad endianness (%c)\n", header[offset+2]);
    exit(-1);
  }
  offset += 4;

  // chunk: size of header
  mdsize = sizeof(int);
  cksize = 8 + mdsize;
  n = fread(header+offset, cksize, 1, fp);
  if ((n!=1) || (READ_INT(header+offset+4)!=READ_INT("HDSZ"))){
    ErrPrintf("ERROR: chunk HDSZ invalid\n");
    exit(-1);
  }
  csp_header_size = READ_INT(header+offset+8);
  LogPrintf("size of header: %d\n", csp_header_size);
  offset += cksize;

  // reading all chunks left
  n = fread(header+offset, csp_header_size-offset, 1, fp);
  if (n!=1){
    ErrPrintf("ERROR: failed to read header\n");
    exit(-1);
  }
  fclose(fp);

  while (offset<csp_header_size){
    cksize = READ_INT(header+offset);
    chunk = READ_INT(header+offset+4);
    offset += 8;
    assert(cksize >= 8);

    if (chunk == READ_INT("MODL")){
      LogPrintf("chunk MODL: %s\n", header+offset);
      if (check_model && strcmp(header+offset, MOD_NAME)){
        WarnPrintf("WARNING: wrong model (%s)\n", MOD_NAME);
        //exit(-1);
      }
    }
    else if (chunk == READ_INT("SIZE")){
      //int csp_H, csp_L, csp_P;
      csp_H = READ_INT(header+offset);
      csp_L = READ_INT(header+offset+4);
      csp_P = READ_INT(header+offset+8);
      LogPrintf("chunk SIZE: %d x %d x %d\n", csp_H, csp_L, csp_P);
      if (((H != 0) && (csp_H != H)) || ((L != 0) && (csp_L != L)) || ((P != 0) && (csp_P != P))){
        ErrPrintf("ERROR: wrong size (%d x %d x %d)\n", H, L, P);
        exit(-1);
      }
      H = csp_H;
      L = csp_L;
      P = csp_P;
    }
    else if (chunk == READ_INT("CELL")){
      //int csp_szcell;
      csp_cell_size = READ_INT(header+offset);
      LogPrintf("chunk CELL: %d\n", csp_cell_size);
      /*if (csp_cell_size > sizeof(Cell)){
      //if (csp_cell_size != 1){
      //if (csp_cell_size != sizeof(Cell)){
        //ErrPrintf("WARNING: size of cell is different (%d)\n", sizeof(Cell));
        ErrPrintf("ERROR: too many data in cells (%d>%d)\n", csp_cell_size, (int)sizeof(Cell));
        exit(-1);
      }*/
    }

    offset += cksize-8;
  }

  return csp_header_size;
}

void read_csp(char *filename)
{
  FILE *fp;
  unsigned char *buf, *aux;
  int i, j, k, n;
  int szcell;
  Cell *pt = TE;

  LogPrintf("reading CSP data : %s\n", filename);

  if (!filename){
    ErrPrintf("ERROR: read_csp - no filename\n");
    exit(-1);
  }

  if (!csp_cell_size){
    csp_cell_size = sizeof(Cell);
    //csp_H = H;
    //csp_L = L;
  }

  AllocMemory(buf,unsigned char,HL*csp_cell_size);

  if( (fp = fopen(filename,"r")) == NULL ) {
    ErrPrintf("ERROR: cannot open file %s\n",filename);
    exit(-4);
  }

  if (csp_header_size){
    n = fread(header, csp_header_size, 1, fp);
    if (n!=1){
      ErrPrintf("ERROR: read_csp - failed to read header\n");
      exit(-1);
    }
  }

  szcell = csp_cell_size ? Min(csp_cell_size,sizeof(Cell)) : sizeof(Cell);
  LogPrintf("szcell = %d\n", szcell);
  LogPrintf("csp_cell_size = %d\n", csp_cell_size);

  for(k=0; k < P; k++){ // profondeur
    fread(buf,csp_cell_size,csp_H*csp_L,fp);
    aux = buf;
    for(j=0; j < H; j++){ // hauteur
      for(i=0; i < L; i++, pt++){ //largeur
#ifdef PARALLEL
        if ((pt->celltype != BORD) && (pt->celltype != TUNNEL)){
#else
        if (pt->celltype != BORD){
#endif
          memcpy(pt, aux, szcell);
          if (*aux >= MAX_CELL){
            ErrPrintf("ERROR : read_csp - incorrect cell type (%d)\n", *aux);
            exit(-1);
          }
          aux+=csp_cell_size;
        }
      }
    }
  }
  fclose(fp);

  FreeMemory(buf,unsigned char,HL*csp_cell_size)

  compress(filename,0);
}

int generate_csp_header()
{
  int offset=0;
  int hdsz_offset=0; //offset of header size
  int cksize=0; //chunk size
  int mdsize=0; //metadata size
  int md4; //4-bytes metadata

  LogPrintf("generation of metadata\n");

  // reset metadata
  memset(header,0,sizeof(header));

  // magic number
  sprintf(header, "\212CSP@1%c\n", little_endian() ? '_':'b');
  offset += 8;

  // chunk: size of header
  mdsize = sizeof(int);
  cksize = 8 + mdsize;
  memcpy(header+offset, &cksize, 4);
  offset += 4;
  memcpy(header+offset, "HDSZ", 4);
  offset += 4;
  hdsz_offset = offset;
  memset(header+offset, 0, mdsize);
  offset += mdsize;

#ifdef MOD_NAME
  // chunk: model name
  mdsize = strlen(MOD_NAME)+1;
  Align4(mdsize); //4-bytes alignement
  cksize = 8 + mdsize;
  memcpy(header+offset, &cksize, 4);
  offset += 4;
  memcpy(header+offset, "MODL", 4);
  offset += 4;
  memcpy(header+offset, MOD_NAME, strlen(MOD_NAME)+1);
  offset += mdsize;
#endif

  // chunk: size of cellular space
  if (!csp_H && !csp_L && !csp_P){
    csp_H = H;
    csp_L = L;
    csp_P = P;
  }
  mdsize = 3*sizeof(int);
  cksize = 8 + mdsize;
  memcpy(header+offset, &cksize, 4);
  offset += 4;
  memcpy(header+offset, "SIZE", 4);
  offset += 4;
  memcpy(header+offset, &csp_H, sizeof(int));
  offset += sizeof(int);
  memcpy(header+offset, &csp_L, sizeof(int));
  offset += sizeof(int);
  memcpy(header+offset, &csp_P, sizeof(int));
  offset += sizeof(int);

  // chunk: cells metadata
  md4 = sizeof(Cell);
  mdsize = sizeof(int);
  cksize = 8 + mdsize;
  memcpy(header+offset, &cksize, 4);
  offset += 4;
  memcpy(header+offset, "CELL", 4);
  offset += 4;
  memcpy(header+offset, &md4, mdsize);
  offset += mdsize;

  // size of header
  memcpy(header+hdsz_offset, &offset, sizeof(int));

  return offset;
}

void write_csp(char dump_type, char *filename)
{
  static int hd_size=0;
  FILE *fp;
  int i,j;
  Cell *pt = TE;
  //char *buf, *aux;
  Cell *buf, *aux;

  LogPrintf("writing CSP data : %s\n", filename);

  if ((dump_type==DUMP_CSP) && !hd_size){
    // header with metadata
    hd_size = generate_csp_header();
    LogPrintf("header size: %d\n", hd_size);
  }

  fp = fopen(filename,"w");
  if (!fp){
    ErrPrintf("Erreur ouverture fichier CSP : %s\n", filename);
    exit(-4);
  }

  if (dump_type==DUMP_CSP) fwrite(header, 1, hd_size, fp);

  AllocMemory(buf, Cell, HL);
  ResetMemory(buf, Cell, HL);

  //pt += H*L;
  for(j=0; j< P; j++){
    aux = buf;
    for(i=0; i< HL; i++, pt++){
#ifdef PARALLEL
      if ((pt->celltype != BORD) && (pt->celltype != TUNNEL)){
#else
      if (pt->celltype != BORD){
#endif
        //*aux++ = (char) pt->celltype;
        *aux++ = *pt;
      }
    }
    if (aux>buf) fwrite(buf, sizeof(Cell), csp_H*csp_L, fp);
  }
  fclose(fp);
  FreeMemory(buf, Cell, HL);
}
