
/*
   Copyright (C) 2012  Statoil ASA, Norway.

   The file 'ecl_rst_file.c' is part of ERT - Ensemble based Reservoir Tool.

   ERT 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 3 of the License, or
   (at your option) any later version.

   ERT 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 at <http://www.gnu.org/licenses/gpl.html>
   for more details.
*/

#include <stdio.h>
#include <string.h>
#include <math.h>
#include <errno.h>
#include <time.h>

#include <ert/util/hash.hpp>
#include <ert/util/util.h>
#include <ert/util/vector.hpp>
#include <ert/util/int_vector.hpp>
#include <ert/util/stringlist.hpp>

#include <ert/ecl/fortio.h>
#include <ert/ecl/ecl_kw.hpp>
#include <ert/ecl/ecl_file.hpp>
#include <ert/ecl/ecl_endian_flip.hpp>
#include <ert/ecl/ecl_kw_magic.hpp>
#include <ert/ecl/ecl_file_kw.hpp>
#include <ert/ecl/ecl_rst_file.hpp>
#include <ert/ecl/ecl_rsthead.hpp>
#include <ert/ecl/ecl_type.hpp>

struct ecl_rst_file_struct {
  fortio_type * fortio;
  bool          unified;
  bool          fmt_file;
};



static ecl_rst_file_type * ecl_rst_file_alloc( const char * filename ) {
  bool unified  = ecl_util_unified_file( filename );
  bool fmt_file;
  ecl_rst_file_type * rst_file = (ecl_rst_file_type*)util_malloc( sizeof * rst_file );

  if (ecl_util_fmt_file( filename , &fmt_file)) {
    rst_file->unified = unified;
    rst_file->fmt_file = fmt_file;
    return rst_file;
  } else {
    util_abort("%s: invalid restart filename:%s - could not determine formatted/unformatted status\n",__func__ , filename);
    return NULL;
  }
}

/**
   Observe that all the open() functions expect that filename conforms
   to the standard ECLIPSE conventions, i.e. with extension .FUNRST /
   .UNRST / .Xnnnn / .Fnnnn.
*/

ecl_rst_file_type * ecl_rst_file_open_read( const char * filename ) {
  ecl_rst_file_type * rst_file = ecl_rst_file_alloc( filename );
  rst_file->fortio = fortio_open_reader( filename , rst_file->fmt_file , ECL_ENDIAN_FLIP );
  return rst_file;
}



/*
  This function will scan through the file and look for seqnum
  headers, and position the file pointer in the right location to
  start writing data for the report step given by @report_step. The
  file is truncated, so that the filepointer will be at the (new) EOF
  when returning.
*/


ecl_rst_file_type * ecl_rst_file_open_write_seek( const char * filename , int report_step) {
  ecl_rst_file_type * rst_file = ecl_rst_file_alloc( filename  );
  offset_type target_pos = 0;
  bool seqnum_found = false;
  rst_file->fortio = fortio_open_readwrite( filename , rst_file->fmt_file , ECL_ENDIAN_FLIP );
  /*
     If the file does not exist at all the fortio_open_readwrite()
     will fail, we just try again - opening a new file in normal write
     mode, and then immediately returning.
  */
  if (!rst_file->fortio) {
    rst_file->fortio = fortio_open_writer( filename , rst_file->fmt_file , ECL_ENDIAN_FLIP );
    return rst_file;
  }

  fortio_fseek( rst_file->fortio , 0 , SEEK_SET );
  {
    ecl_kw_type * work_kw = ecl_kw_alloc_new("WORK-KW" , 0 , ECL_INT, NULL);

    while (true) {
      offset_type current_offset = fortio_ftell( rst_file->fortio );

      if (fortio_read_at_eof(rst_file->fortio)) {
        if (seqnum_found)
          target_pos = current_offset;
        break;
      }

      if (ecl_kw_fread_header( work_kw , rst_file->fortio) == ECL_KW_READ_FAIL)
        break;

      if (ecl_kw_name_equal( work_kw , SEQNUM_KW)) {
        ecl_kw_fread_realloc_data( work_kw , rst_file->fortio);
        int file_step = ecl_kw_iget_int( work_kw , 0 );
        if (file_step >= report_step) {
          target_pos = current_offset;
          break;
        }
        seqnum_found = true;
      } else
          ecl_kw_fskip_data( work_kw , rst_file->fortio );

    }

    ecl_kw_free( work_kw );
  }

  fortio_fseek( rst_file->fortio , target_pos , SEEK_SET);
  fortio_ftruncate_current( rst_file->fortio );
  return rst_file;
}

ecl_rst_file_type * ecl_rst_file_open_write( const char * filename ) {
  ecl_rst_file_type * rst_file = ecl_rst_file_alloc( filename  );
  rst_file->fortio = fortio_open_writer( filename , rst_file->fmt_file , ECL_ENDIAN_FLIP );
  return rst_file;
}

ecl_rst_file_type * ecl_rst_file_open_append( const char * filename ) {
  ecl_rst_file_type * rst_file = ecl_rst_file_alloc( filename );
  rst_file->fortio = fortio_open_append( filename , rst_file->fmt_file , ECL_ENDIAN_FLIP );
  return rst_file;
}

void ecl_rst_file_close( ecl_rst_file_type * rst_file ) {
  fortio_fclose( rst_file->fortio );
  free( rst_file );
}


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

static void ecl_rst_file_fwrite_SEQNUM( ecl_rst_file_type * rst_file , int seqnum ) {
  ecl_kw_type * seqnum_kw = ecl_kw_alloc( SEQNUM_KW , 1 , ECL_INT );
  ecl_kw_iset_int( seqnum_kw , 0 , seqnum );
  ecl_kw_fwrite( seqnum_kw , rst_file->fortio );
  ecl_kw_free( seqnum_kw );
}

void ecl_rst_file_start_solution( ecl_rst_file_type * rst_file ) {
  ecl_kw_type * startsol_kw = ecl_kw_alloc( STARTSOL_KW , 0 , ECL_MESS );
  ecl_kw_fwrite( startsol_kw , rst_file->fortio );
  ecl_kw_free( startsol_kw );
}

void ecl_rst_file_end_solution( ecl_rst_file_type * rst_file ) {
  ecl_kw_type * endsol_kw = ecl_kw_alloc( ENDSOL_KW , 0 , ECL_MESS );
  ecl_kw_fwrite( endsol_kw , rst_file->fortio );
  ecl_kw_free( endsol_kw );
}



static ecl_kw_type * ecl_rst_file_alloc_INTEHEAD( ecl_rst_file_type * rst_file,
                                                  ecl_rsthead_type * rsthead,
                                                  int simulator ) {
  ecl_kw_type * intehead_kw = ecl_kw_alloc( INTEHEAD_KW , INTEHEAD_RESTART_SIZE , ECL_INT );
  ecl_kw_scalar_set_int( intehead_kw , 0 );

  ecl_kw_iset_int( intehead_kw , INTEHEAD_UNIT_INDEX    , rsthead->unit_system );
  ecl_kw_iset_int( intehead_kw , INTEHEAD_NX_INDEX      , rsthead->nx);
  ecl_kw_iset_int( intehead_kw , INTEHEAD_NY_INDEX      , rsthead->ny);
  ecl_kw_iset_int( intehead_kw , INTEHEAD_NZ_INDEX      , rsthead->nz);
  ecl_kw_iset_int( intehead_kw , INTEHEAD_NACTIVE_INDEX , rsthead->nactive);
  ecl_kw_iset_int( intehead_kw , INTEHEAD_PHASE_INDEX   , rsthead->phase_sum );

  /* All well properties are hardcoded to zero. */
  {
    int NGMAXZ = 0;
    int NWGMAX = 0;
    int NIGRPZ = 0;
    int NSWLMX = 0;
    int NSEGMX = 0;
    int NISEGZ = 0;

    ecl_kw_iset_int( intehead_kw , INTEHEAD_NWELLS_INDEX  , rsthead->nwells );
    ecl_kw_iset_int( intehead_kw , INTEHEAD_NCWMAX_INDEX  , rsthead->ncwmax );
    ecl_kw_iset_int( intehead_kw , INTEHEAD_NWGMAX_INDEX  , NWGMAX );
    ecl_kw_iset_int( intehead_kw , INTEHEAD_NGMAXZ_INDEX  , NGMAXZ );
    ecl_kw_iset_int( intehead_kw , INTEHEAD_NIWELZ_INDEX  , rsthead->niwelz );
    ecl_kw_iset_int( intehead_kw , INTEHEAD_NZWELZ_INDEX  , rsthead->nzwelz );
    ecl_kw_iset_int( intehead_kw , INTEHEAD_NICONZ_INDEX  , rsthead->niconz );
    ecl_kw_iset_int( intehead_kw , INTEHEAD_NIGRPZ_INDEX  , NIGRPZ );

    {
      ecl_util_set_date_values( rsthead->sim_time , &rsthead->day , &rsthead->month , &rsthead->year );
      ecl_kw_iset_int( intehead_kw , INTEHEAD_DAY_INDEX    , rsthead->day );
      ecl_kw_iset_int( intehead_kw , INTEHEAD_MONTH_INDEX  , rsthead->month );
      ecl_kw_iset_int( intehead_kw , INTEHEAD_YEAR_INDEX   , rsthead->year );
    }

    ecl_kw_iset_int( intehead_kw , INTEHEAD_IPROG_INDEX , simulator);
    ecl_kw_iset_int( intehead_kw , INTEHEAD_NSWLMX_INDEX  , NSWLMX );
    ecl_kw_iset_int( intehead_kw , INTEHEAD_NSEGMX_INDEX  , NSEGMX );
    ecl_kw_iset_int( intehead_kw , INTEHEAD_NISEGZ_INDEX  , NISEGZ );
  }
  return intehead_kw;
}



static ecl_kw_type * ecl_rst_file_alloc_LOGIHEAD( int simulator ) {
  bool dual_porosity          = false;
  bool radial_grid_ECLIPSE100 = false;
  bool radial_grid_ECLIPSE300 = false;

  ecl_kw_type * logihead_kw = ecl_kw_alloc( LOGIHEAD_KW , LOGIHEAD_RESTART_SIZE , ECL_BOOL );

  ecl_kw_scalar_set_bool( logihead_kw , false );

  if (simulator == INTEHEAD_ECLIPSE100_VALUE)
    ecl_kw_iset_bool( logihead_kw , LOGIHEAD_RADIAL100_INDEX , radial_grid_ECLIPSE100);
  else
    ecl_kw_iset_bool( logihead_kw , LOGIHEAD_RADIAL300_INDEX , radial_grid_ECLIPSE300);

  ecl_kw_iset_bool( logihead_kw , LOGIHEAD_DUALP_INDEX , dual_porosity);
  return logihead_kw;
}


static ecl_kw_type * ecl_rst_file_alloc_DOUBHEAD( ecl_rst_file_type * rst_file , double days) {
  ecl_kw_type * doubhead_kw = ecl_kw_alloc( DOUBHEAD_KW , DOUBHEAD_RESTART_SIZE , ECL_DOUBLE );

  ecl_kw_scalar_set_double( doubhead_kw , 0);
  ecl_kw_iset_double( doubhead_kw , DOUBHEAD_DAYS_INDEX , days );


  return doubhead_kw;
}



void ecl_rst_file_fwrite_header( ecl_rst_file_type * rst_file ,
                                 int seqnum ,
                                 ecl_rsthead_type * rsthead_data ) {

  if (rst_file->unified)
    ecl_rst_file_fwrite_SEQNUM( rst_file , seqnum );

  {
    ecl_kw_type * intehead_kw = ecl_rst_file_alloc_INTEHEAD( rst_file , rsthead_data , INTEHEAD_ECLIPSE100_VALUE);
    ecl_kw_fwrite( intehead_kw , rst_file->fortio );
    ecl_kw_free( intehead_kw );
  }

  {
    ecl_kw_type * logihead_kw = ecl_rst_file_alloc_LOGIHEAD( INTEHEAD_ECLIPSE100_VALUE);
    ecl_kw_fwrite( logihead_kw , rst_file->fortio );
    ecl_kw_free( logihead_kw );
  }


  {
    ecl_kw_type * doubhead_kw = ecl_rst_file_alloc_DOUBHEAD( rst_file , rsthead_data->sim_days );
    ecl_kw_fwrite( doubhead_kw , rst_file->fortio );
    ecl_kw_free( doubhead_kw );
  }
}

void ecl_rst_file_add_kw(ecl_rst_file_type * rst_file , const ecl_kw_type * ecl_kw ) {
  ecl_kw_fwrite( ecl_kw , rst_file->fortio );
}


offset_type ecl_rst_file_ftell(const ecl_rst_file_type * rst_file ) {
  return fortio_ftell( rst_file->fortio );
}
