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

   The file 'ecl_smspec.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 <string.h>
#include <stdbool.h>
#include <math.h>
#include <time.h>
#include <errno.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/float_vector.hpp>
#include <ert/util/stringlist.hpp>

#include <ert/ecl/ecl_smspec.hpp>
#include <ert/ecl/ecl_file.hpp>
#include <ert/ecl/ecl_kw_magic.hpp>
#include <ert/ecl/ecl_kw.hpp>
#include <ert/ecl/ecl_util.hpp>
#include <ert/ecl/smspec_node.hpp>
#include <ert/ecl/ecl_endian_flip.hpp>
#include <ert/ecl/ecl_type.hpp>

#ifdef HAVE_FNMATCH
#include <fnmatch.h>
#endif


/**
   This file implements the indexing into the ECLIPSE summary files.
*/

/*
  Supporting a new variable type:
  -------------------------------

  1. The function smspec_node_alloc() must be updated to return a valid
     smspec_node_type instance when called with the new var_type.

  2. Update the function ecl_smpec_install_gen_key() to install smpec_index
     instances of this particular type. The format of the general key is
     implicitly defined in this function.

  3. The ecl_smspec structure supports two different types of lookup:

       a) Lookup based on general key like e.g. WWCT:OP6
       b) Specific lookup based on the variable type, i.e. :

              ecl_smspec_get_well_var( xxx , well_name , var).

      Historically everything started with specific lookup as in case b);
      however the general lookup proved to be very convenient, and the
      specfic lookup method has seen less use[*]. The final step in
      supporting a new variable is to update the function
      ecl_smspec_fread_header().

      If you want to support specific lookup of the new variable type you
      must add the necessary datastructures to the ecl_smspec_struct
      structure and then subsequently fill that structure in the big
      switch() in ecl_smspec_fread_header() - if you do not care about
      specific lookup you just have to add an empty case() slot to the
      switch in ecl_smspec_fread_header(). The LGR variables, and also
      ECL_SMSPEC_SEGMENT_VAR do not support specific lookup.

      [*]: The advantage of the specific lookup is that it is possible
           to supply better error messages (The well 'XX' does not
           exist, instead of just unknown key: 'WWCT:XX'), and it is
           also possible to support queries like: give me all the
           well names.

  4. Mark the variable type as supported with a 'X' in the defintion of
     ecl_smspec_var_type in ecl_smspec.h.

 */



#define ECL_SMSPEC_ID          806647
#define PARAMS_GLOBAL_DEFAULT  -99



struct ecl_smspec_struct {
  UTIL_TYPE_ID_DECLARATION;
  /*
    All the hash tables listed below here are different ways to access
    smspec_node instances. The actual smspec_node instances are
    owned by the smspec_nodes vector;
  */
  hash_type          * well_var_index;             /* Indexes for all well variables: {well1: {var1: index1 , var2: index2} , well2: {var1: index1 , var2: index2}} */
  hash_type          * well_completion_var_index;  /* Indexes for completion indexes .*/
  hash_type          * group_var_index;            /* Indexes for group variables.*/
  hash_type          * field_var_index;
  hash_type          * region_var_index;           /* The stored index is an offset. */
  hash_type          * misc_var_index;             /* Variables like 'TCPU' and 'NEWTON'. */
  hash_type          * block_var_index;            /* Block variables like BPR */
  hash_type          * gen_var_index;              /* This is "everything" - things can either be found as gen_var("WWCT:OP_X") or as well_var("WWCT" , "OP_X") */


  vector_type        * smspec_nodes;
  bool                 write_mode;
  bool                 need_nums;
  int_vector_type    * index_map;

  /*-----------------------------------------------------------------*/

  int               time_seconds;
  int               grid_dims[3];                 /* Grid dimensions - in DIMENS[1,2,3] */
  int               num_regions;
  int               Nwells , param_offset;
  int               params_size;
  const char      * key_join_string;               /* The string used to join keys when building gen_key keys - typically ":" -
                                                      but arbitrary - NOT necessary to be able to invert the joining. */
  char            * header_file;                   /* FULL path to the currenbtly loaded header_file. */

  bool                formatted;                     /* Has this summary instance been loaded from a formatted (i.e. FSMSPEC file) or unformatted (i.e. SMSPEC) file. */
  time_t              sim_start_time;                /* When did the simulation start - worldtime. */

  int                 time_index;                    /* The fields time_index, day_index, month_index and year_index */
  int                 day_index;                     /* are used by the ecl_sum_data object to locate per. timestep */
  int                 month_index;                   /* time information. */
  int                 year_index;
  bool                has_lgr;
  float_vector_type * params_default;

  char              * restart_case;
  ert_ecl_unit_enum   unit_system;
  int                 restart_step;
};


/**
About indexing:
---------------

The ECLISPE summary files are organised (roughly) like this:

 1. A header-file called xxx.SMPSEC is written, which is common to
    every timestep.

 2. For each timestep the summary vector is written in the form of a
    vector 'PARAMS' of length N with floats. In the PARAMS vector all
    types of data are stacked togeheter, and one must use the header
    info in the SMSPEC file to disentangle the summary data.

Here I will try to describe how the header in SMSPEC is organised, and
how that support is imlemented here. The SMSPEC header is organized
around three character vectors, of length N. To find the position in
the PARAMS vector of a certain quantity, you must consult one, two or
all three of these vectors. The most important vecor - which must
always be consulted is the KEYWORDS vector, then it is the WGNAMES and
NUMS (integer) vectors whcih must be consulted for some variable
types.


Let us a consider a system consisting of:

  * Two wells: P1 and P2 - for each well we have variables WOPR, WWCT
    and WGOR.

  * Three regions: For each region we have variables RPR and RXX(??)

  * We have stored field properties FOPT and FWPT


KEYWORDS = ['TIME','FOPR','FPR','FWCT','WOPR','WOPR,'WWCT','WWCT]
       ....



general_var:
------------
VAR_TYPE:(WELL_NAME|GROUP_NAME|NUMBER):NUMBER

Field var:         VAR_TYPE
Misc var:          VAR_TYPE
Well var:          VAR_TYPE:WELL_NAME
Group var:         VAR_TYPE:GROUP_NAME
Block var:         VAR_TYPE:i,j,k  (where i,j,k is calculated form NUM)
Region var         VAR_TYPE:index  (where index is NOT from the nums vector, it it is just an offset).
Completion var:    VAR_TYPE:WELL_NAME:NUM
....

*/


/**
   The special_vars list is used to associate keywords with special
   types, when the kewyord name is in conflict with the default vector
   naming convention; all the variables mentioned in the list below
   are given the type ECL_SMSPEC_MISC_VAR.

   For instance the keyword 'NEWTON' starts with 'N' and is
   classified as a NETWORK type variable. However it should rather
   be classified as a MISC type variable. (What a fucking mess).

   The special_vars list is used in the functions
   ecl_smspec_identify_special_var() and ecl_smspec_identify_var_type().
*/

static const char* special_vars[] = {"NEWTON",
                                     "NAIMFRAC",
                                     "NLINEARS",
                                     "NLINSMIN",
                                     "NLINSMAX",
                                     "ELAPSED",
                                     "MAXDPR",
                                     "MAXDSO",
                                     "MAXDSG",
                                     "MAXDSW",
                                     "STEPTYPE",
                                     "WNEWTON"};



/*
  The smspec_required_keywords variable contains a list of keywords
  which are *absolutely* required in the SMSPEC file, but observe that
  depending on the content of the "KEYWORDS" array other keywords
  might bre requred as well - this typically includes the NUMS
  keyword. Such 'second-order' dependencies are not accounted for with
  this simple list.
*/

static const size_t num_req_keywords = 5;
static const char* smspec_required_keywords[] = {
                                                   WGNAMES_KW,
                                                   KEYWORDS_KW,
                                                   STARTDAT_KW,
                                                   UNITS_KW,
                                                   DIMENS_KW
                                                 };


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

ecl_smspec_type * ecl_smspec_alloc_empty(bool write_mode , const char * key_join_string) {
  ecl_smspec_type *ecl_smspec;
  ecl_smspec = (ecl_smspec_type*)util_malloc(sizeof *ecl_smspec );
  UTIL_TYPE_ID_INIT(ecl_smspec , ECL_SMSPEC_ID);

  ecl_smspec->well_var_index                 = hash_alloc();
  ecl_smspec->well_completion_var_index      = hash_alloc();
  ecl_smspec->group_var_index                = hash_alloc();
  ecl_smspec->field_var_index                = hash_alloc();
  ecl_smspec->region_var_index               = hash_alloc();
  ecl_smspec->misc_var_index                 = hash_alloc();
  ecl_smspec->block_var_index                = hash_alloc();
  ecl_smspec->gen_var_index                  = hash_alloc();
  ecl_smspec->sim_start_time                 = -1;
  ecl_smspec->key_join_string                = key_join_string;
  ecl_smspec->header_file                    = NULL;

  ecl_smspec->smspec_nodes                   = vector_alloc_new();

  ecl_smspec->time_index   = -1;
  ecl_smspec->day_index    = -1;
  ecl_smspec->year_index   = -1;
  ecl_smspec->month_index  = -1;
  ecl_smspec->time_seconds = -1;

  /*
    The unit system is given as an integer in the INTEHEAD keyword. The INTEHEAD
    keyword is optional, and we have for a long time been completely oblivious
    to the possibility of extracting unit system information from the SMSPEC file.
  */
  ecl_smspec->unit_system  = ECL_METRIC_UNITS;

  ecl_smspec->index_map = int_vector_alloc(0,0);
  ecl_smspec->restart_case = NULL;
  ecl_smspec->restart_step = -1;
  ecl_smspec->params_default = float_vector_alloc(0 , PARAMS_GLOBAL_DEFAULT);
  ecl_smspec->write_mode = write_mode;
  ecl_smspec->need_nums = false;

  return ecl_smspec;
}


int * ecl_smspec_alloc_mapping( const ecl_smspec_type * self, const ecl_smspec_type * other) {
  int params_size = ecl_smspec_get_params_size( self );
  int * mapping = (int*)util_malloc( params_size * sizeof * mapping );

  for (int i = 0; i < params_size; i++)
    mapping[i] = -1;


  for (int i=0; i < ecl_smspec_num_nodes( self ); i++) {
    const smspec_node_type * self_node = ecl_smspec_iget_node( self , i );
    int self_index = smspec_node_get_params_index( self_node );
    const char * key = smspec_node_get_gen_key1( self_node );
    if (ecl_smspec_has_general_var( other , key)) {
      const smspec_node_type * other_node = ecl_smspec_get_general_var_node( other , key);
      int other_index = smspec_node_get_params_index(other_node);
      mapping[ self_index ]  =  other_index;
    }
  }

  return mapping;
}


/**
   Observe that the index here is into the __INTERNAL__ indexing in
   the smspec_nodes vector; and in general widely different from the
   params_index of the returned smspec_node instance.
*/


const smspec_node_type * ecl_smspec_iget_node( const ecl_smspec_type * smspec , int index ) {
  return (const smspec_node_type*)vector_iget_const( smspec->smspec_nodes , index );
}

int ecl_smspec_num_nodes( const ecl_smspec_type * smspec) {
  return vector_get_size( smspec->smspec_nodes );
}


/**
 * Returns an ecl data type for which all names will fit. If the maximum name
 * length is at most 8, an ECL_CHAR is returned and otherwise a large enough
 * ECL_STRING.
 */
static ecl_data_type get_wgnames_type(const ecl_smspec_type * smspec) {
  size_t max_len = 0;
  for(int i = 0; i < ecl_smspec_num_nodes(smspec); ++i) {
    const smspec_node_type * node = ecl_smspec_iget_node(smspec, i);
    const char * name = smspec_node_get_wgname( node );
    if(name)
      max_len = util_size_t_max(max_len, strlen(name));
  }

  return max_len <= ECL_STRING8_LENGTH ? ECL_CHAR : ECL_STRING(max_len);
}

static void ecl_smspec_fwrite_INTEHEAD(const ecl_smspec_type * smspec, fortio_type * fortio) {
  ecl_kw_type * intehead = ecl_kw_alloc( INTEHEAD_KW, INTEHEAD_SMSPEC_SIZE, ECL_INT);
  ecl_kw_iset_int(intehead, INTEHEAD_SMSPEC_UNIT_INDEX, smspec->unit_system);
  /*
    The simulator type is just hardcoded to ECLIPSE100.
  */
  ecl_kw_iset_int(intehead, INTEHEAD_SMSPEC_IPROG_INDEX, INTEHEAD_ECLIPSE100_VALUE);
  ecl_kw_fwrite(intehead, fortio);
  ecl_kw_free(intehead);
}


static void ecl_smspec_fwrite_RESTART(const ecl_smspec_type * smspec, fortio_type * fortio) {
  ecl_kw_type * restart_kw = ecl_kw_alloc( RESTART_KW , SUMMARY_RESTART_SIZE , ECL_CHAR );
  for (int i=0; i < SUMMARY_RESTART_SIZE; i++)
    ecl_kw_iset_string8( restart_kw , i , "");

  if (smspec->restart_case != NULL) {
    int restart_case_len = strlen(smspec->restart_case);

    int offset = 0;
    for (int i = 0; i < SUMMARY_RESTART_SIZE ; i++) {
      if (offset < restart_case_len)
        ecl_kw_iset_string8( restart_kw , i , &smspec->restart_case[ offset ]);
      offset += ECL_STRING8_LENGTH;
    }
  }
  ecl_kw_fwrite( restart_kw , fortio );
  ecl_kw_free( restart_kw );
}



static void ecl_smspec_fwrite_DIMENS(const ecl_smspec_type * smspec, fortio_type * fortio) {
  ecl_kw_type * dimens_kw = ecl_kw_alloc( DIMENS_KW , DIMENS_SIZE , ECL_INT );
  int num_nodes = ecl_smspec_num_nodes( smspec );
  ecl_kw_iset_int( dimens_kw , DIMENS_SMSPEC_SIZE_INDEX , num_nodes );
  ecl_kw_iset_int( dimens_kw , DIMENS_SMSPEC_NX_INDEX   , smspec->grid_dims[0] );
  ecl_kw_iset_int( dimens_kw , DIMENS_SMSPEC_NY_INDEX   , smspec->grid_dims[1] );
  ecl_kw_iset_int( dimens_kw , DIMENS_SMSPEC_NZ_INDEX   , smspec->grid_dims[2] );
  ecl_kw_iset_int( dimens_kw , 4  , 0 );  // Do not know what this is for.
  ecl_kw_iset_int( dimens_kw , DIMENS_SMSPEC_RESTART_STEP_INDEX , smspec->restart_step );

  ecl_kw_fwrite( dimens_kw , fortio );
  ecl_kw_free( dimens_kw );
}


static void ecl_smspec_fwrite_STARTDAT(const ecl_smspec_type * smspec, fortio_type * fortio) {
  ecl_kw_type * startdat_kw = ecl_kw_alloc( STARTDAT_KW , STARTDAT_SIZE , ECL_INT );
  int day,month,year;
  ecl_util_set_date_values( smspec->sim_start_time , &day, &month , &year);

  ecl_kw_iset_int( startdat_kw , STARTDAT_DAY_INDEX   , day );
  ecl_kw_iset_int( startdat_kw , STARTDAT_MONTH_INDEX , month );
  ecl_kw_iset_int( startdat_kw , STARTDAT_YEAR_INDEX  , year );

  ecl_kw_fwrite( startdat_kw , fortio );
  ecl_kw_free( startdat_kw );
}


static void ecl_smspec_fortio_fwrite( const ecl_smspec_type * smspec , fortio_type * fortio) {
  ecl_smspec_fwrite_INTEHEAD(smspec, fortio);
  ecl_smspec_fwrite_RESTART(smspec, fortio);
  ecl_smspec_fwrite_DIMENS(smspec, fortio);

  int num_nodes = ecl_smspec_num_nodes( smspec );
  ecl_kw_type * keywords_kw = ecl_kw_alloc( KEYWORDS_KW , num_nodes , ECL_CHAR );
  ecl_kw_type * units_kw    = ecl_kw_alloc( UNITS_KW    , num_nodes , ECL_CHAR );
  ecl_kw_type * nums_kw     = NULL;

  // If the names_type is an ECL_STRING we expect this to be an INTERSECT
  // summary, otherwise an ECLIPSE summary.
  ecl_data_type names_type  = get_wgnames_type(smspec);
  ecl_kw_type * wgnames_kw = ecl_kw_alloc( ecl_type_is_char(names_type) ? WGNAMES_KW : NAMES_KW,
                                           num_nodes,
                                           names_type );

  if (smspec->need_nums)
    nums_kw = ecl_kw_alloc( NUMS_KW , num_nodes , ECL_INT);

  for (int i=0; i < ecl_smspec_num_nodes( smspec ); i++) {
    const smspec_node_type * smspec_node = ecl_smspec_iget_node( smspec , i );
    /*
      It is possible to add variables with deferred initialisation
      with the ecl_sum_add_blank_var() function. Before these
      variables can be actually used for anything interesting they
      must be initialized with the ecl_sum_init_var() function.

      If a call to save the smspec file comes before all the
      variable have been initialized things will potentially go
      belly up. This is solved with the following uber-hack:

      o One of the well related keywords is chosen; in
        particular 'WWCT' in this case.

      o The wgname value is set to DUMMY_WELL

      The use of DUMMY_WELL ensures that this field will be
      ignored when/if this smspec file is read in at a later
      stage.
    */
    if (smspec_node_get_var_type( smspec_node ) == ECL_SMSPEC_INVALID_VAR) {
      ecl_kw_iset_string8( keywords_kw , i , "WWCT" );
      ecl_kw_iset_string8( units_kw , i , "????????");
      ecl_kw_iset_string_ptr( wgnames_kw , i , DUMMY_WELL);
    } else {
      ecl_kw_iset_string8( keywords_kw , i , smspec_node_get_keyword( smspec_node ));
      ecl_kw_iset_string8( units_kw , i , smspec_node_get_unit( smspec_node ));
      {
        const char * wgname = DUMMY_WELL;
        if (smspec_node_get_wgname( smspec_node ))
          wgname = smspec_node_get_wgname( smspec_node );
        ecl_kw_iset_string_ptr( wgnames_kw , i , wgname);
      }
    }

    if (nums_kw != NULL)
      ecl_kw_iset_int( nums_kw , i , smspec_node_get_num( smspec_node ));
  }
  ecl_kw_fwrite( keywords_kw , fortio );
  ecl_kw_fwrite( wgnames_kw , fortio );
  if (nums_kw != NULL)
    ecl_kw_fwrite( nums_kw , fortio );
  ecl_kw_fwrite( units_kw , fortio );

  ecl_kw_free( keywords_kw );
  ecl_kw_free( wgnames_kw );
  ecl_kw_free( units_kw );
  if (nums_kw != NULL)
    ecl_kw_free( nums_kw );

  ecl_smspec_fwrite_STARTDAT(smspec, fortio);
}


void ecl_smspec_fwrite( const ecl_smspec_type * smspec , const char * ecl_case , bool fmt_file ) {
  char * filename = ecl_util_alloc_filename( NULL , ecl_case , ECL_SUMMARY_HEADER_FILE , fmt_file , 0 );
  fortio_type * fortio = fortio_open_writer( filename , fmt_file , ECL_ENDIAN_FLIP);

  if (!fortio) {
    const char * error_fmt_msg = "%s: Unable to open fortio file %s, error: %s .\n";
    util_abort( error_fmt_msg , __func__ , filename , strerror( errno ) );
  }

  ecl_smspec_fortio_fwrite( smspec , fortio );

  fortio_fclose( fortio );
  free( filename );
}



static ecl_smspec_type * ecl_smspec_alloc_writer__( const char * key_join_string , const char * restart_case, int restart_step, time_t sim_start , bool time_in_days , int nx , int ny , int nz) {
 ecl_smspec_type * ecl_smspec = ecl_smspec_alloc_empty( true , key_join_string );
  /*
    Only a total of 9 * 8 characters is set aside for the restart keyword, if
    the supplied restart case is longer than that we silently ignore it.
  */
  if (restart_case) {
    if (strlen(restart_case) <= (SUMMARY_RESTART_SIZE * ECL_STRING8_LENGTH)) {
      ecl_smspec->restart_case = util_alloc_string_copy( restart_case );
      ecl_smspec->restart_step = restart_step;
    }
  }
  ecl_smspec->grid_dims[0] = nx;
  ecl_smspec->grid_dims[1] = ny;
  ecl_smspec->grid_dims[2] = nz;
  ecl_smspec->sim_start_time = sim_start;

  {
    smspec_node_type * time_node;
    if (time_in_days) {
      time_node = smspec_node_alloc( ECL_SMSPEC_MISC_VAR ,
                                     NULL ,
                                     "TIME" ,
                                     "DAYS" ,
                                     key_join_string ,
                                     ecl_smspec->grid_dims ,
                                     0  ,
                                     -1 ,
                                     0 );
      ecl_smspec->time_seconds = 3600 * 24;
    } else {
      time_node = smspec_node_alloc( ECL_SMSPEC_MISC_VAR ,
                                     NULL ,
                                     "TIME" ,
                                     "HOURS" ,
                                     key_join_string ,
                                     ecl_smspec->grid_dims ,
                                     0  ,
                                     -1 ,
                                     0 );
      ecl_smspec->time_seconds = 3600;
    }

    ecl_smspec_add_node( ecl_smspec , time_node );
    ecl_smspec->time_index = smspec_node_get_params_index( time_node );
  }
  return ecl_smspec;
}

ecl_smspec_type * ecl_smspec_alloc_restart_writer( const char * key_join_string , const char * restart_case, int restart_step, time_t sim_start , bool time_in_days , int nx , int ny , int nz) {
  return ecl_smspec_alloc_writer__(key_join_string, restart_case, restart_step, sim_start, time_in_days, nx, ny, nz);
}

ecl_smspec_type * ecl_smspec_alloc_writer(const char * key_join_string, time_t sim_start, bool time_in_days, int nx, int ny , int nz) {
  return ecl_smspec_alloc_writer__(key_join_string, NULL, 0, sim_start, time_in_days, nx, ny, nz);
}


UTIL_SAFE_CAST_FUNCTION( ecl_smspec , ECL_SMSPEC_ID )


/**
   Goes through the special_vars static table to check if @var is one
   the special variables which does not follow normal naming
   convention. If the test eavulates to true the function will return
   ECL_SMSPEC_MISC_VAR, otherwise the function will return
   ECL_SMSPEC_INVALID_VAR and the variable type will be determined
   from the var name according to standard naming conventions.

   It is important that this function is called before the standard
   method.
*/

static ecl_smspec_var_type ecl_smspec_identify_special_var( const char * var ) {
  ecl_smspec_var_type var_type = ECL_SMSPEC_INVALID_VAR;

  int num_special = sizeof( special_vars ) / sizeof( special_vars[0] );
  int i;
  for (i=0; i < num_special; i++) {
    if (strcmp( var , special_vars[i]) == 0) {
      var_type = ECL_SMSPEC_MISC_VAR;
      break;
    }
  }

  return var_type;
}


/*
   See table 3.4 in the ECLIPSE file format reference manual.

   Observe that the combined ecl_sum style keys like e.g. WWCT:OP1
   should be formatted with the keyword first, so that this function
   will identify both 'WWCT' and 'WWCT:OP_1' as a ECL_SMSPEC_WELL_VAR
   instance.
*/


ecl_smspec_var_type  ecl_smspec_identify_var_type(const char * var) {
  ecl_smspec_var_type var_type = ecl_smspec_identify_special_var( var );
  if (var_type == ECL_SMSPEC_INVALID_VAR) {
    switch(var[0]) {
    case('A'):
      var_type = ECL_SMSPEC_AQUIFER_VAR;
      break;
    case('B'):
      var_type = ECL_SMSPEC_BLOCK_VAR;
      break;
    case('C'):
      var_type = ECL_SMSPEC_COMPLETION_VAR;
      break;
    case('F'):
      var_type = ECL_SMSPEC_FIELD_VAR;
      break;
    case('G'):
      var_type = ECL_SMSPEC_GROUP_VAR;
      break;
    case('L'):
      switch(var[1]) {
      case('B'):
        var_type = ECL_SMSPEC_LOCAL_BLOCK_VAR;
        break;
      case('C'):
        var_type = ECL_SMSPEC_LOCAL_COMPLETION_VAR;
        break;
      case('W'):
        var_type = ECL_SMSPEC_LOCAL_WELL_VAR;
        break;
      default:
        /*
          The documentation explicitly mentions keywords starting with
          LB, LC and LW as special types, but keywords starting with
          L[^BCW] are also valid. These come in the misceallaneous
          category; at least the LLINEAR keyword is an example of such
          a keyword.
        */
        var_type = ECL_SMSPEC_MISC_VAR;
      }
      break;
    case('N'):
      var_type = ECL_SMSPEC_NETWORK_VAR;
      break;
    case('R'):
      {
        /*
          The distinction between region-to-region variables and plain
          region variables is less than clear: The current
          interpretation is that the cases:

             1. Any variable matching:

                a) Starts with 'R'
                b) Has 'F' as the third character

             2. The variable "RNLF"

          Get variable type ECL_SMSPEC_REGION_2_REGION_VAR. The rest
          get the type ECL_SMSPEC_REGION_VAR.
        */

        if (util_string_equal( var , "RNLF"))
          var_type = ECL_SMSPEC_REGION_2_REGION_VAR;
        else if (var[2] == 'F')
          var_type = ECL_SMSPEC_REGION_2_REGION_VAR;
        else
          var_type  = ECL_SMSPEC_REGION_VAR;

      }
      break;
    case('S'):
      var_type = ECL_SMSPEC_SEGMENT_VAR;
      break;
    case('W'):
      var_type = ECL_SMSPEC_WELL_VAR;
      break;
    default:
      /*
        It is unfortunately impossible to recognize an error situation -
        the rest just goes in "other" variables.
      */
      var_type = ECL_SMSPEC_MISC_VAR;
    }
  }
  return var_type;
}


static bool ecl_smspec_lgr_var_type( ecl_smspec_var_type var_type) {
  if ((var_type == ECL_SMSPEC_LOCAL_BLOCK_VAR) ||
      (var_type == ECL_SMSPEC_LOCAL_WELL_VAR) ||
      (var_type == ECL_SMSPEC_LOCAL_COMPLETION_VAR))

    return true;
  else
    return false;
}


/**
   Takes a ecl_smspec_var_type variable as input, and return a string
   representation of this var_type. Suitable for debug messages +++
*/

const char * ecl_smspec_get_var_type_name( ecl_smspec_var_type var_type ) {
  switch(var_type) {
  case(ECL_SMSPEC_INVALID_VAR):
    return "INVALID_VAR";
    break;
  case(ECL_SMSPEC_AQUIFER_VAR):
    return "AQUIFER_VAR";
    break;
  case(ECL_SMSPEC_WELL_VAR):
    return "WELL_VAR";
    break;
  case(ECL_SMSPEC_REGION_VAR):
    return "REGION_VAR";
    break;
  case(ECL_SMSPEC_FIELD_VAR):
    return "FIELD_VAR";
    break;
  case(ECL_SMSPEC_GROUP_VAR):
    return "GROUP_VAR";
    break;
  case(ECL_SMSPEC_BLOCK_VAR):
    return "BLOCK_VAR";
    break;
  case(ECL_SMSPEC_COMPLETION_VAR):
    return "COMPLETION_VAR";
    break;
  case(ECL_SMSPEC_LOCAL_BLOCK_VAR):
    return "LOCAL_BLOCK_VAR";
    break;
  case(ECL_SMSPEC_LOCAL_COMPLETION_VAR):
    return "LOCAL_COMPLETION_VAR";
    break;
  case(ECL_SMSPEC_LOCAL_WELL_VAR):
    return "LOCAL_WELL_VAR";
    break;
  case(ECL_SMSPEC_NETWORK_VAR):
    return "NETWORK_VAR";
    break;
  case(ECL_SMSPEC_REGION_2_REGION_VAR):
    return "REGION_2_REGION_VAR";
    break;
  case(ECL_SMSPEC_SEGMENT_VAR):
    return "SEGMENT_VAR";
    break;
  case(ECL_SMSPEC_MISC_VAR):
    return "MISC_VAR";
    break;
  default:
    util_abort("%s: Unrecognized variable type:%d \n",__func__ , var_type);
    return NULL;
  }
}






/**
  Input i,j,k are assumed to be in the interval [1..nx] , [1..ny],
  [1..nz], return value is a global index which can be used in the
  xxx_block_xxx routines.
*/


static int ecl_smspec_get_global_grid_index(const ecl_smspec_type * smspec , int i , int j , int k) {
  return i + (j - 1) * smspec->grid_dims[0] + (k - 1) * smspec->grid_dims[0] * smspec->grid_dims[1];
}




/**
   This function takes a fully initialized smspec_node instance, generates the
   corresponding key and inserts smspec_node instance in the main hash table
   smspec->gen_var_index.

   The format strings used, i.e. VAR:WELL for well based variables is implicitly
   defined through the format strings used in this function.
*/

static void ecl_smspec_install_gen_keys( ecl_smspec_type * smspec , smspec_node_type * smspec_node ) {
  /* Insert the default general mapping. */
  {
    const char * gen_key1 = smspec_node_get_gen_key1( smspec_node );
    if (gen_key1 != NULL)
      hash_insert_ref(smspec->gen_var_index , gen_key1 , smspec_node);
  }

  /* Insert the (optional) extra mapping for block related variables and region_2_region variables: */
  {
    const char * gen_key2 = smspec_node_get_gen_key2( smspec_node );
    if (gen_key2 != NULL)
      hash_insert_ref(smspec->gen_var_index , gen_key2 , smspec_node);
  }
}

static void ecl_smspec_install_special_keys( ecl_smspec_type * ecl_smspec , smspec_node_type * smspec_node) {
  /**
      This large switch is for installing keys which have custom lookup
      paths, in addition to the lookup based on general keys. Examples
      of this is e.g. well variables which can be looked up through:

      ecl_smspec_get_well_var_index( smspec , well_name , var );
  */

  const char * well            = smspec_node_get_wgname( smspec_node );
  const char * group           = well;
  const int num                = smspec_node_get_num(smspec_node);
  const char * keyword         = smspec_node_get_keyword(smspec_node);
  ecl_smspec_var_type var_type = smspec_node_get_var_type( smspec_node );

  switch(var_type) {
  case(ECL_SMSPEC_COMPLETION_VAR):
    /* Three level indexing: variable -> well -> string(cell_nr)*/
    if (!hash_has_key(ecl_smspec->well_completion_var_index , well))
      hash_insert_hash_owned_ref(ecl_smspec->well_completion_var_index , well , hash_alloc() , hash_free__);
    {
      hash_type * cell_hash = (hash_type*)hash_get(ecl_smspec->well_completion_var_index , well);
      char cell_str[16];
      sprintf(cell_str , "%d" , num);
      if (!hash_has_key(cell_hash , cell_str))
        hash_insert_hash_owned_ref(cell_hash , cell_str , hash_alloc() , hash_free__);
      {
        hash_type * var_hash = (hash_type*)hash_get(cell_hash , cell_str);
        hash_insert_ref(var_hash , keyword , smspec_node );
      }
    }
    break;
  case(ECL_SMSPEC_FIELD_VAR):
    /*
      Field variable
    */
    hash_insert_ref( ecl_smspec->field_var_index , keyword , smspec_node );
    break;
  case(ECL_SMSPEC_GROUP_VAR):
    if (!hash_has_key(ecl_smspec->group_var_index , group))
      hash_insert_hash_owned_ref(ecl_smspec->group_var_index , group, hash_alloc() , hash_free__);
    {
      hash_type * var_hash = (hash_type*)hash_get(ecl_smspec->group_var_index , group);
      hash_insert_ref(var_hash , keyword , smspec_node );
    }
    break;
  case(ECL_SMSPEC_REGION_VAR):
    if (!hash_has_key(ecl_smspec->region_var_index , keyword))
      hash_insert_hash_owned_ref( ecl_smspec->region_var_index , keyword , hash_alloc() , hash_free__);
    {
      hash_type * var_hash = (hash_type*)hash_get(ecl_smspec->region_var_index , keyword);
      char num_str[16];
      sprintf( num_str , "%d" , num);
      hash_insert_ref(var_hash , num_str , smspec_node);
    }
    ecl_smspec->num_regions = util_int_max(ecl_smspec->num_regions , num);
    break;
  case (ECL_SMSPEC_WELL_VAR):
    if (!hash_has_key(ecl_smspec->well_var_index , well))
      hash_insert_hash_owned_ref(ecl_smspec->well_var_index , well , hash_alloc() , hash_free__);
    {
      hash_type * var_hash = (hash_type*)hash_get(ecl_smspec->well_var_index , well);
      hash_insert_ref(var_hash , keyword , smspec_node );
    }
    break;
  case(ECL_SMSPEC_MISC_VAR):
    /* Misc variable - i.e. date or CPU time ... */
    hash_insert_ref(ecl_smspec->misc_var_index , keyword , smspec_node );
    break;
  case(ECL_SMSPEC_BLOCK_VAR):
    /* A block variable */
    if (!hash_has_key(ecl_smspec->block_var_index , keyword))
      hash_insert_hash_owned_ref(ecl_smspec->block_var_index , keyword , hash_alloc() , hash_free__);
    {
      hash_type * block_hash = (hash_type*)hash_get(ecl_smspec->block_var_index , keyword);
      char block_nr[16];
      sprintf( block_nr , "%d" , num );
      hash_insert_ref(block_hash , block_nr , smspec_node);
    }
    break;
    /**
        The variables below are ONLY accesable through the gen_key
        setup; but the must be mentioned in this switch statement,
        otherwise they will induce a hard failure in the default: target
        below.
    */
  case(ECL_SMSPEC_LOCAL_BLOCK_VAR):
    break;
  case(ECL_SMSPEC_LOCAL_COMPLETION_VAR):
    break;
  case(ECL_SMSPEC_LOCAL_WELL_VAR):
    break;
  case(ECL_SMSPEC_SEGMENT_VAR):
    break;
  case(ECL_SMSPEC_REGION_2_REGION_VAR):
    break;
  case(ECL_SMSPEC_AQUIFER_VAR):
    break;
  default:
    smspec_node_fprintf(smspec_node, stderr);
    util_abort("%: Internal error - should never be here ?? \n",__func__);
    break;
  }
}

/**
   The usage of this functon breaks down completely if LGR's are involved.
*/

bool ecl_smspec_needs_wgname( ecl_smspec_var_type var_type ) {
  switch( var_type ) {
  case(ECL_SMSPEC_COMPLETION_VAR):
    return true;
    break;
  case(ECL_SMSPEC_FIELD_VAR):
    return false;
    break;
  case(ECL_SMSPEC_GROUP_VAR):
    return true;
    break;
  case(ECL_SMSPEC_WELL_VAR):
    return true;
    break;
  case(ECL_SMSPEC_REGION_VAR):
    return false;
    break;
  case(ECL_SMSPEC_REGION_2_REGION_VAR):
    return false;
    break;
  case(ECL_SMSPEC_MISC_VAR):
    return false;
    break;
  case(ECL_SMSPEC_BLOCK_VAR):
    return false;
    break;
  case(ECL_SMSPEC_AQUIFER_VAR):
    return false;
    break;
  case(ECL_SMSPEC_SEGMENT_VAR):
    return true;
    break;
  default:
    util_exit("Sorry: support for variables of type:%s is not implemented in %s.\n",ecl_smspec_get_var_type_name( var_type ), __FILE__);
  }
  /* Really should not be here. */
  return false;
}


/**
   The usage of this functon breaks down completely if LGR's are involved.
*/
bool ecl_smspec_needs_num( ecl_smspec_var_type var_type ) {
  switch( var_type ) {
  case(ECL_SMSPEC_COMPLETION_VAR):
    return true;
    break;
  case(ECL_SMSPEC_AQUIFER_VAR):
    return true;
    break;
  case(ECL_SMSPEC_FIELD_VAR):
    return false;
    break;
  case(ECL_SMSPEC_GROUP_VAR):
    return false;
    break;
  case(ECL_SMSPEC_WELL_VAR):
    return false;
    break;
  case(ECL_SMSPEC_REGION_VAR):
    return true;
    break;
  case(ECL_SMSPEC_REGION_2_REGION_VAR):
    return true;
    break;
  case(ECL_SMSPEC_MISC_VAR):
    return false;
    break;
  case(ECL_SMSPEC_BLOCK_VAR):
    return true;
    break;
  default:
    util_exit("Sorry: support for variables of type:%s is not implemented in %s.\n",ecl_smspec_get_var_type_name( var_type ), __FILE__);
  }
  return false;
}




bool ecl_smspec_equal(const ecl_smspec_type * self,
                      const ecl_smspec_type * other) {
  if (vector_get_size( self->smspec_nodes ) != vector_get_size( other->smspec_nodes))
    return false;

  for (int i=0; i < vector_get_size( self->smspec_nodes ); i++) {
    const smspec_node_type * node1 = (const smspec_node_type*)vector_iget_const(self->smspec_nodes, i);
    const smspec_node_type * node2 = (const smspec_node_type*)vector_iget_const(other->smspec_nodes, i);

    if (!smspec_node_equal(node1, node2))
      return false;
  }

  return true;
}




static void ecl_smspec_load_restart( ecl_smspec_type * ecl_smspec , const ecl_file_type * header ) {
  if (ecl_file_has_kw( header , RESTART_KW )) {
    const ecl_kw_type * restart_kw = ecl_file_iget_named_kw(header, RESTART_KW , 0);
    char   tmp_base[73];   /* To accomodate a maximum of 9 items which consist of 8 characters each. */
    char * restart_base;
    int i;
    tmp_base[0] = '\0';
    for (i=0; i < ecl_kw_get_size( restart_kw ); i++)
      strcat( tmp_base , (const char*)ecl_kw_iget_ptr( restart_kw , i ));

    restart_base = util_alloc_strip_copy( tmp_base );
    if (strlen(restart_base)) {  /* We ignore the empty ones. */
      char * path;
      char * smspec_header;

      /*
        The conditional block here is to support the following situation:

           1. A simulation with a restart has been performed on Posix with path
              separator '/'.

           2. The simulation is loaded on windows, where the native path
              separator is '\'.

        This code block will translate '/' -> '\' in the restart keyword which
        is read from the summary file.
      */
#ifdef ERT_WINDOWS
      for (int i=0; i < strlen(restart_base); i++) {
        if (restart_base[i] == UTIL_POSIX_PATH_SEP_CHAR)
          restart_base[i] = UTIL_PATH_SEP_CHAR;
      }
#endif

      util_alloc_file_components( ecl_smspec->header_file , &path , NULL , NULL );
      smspec_header = ecl_util_alloc_exfilename( path , restart_base , ECL_SUMMARY_HEADER_FILE , ecl_smspec->formatted , 0);
      if (!util_same_file(smspec_header , ecl_smspec->header_file))    /* Restart from the current case is ignored. */ {
        if (util_is_abs_path(restart_base))
          ecl_smspec->restart_case = util_alloc_string_copy( restart_base );
        else {
          char * tmp_path = util_alloc_filename( path , restart_base , NULL );
          ecl_smspec->restart_case = util_alloc_abs_path(tmp_path);
          free( tmp_path );
        }
      }

      free( path );
      free( smspec_header );
    }
    free( restart_base );
  }
}



void ecl_smspec_index_node( ecl_smspec_type * ecl_smspec , smspec_node_type * smspec_node) {
  ecl_smspec_install_gen_keys( ecl_smspec , smspec_node );
  ecl_smspec_install_special_keys( ecl_smspec , smspec_node );

  if (smspec_node_need_nums( smspec_node ))
    ecl_smspec->need_nums = true;
}


static void ecl_smspec_set_params_size( ecl_smspec_type * ecl_smspec , int params_size) {
  ecl_smspec->params_size = params_size;
  float_vector_iset( ecl_smspec->params_default , ecl_smspec->params_size - 1 , PARAMS_GLOBAL_DEFAULT);
}



void ecl_smspec_insert_node(ecl_smspec_type * ecl_smspec, smspec_node_type * smspec_node){
  int internal_index = vector_get_size( ecl_smspec->smspec_nodes );

  /* This IF test should only apply in write_mode. */
  if (smspec_node_get_params_index( smspec_node ) < 0) {
    if (!ecl_smspec->write_mode)
      util_abort("%s: internal error \n",__func__);
    smspec_node_set_params_index( smspec_node , internal_index);

    if (internal_index >= ecl_smspec->params_size)
      ecl_smspec_set_params_size( ecl_smspec , internal_index + 1);
  }
  vector_append_owned_ref( ecl_smspec->smspec_nodes , smspec_node , smspec_node_free__ );

  {
    int params_index = smspec_node_get_params_index( smspec_node );

    /* This indexing must be used when writing. */
    int_vector_iset( ecl_smspec->index_map , internal_index , params_index);

    float_vector_iset( ecl_smspec->params_default , params_index , smspec_node_get_default(smspec_node) );
  }
}


void ecl_smspec_add_node( ecl_smspec_type * ecl_smspec , smspec_node_type * smspec_node ) {
  ecl_smspec_insert_node( ecl_smspec , smspec_node );
  ecl_smspec_index_node( ecl_smspec , smspec_node );
}



void ecl_smspec_init_var( ecl_smspec_type * ecl_smspec , smspec_node_type * smspec_node , const char * keyword , const char * wgname , int num, const char * unit ) {
  smspec_node_init( smspec_node , ecl_smspec_identify_var_type( keyword ) , wgname , keyword , unit , ecl_smspec->key_join_string , ecl_smspec->grid_dims , num );
  ecl_smspec_index_node( ecl_smspec , smspec_node );
}


const int_vector_type * ecl_smspec_get_index_map( const ecl_smspec_type * smspec ) {
  return smspec->index_map;
}

/**
 * This function is to support the NAMES alias for WGNAMES. If similar
 * situations occur in the future, this is a sane starting point for general
 * support.
 */
static const char * get_active_keyword_alias(ecl_file_type * header, const char * keyword) {
  if (strcmp(keyword, WGNAMES_KW) == 0 || strcmp(keyword, NAMES_KW) == 0)
    return ecl_file_has_kw(header, WGNAMES_KW) ? WGNAMES_KW : NAMES_KW;

  return keyword;
}

static bool ecl_smspec_check_header( ecl_file_type * header ) {
  bool OK = true;
  for (size_t i=0; i < num_req_keywords && OK; i++) {
    OK &= ecl_file_has_kw(
            header,
            get_active_keyword_alias(header, smspec_required_keywords[i])
            );
  }

  return OK;
}


static bool ecl_smspec_fread_header(ecl_smspec_type * ecl_smspec, const char * header_file , bool include_restart) {
  ecl_file_type * header = ecl_file_open( header_file , 0);
  if (header && ecl_smspec_check_header( header )) {
    const char * names_alias = get_active_keyword_alias(header, WGNAMES_KW);
    ecl_kw_type *wells       = ecl_file_iget_named_kw(header, names_alias , 0);
    ecl_kw_type *keywords    = ecl_file_iget_named_kw(header, KEYWORDS_KW , 0);
    ecl_kw_type *startdat    = ecl_file_iget_named_kw(header, STARTDAT_KW , 0);
    ecl_kw_type *units       = ecl_file_iget_named_kw(header, UNITS_KW    , 0);
    ecl_kw_type *dimens      = ecl_file_iget_named_kw(header, DIMENS_KW   , 0);
    ecl_kw_type *nums        = NULL;
    ecl_kw_type *lgrs        = NULL;
    ecl_kw_type *numlx       = NULL;
    ecl_kw_type *numly       = NULL;
    ecl_kw_type *numlz       = NULL;

    int params_index;
    ecl_smspec->num_regions     = 0;
    if (startdat == NULL)
      util_abort("%s: could not locate STARTDAT keyword in header - aborting \n",__func__);

    if (ecl_file_has_kw(header , NUMS_KW))
      nums = ecl_file_iget_named_kw(header , NUMS_KW , 0);

    if (ecl_file_has_kw(header, INTEHEAD_KW)) {
      const ecl_kw_type * intehead = ecl_file_iget_named_kw(header, INTEHEAD_KW, 0);
      ecl_smspec->unit_system = (ert_ecl_unit_enum)ecl_kw_iget_int(intehead, INTEHEAD_SMSPEC_UNIT_INDEX);
      /*
        The second item in the INTEHEAD vector is an integer designating which
        simulator has been used for the current simulation, that is currently
        ignored.
      */
    }

    if (ecl_file_has_kw( header , LGRS_KW )) {/* The file has LGR information. */
      lgrs  = ecl_file_iget_named_kw( header , LGRS_KW  , 0 );
      numlx = ecl_file_iget_named_kw( header , NUMLX_KW , 0 );
      numly = ecl_file_iget_named_kw( header , NUMLY_KW , 0 );
      numlz = ecl_file_iget_named_kw( header , NUMLZ_KW , 0 );
      ecl_smspec->has_lgr = true;
    } else
      ecl_smspec->has_lgr = false;

    {
      int * date = ecl_kw_get_int_ptr(startdat);
      ecl_smspec->sim_start_time = ecl_util_make_date(date[STARTDAT_DAY_INDEX]   ,
          date[STARTDAT_MONTH_INDEX] ,
          date[STARTDAT_YEAR_INDEX]);
    }

    ecl_smspec->grid_dims[0] = ecl_kw_iget_int(dimens , DIMENS_SMSPEC_NX_INDEX );
    ecl_smspec->grid_dims[1] = ecl_kw_iget_int(dimens , DIMENS_SMSPEC_NY_INDEX );
    ecl_smspec->grid_dims[2] = ecl_kw_iget_int(dimens , DIMENS_SMSPEC_NZ_INDEX );
    ecl_smspec->restart_step = ecl_kw_iget_int(dimens , DIMENS_SMSPEC_RESTART_STEP_INDEX);
    ecl_smspec_set_params_size( ecl_smspec , ecl_kw_get_size(keywords));

    ecl_util_get_file_type( header_file , &ecl_smspec->formatted , NULL );

    {
      for (params_index=0; params_index < ecl_kw_get_size(wells); params_index++) {
        float default_value          = PARAMS_GLOBAL_DEFAULT;
        int num                      = SMSPEC_NUMS_INVALID;
        char * well                  = (char*)util_alloc_strip_copy((const char*)ecl_kw_iget_ptr(wells    , params_index));
        char * kw                    = (char*)util_alloc_strip_copy((const char*)ecl_kw_iget_ptr(keywords , params_index));
        char * unit                  = (char*)util_alloc_strip_copy((const char*)ecl_kw_iget_ptr(units    , params_index));
        char * lgr_name              = NULL;

        smspec_node_type * smspec_node;
        ecl_smspec_var_type var_type = ecl_smspec_identify_var_type( kw );
        if (nums != NULL) num        = ecl_kw_iget_int(nums , params_index);
        if (ecl_smspec_lgr_var_type( var_type )) {
          int lgr_i = ecl_kw_iget_int( numlx , params_index );
          int lgr_j = ecl_kw_iget_int( numly , params_index );
          int lgr_k = ecl_kw_iget_int( numlz , params_index );
          lgr_name  = (char*)util_alloc_strip_copy(  (const char*)ecl_kw_iget_ptr( lgrs , params_index ));
          smspec_node = smspec_node_alloc_lgr( var_type , well , kw , unit , lgr_name , ecl_smspec->key_join_string , lgr_i , lgr_j , lgr_k , params_index, default_value);
        } else
          smspec_node = smspec_node_alloc( var_type , well , kw , unit , ecl_smspec->key_join_string , ecl_smspec->grid_dims , num , params_index , default_value);

        if (smspec_node)
          ecl_smspec_add_node( ecl_smspec , smspec_node );

        free( kw );
        free( well );
        free( unit );
        free( lgr_name );
      }
    }

    ecl_smspec->header_file = util_alloc_realpath( header_file );
    if (include_restart)
      ecl_smspec_load_restart( ecl_smspec , header );

    ecl_file_close( header );

    return true;
  } else
    return false;
}



ecl_smspec_type * ecl_smspec_fread_alloc(const char *header_file, const char * key_join_string , bool include_restart) {
  ecl_smspec_type *ecl_smspec;

  {
    char *path;
    util_alloc_file_components(header_file , &path , NULL , NULL);
    ecl_smspec = ecl_smspec_alloc_empty(false , key_join_string);
    free(path);
  }

  if (ecl_smspec_fread_header(ecl_smspec , header_file , include_restart)) {

    if (hash_has_key( ecl_smspec->misc_var_index , "TIME")) {
      const smspec_node_type * time_node = (const smspec_node_type*)hash_get(ecl_smspec->misc_var_index , "TIME");
      const char * time_unit = smspec_node_get_unit( time_node );
      ecl_smspec->time_index = smspec_node_get_params_index( time_node );

      if (util_string_equal( time_unit , "DAYS"))
        ecl_smspec->time_seconds = 3600 * 24;
      else if (util_string_equal( time_unit , "HOURS"))
        ecl_smspec->time_seconds = 3600;
      else
        util_abort("%s: time_unit:%s not recognized \n",__func__ , time_unit);
    }

    if (hash_has_key(ecl_smspec->misc_var_index , "DAY")) {
      ecl_smspec->day_index   = smspec_node_get_params_index( (const smspec_node_type*)hash_get(ecl_smspec->misc_var_index , "DAY") );
      ecl_smspec->month_index = smspec_node_get_params_index( (const smspec_node_type*)hash_get(ecl_smspec->misc_var_index , "MONTH") );
      ecl_smspec->year_index  = smspec_node_get_params_index( (const smspec_node_type*)hash_get(ecl_smspec->misc_var_index , "YEAR") );
    }

    if ((ecl_smspec->time_index == -1) && ( ecl_smspec->day_index == -1)) {
      /* Unusable configuration.

         Seems the ECLIPSE file can also have time specified with
         'YEARS' as basic time unit; that mode is not supported.
      */

      util_abort("%s: Sorry the SMSPEC file seems to lack all time information, need either TIME, or DAY/MONTH/YEAR information. Can not proceed.",__func__);
      return NULL;
    }
    return ecl_smspec;
  } else {
    /** Failed to load from disk. */
    ecl_smspec_free( ecl_smspec );
    return NULL;
  }
}


int ecl_smspec_get_num_groups(const ecl_smspec_type * ecl_smspec) {
  return hash_get_size(ecl_smspec->group_var_index);
}


char ** ecl_smspec_alloc_group_names(const ecl_smspec_type * ecl_smspec) {
  return hash_alloc_keylist(ecl_smspec->group_var_index);
}

int ecl_smspec_get_num_regions(const ecl_smspec_type * ecl_smspec) {
  return ecl_smspec->num_regions;
}



/******************************************************************/
/*
   For each type of summary data (according to the types in
   ecl_smcspec_var_type there are a set accessor functions:

   xx_get_xx: This function will take the apropriate input, and
   return a double value. The function will fail with util_abort()
   if the ecl_smspec object can not recognize the input. THis
   function is not here.

   xxx_has_xx: Ths will return true / false depending on whether the
   ecl_smspec object the variable we ask for.

   xxx_get_xxx_index: This function will rerturn an (internal)
   integer index of where the variable in question is stored, this
   index can then be subsequently used for faster lookup. If the
   variable can not be found, the function will return -1.

   In general the index function is the real function, the others are
   only wrappers around this. In addition there are specialized
   functions, like get_well_names() and so on.
*/


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

#define NODE_RETURN_INDEX(node)                         \
  if (node == NULL)                                     \
    return -1;                                          \
  else                                                  \
    return smspec_node_get_params_index( node );


#define NODE_RETURN_EXISTS(node)                \
  if (node == NULL)                             \
    return false;                               \
  else                                          \
    return true;



/******************************************************************/
/* Well variables */

const smspec_node_type * ecl_smspec_get_well_var_node( const ecl_smspec_type * smspec , const char * well , const char * var) {
  const smspec_node_type * node = NULL;
  if (hash_has_key( smspec->well_var_index , well)) {
    hash_type * var_hash = (hash_type*)hash_get(smspec->well_var_index , well);
    if (hash_has_key(var_hash , var))
      node = (const smspec_node_type*)hash_get(var_hash , var);
  }
  return node;
}


int ecl_smspec_get_well_var_params_index(const ecl_smspec_type * ecl_smspec , const char * well , const char *var) {
  const smspec_node_type * node = ecl_smspec_get_well_var_node( ecl_smspec , well , var );
  NODE_RETURN_INDEX(node);
}


bool ecl_smspec_has_well_var(const ecl_smspec_type * ecl_smspec , const char * well , const char *var) {
  const smspec_node_type * node = ecl_smspec_get_well_var_node(ecl_smspec , well ,var);
  NODE_RETURN_EXISTS(node);
}



/*****************************************************************/
/* Group variables */

const smspec_node_type * ecl_smspec_get_group_var_node( const ecl_smspec_type * smspec , const char * group , const char * var) {
  const smspec_node_type * node = NULL;
  if (hash_has_key(smspec->group_var_index , group)) {
    hash_type * var_hash = (hash_type*)hash_get(smspec->group_var_index , group);
    if (hash_has_key(var_hash , var))
      node = (const smspec_node_type*)hash_get(var_hash , var);
  }
  return node;
}


int ecl_smspec_get_group_var_params_index(const ecl_smspec_type * ecl_smspec , const char * group , const char *var) {
  const smspec_node_type * node = ecl_smspec_get_group_var_node( ecl_smspec , group , var );
  NODE_RETURN_INDEX(node);
}


bool ecl_smspec_has_group_var(const ecl_smspec_type * ecl_smspec , const char * group , const char *var) {
  const smspec_node_type * node = ecl_smspec_get_group_var_node(ecl_smspec , group ,var);
  NODE_RETURN_EXISTS(node);
}


/*****************************************************************/
/* Field variables */

const smspec_node_type * ecl_smspec_get_field_var_node(const ecl_smspec_type * ecl_smspec , const char *var) {
  const smspec_node_type * node = NULL;
  if (hash_has_key(ecl_smspec->field_var_index , var))
    node = (const smspec_node_type*)hash_get(ecl_smspec->field_var_index , var);

  return node;
}


int ecl_smspec_get_field_var_params_index(const ecl_smspec_type * ecl_smspec , const char *var) {
  const smspec_node_type * node = ecl_smspec_get_field_var_node( ecl_smspec , var );
  NODE_RETURN_INDEX(node);
}




bool ecl_smspec_has_field_var(const ecl_smspec_type * ecl_smspec , const char *var) {
  const smspec_node_type * node = ecl_smspec_get_field_var_node( ecl_smspec , var );
  NODE_RETURN_EXISTS(node);
}

/*****************************************************************/
/* Block variables */

/**
   Observe that block_nr is represented as char literal,
   i.e. "2345". This is because it will be used as a hash key.

   This is the final low level function which actually consults the
   hash tables.
*/

static const smspec_node_type * ecl_smspec_get_block_var_node_string(const ecl_smspec_type * ecl_smspec , const char * block_var , const char * block_str) {
  const smspec_node_type * node = NULL;

  if (hash_has_key(ecl_smspec->block_var_index , block_var)) {
    hash_type * block_hash = (hash_type*)hash_get(ecl_smspec->block_var_index , block_var);
    if (hash_has_key(block_hash , block_str))
      node = (const smspec_node_type*)hash_get(block_hash , block_str);
  }

  return node;
}


const smspec_node_type * ecl_smspec_get_block_var_node(const ecl_smspec_type * ecl_smspec , const char * block_var , int block_nr) {
  const smspec_node_type * node;
  char * block_str = util_alloc_sprintf("%d" , block_nr);
  node = ecl_smspec_get_block_var_node_string(ecl_smspec , block_var , block_str);
  free( block_str );
  return node;
}


const smspec_node_type * ecl_smspec_get_block_var_node_ijk(const ecl_smspec_type * ecl_smspec , const char * block_var , int i , int j , int k) {
  return ecl_smspec_get_block_var_node( ecl_smspec , block_var , ecl_smspec_get_global_grid_index( ecl_smspec , i,j,k) );
}


bool ecl_smspec_has_block_var(const ecl_smspec_type * ecl_smspec , const char * block_var , int block_nr) {
  const smspec_node_type * node = ecl_smspec_get_block_var_node( ecl_smspec , block_var , block_nr );
  NODE_RETURN_EXISTS(node);
}


bool ecl_smspec_has_block_var_ijk(const ecl_smspec_type * ecl_smspec , const char * block_var , int i , int j , int k) {
  const smspec_node_type * node = ecl_smspec_get_block_var_node_ijk( ecl_smspec , block_var , i,j,k );
  NODE_RETURN_EXISTS(node);
}


int ecl_smspec_get_block_var_params_index(const ecl_smspec_type * ecl_smspec , const char * block_var , int block_nr) {
  const smspec_node_type * node = ecl_smspec_get_block_var_node( ecl_smspec , block_var , block_nr );
  NODE_RETURN_INDEX(node);
}


int ecl_smspec_get_block_var_params_index_ijk(const ecl_smspec_type * ecl_smspec , const char * block_var , int i , int j , int k) {
  const smspec_node_type * node = ecl_smspec_get_block_var_node_ijk( ecl_smspec , block_var , i,j,k );
  NODE_RETURN_INDEX(node);
}


/*****************************************************************/
/* Region variables */
/**
   region_nr: [1...num_regions] (NOT C-based indexing)
*/



const smspec_node_type * ecl_smspec_get_region_var_node(const ecl_smspec_type * ecl_smspec , const char *region_var , int region_nr) {
  const smspec_node_type * node = NULL;

  if (hash_has_key(ecl_smspec->region_var_index , region_var)) {
    char * nr_str = util_alloc_sprintf( "%d" , region_nr );
    hash_type * nr_hash = (hash_type*)hash_get(ecl_smspec->region_var_index , region_var);
    if (hash_has_key( nr_hash , nr_str))
      node = (const smspec_node_type*)hash_get( nr_hash , nr_str );
    free( nr_str );
  }

  return node;
}


bool ecl_smspec_has_region_var(const ecl_smspec_type * ecl_smspec , const char *region_var, int region_nr) {
  const smspec_node_type * node = ecl_smspec_get_region_var_node( ecl_smspec , region_var , region_nr );
  NODE_RETURN_EXISTS(node);
}


int ecl_smspec_get_region_var_params_index(const ecl_smspec_type * ecl_smspec , const char *region_var, int region_nr) {
  const smspec_node_type * node = ecl_smspec_get_region_var_node( ecl_smspec , region_var , region_nr );
  NODE_RETURN_INDEX(node);
}

/*****************************************************************/
/* Misc variables */

const smspec_node_type * ecl_smspec_get_misc_var_node(const ecl_smspec_type * ecl_smspec , const char *var) {
  const smspec_node_type * node = NULL;

  if (hash_has_key(ecl_smspec->misc_var_index , var))
    node = (const smspec_node_type*)hash_get(ecl_smspec->misc_var_index , var);

  return node;
}


bool ecl_smspec_has_misc_var(const ecl_smspec_type * ecl_smspec , const char *var) {
  const smspec_node_type * node = ecl_smspec_get_misc_var_node( ecl_smspec , var );
  NODE_RETURN_EXISTS(node);
}

int ecl_smspec_get_misc_var_params_index(const ecl_smspec_type * ecl_smspec , const char *var) {
  const smspec_node_type * node = ecl_smspec_get_misc_var_node( ecl_smspec , var );
  NODE_RETURN_INDEX(node);
}


/*****************************************************************/
/* Well completion - not fully implemented ?? */


const smspec_node_type * ecl_smspec_get_well_completion_var_node(const ecl_smspec_type * ecl_smspec , const char * well , const char *var, int cell_nr) {
  const smspec_node_type * node = NULL;

  char * cell_str = util_alloc_sprintf("%d" , cell_nr);
  if (hash_has_key(ecl_smspec->well_completion_var_index , well)) {
    hash_type * cell_hash = (hash_type*)hash_get(ecl_smspec->well_completion_var_index , well);

    if (hash_has_key(cell_hash , cell_str)) {
      hash_type * var_hash = (hash_type*)hash_get(cell_hash , cell_str);
      if (hash_has_key(var_hash , var))
        node = (const smspec_node_type*)hash_get( var_hash , var);
    }
  }
  free(cell_str);
  return node;
}


bool  ecl_smspec_has_well_completion_var(const ecl_smspec_type * ecl_smspec , const char * well , const char *var, int cell_nr) {
  const smspec_node_type * node = ecl_smspec_get_well_completion_var_node( ecl_smspec , well , var , cell_nr );
  NODE_RETURN_EXISTS( node );
}


int  ecl_smspec_get_well_completion_var_params_index(const ecl_smspec_type * ecl_smspec , const char * well , const char *var, int cell_nr) {
  const smspec_node_type * node = ecl_smspec_get_well_completion_var_node( ecl_smspec , well , var , cell_nr );
  NODE_RETURN_INDEX( node );
}


/*****************************************************************/
/* General variables ... */


/* There is a quite wide range of error which are just returned as
   "Not found" (i.e. -1). */
/* Completions not supported yet. */



const smspec_node_type * ecl_smspec_get_general_var_node( const ecl_smspec_type * smspec , const char * lookup_kw ) {
  if (hash_has_key( smspec->gen_var_index , lookup_kw )) {
    const smspec_node_type * smspec_node = (const smspec_node_type*)hash_get( smspec->gen_var_index , lookup_kw );
    return smspec_node;
  } else
    return NULL;
}


int ecl_smspec_get_general_var_params_index(const ecl_smspec_type * ecl_smspec , const char * lookup_kw) {
  const smspec_node_type * node = ecl_smspec_get_general_var_node( ecl_smspec , lookup_kw );
  NODE_RETURN_INDEX( node );
}


bool ecl_smspec_has_general_var(const ecl_smspec_type * ecl_smspec , const char * lookup_kw) {
  const smspec_node_type * node = ecl_smspec_get_general_var_node( ecl_smspec , lookup_kw );
  NODE_RETURN_EXISTS( node );
}


/** DIES if the lookup_kw is not present. */
const char * ecl_smspec_get_general_var_unit( const ecl_smspec_type * ecl_smspec , const char * lookup_kw) {
  const smspec_node_type * smspec_node = (const smspec_node_type*)hash_get( ecl_smspec->gen_var_index , lookup_kw );
  return smspec_node_get_unit( smspec_node );
}


/*****************************************************************/
/*
   Pure indexed lookup - these functions can be used after one of the
   ecl_smspec_get_xxx_index() functions has been used first.
*/

//const char * ecl_smspec_iget_unit( const ecl_smspec_type * smspec , int node_index ) {
//  const smspec_node_type * smspec_node = ecl_smspec_iget_node( smspec , node_index );
//  return smspec_node_get_unit( smspec_node );
//}
//
//int ecl_smspec_iget_num( const ecl_smspec_type * smspec , int node_index ) {
//  const smspec_node_type * smspec_node = ecl_smspec_iget_node( smspec , node_index );
//  return smspec_node_get_num( smspec_node );
//}
//
//const char * ecl_smspec_iget_wgname( const ecl_smspec_type * smspec , int node_index ) {
//  const smspec_node_type * smspec_node = ecl_smspec_iget_node( smspec , node_index );
//  return smspec_node_get_wgname( smspec_node );
//}
//
//const char * ecl_smspec_iget_keyword( const ecl_smspec_type * smspec , int index ) {
//  const smspec_node_type * smspec_node = ecl_smspec_iget_node( smspec , index );
//  return smspec_node_get_keyword( smspec_node );
//}


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

int ecl_smspec_get_time_seconds( const ecl_smspec_type * ecl_smspec ) {
  return ecl_smspec->time_seconds;
}

int ecl_smspec_get_time_index( const ecl_smspec_type * ecl_smspec ) {
  return ecl_smspec->time_index;
}

time_t ecl_smspec_get_start_time(const ecl_smspec_type * ecl_smspec) {
  return ecl_smspec->sim_start_time;
}

bool ecl_smspec_get_formatted( const ecl_smspec_type * ecl_smspec) {
  return ecl_smspec->formatted;
}

const char * ecl_smspec_get_header_file( const ecl_smspec_type * ecl_smspec ) {
  return ecl_smspec->header_file;
}


int ecl_smspec_get_restart_step(const ecl_smspec_type * ecl_smspec) {
  return ecl_smspec->restart_step;
}

int ecl_smspec_get_first_step(const ecl_smspec_type * ecl_smspec) {
  if (ecl_smspec->restart_step > 0)
    return ecl_smspec->restart_step + 1;
  else
    return 1;
}


const char * ecl_smspec_get_restart_case( const ecl_smspec_type * ecl_smspec) {
  return ecl_smspec->restart_case;
}


const float_vector_type * ecl_smspec_get_params_default( const ecl_smspec_type * ecl_smspec ) {
  return ecl_smspec->params_default;
}



void ecl_smspec_free(ecl_smspec_type *ecl_smspec) {
  hash_free(ecl_smspec->well_var_index);
  hash_free(ecl_smspec->well_completion_var_index);
  hash_free(ecl_smspec->group_var_index);
  hash_free(ecl_smspec->field_var_index);
  hash_free(ecl_smspec->region_var_index);
  hash_free(ecl_smspec->misc_var_index);
  hash_free(ecl_smspec->block_var_index);
  hash_free(ecl_smspec->gen_var_index);
  free( ecl_smspec->header_file );
  int_vector_free( ecl_smspec->index_map );
  float_vector_free( ecl_smspec->params_default );
  vector_free( ecl_smspec->smspec_nodes );
  free( ecl_smspec->restart_case );
  free( ecl_smspec );
}


void ecl_smspec_free__(void * __ecl_smspec) {
  ecl_smspec_type * ecl_smspec = ecl_smspec_safe_cast( __ecl_smspec);
  ecl_smspec_free( ecl_smspec );
}


int ecl_smspec_get_date_day_index( const ecl_smspec_type * smspec ) {
  return smspec->day_index;
}

int ecl_smspec_get_date_month_index( const ecl_smspec_type * smspec ) {
  return smspec->month_index;
}

int ecl_smspec_get_date_year_index( const ecl_smspec_type * smspec ) {
  return smspec->year_index;
}



/**
   This function checks whether an input general key (i.e. FWPR or
   GGPT:NORTH) represents an accumulated total. If the variable is not
   internalized the function will fail hard.
*/


bool ecl_smspec_general_is_total( const ecl_smspec_type * smspec , const char * gen_key) {
  const  smspec_node_type * smspec_node = (const smspec_node_type*)hash_get( smspec->gen_var_index , gen_key );
  return smspec_node_is_total( smspec_node );
}




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


/**
   Fills a stringlist instance with all the gen_key string matching
   the supplied pattern. I.e.

     ecl_smspec_alloc_matching_general_var_list( smspec , "WGOR:*");

   will give a list of WGOR for ALL the wells. The function is
   unfortunately not as useful as one might think because ECLIPSE is
   quite stupid; it will for instance happily give you the WOPR for a
   water injector or WWIR for an oil producer.

   The function can be called several times with different patterns,
   the stringlist is not cleared on startup; the keys in the list are
   unique - keys are not added multiple times. If pattern == NULL all
   keys will match.
*/


void ecl_smspec_select_matching_general_var_list( const ecl_smspec_type * smspec , const char * pattern , stringlist_type * keys) {
  hash_type * ex_keys = hash_alloc( );
  int i;
  for (i=0; i < stringlist_get_size( keys ); i++)
    hash_insert_int( ex_keys , stringlist_iget( keys , i ) , 1);

  {
    hash_iter_type * iter = hash_iter_alloc( smspec->gen_var_index );
    while (!hash_iter_is_complete( iter )) {
      const char * key = hash_iter_get_next_key( iter );

      /*
         The TIME is typically special cased by output and will not
         match the 'all keys' wildcard.
      */
      if (util_string_equal( key , "TIME")) {
        if ((pattern == NULL) || (util_string_equal( pattern , "*")))
          continue;
      }


      if ((pattern == NULL) || (util_fnmatch( pattern , key ) == 0)) {
        if (!hash_has_key( ex_keys , key))
          stringlist_append_copy( keys , key );
      }
    }
    hash_iter_free( iter );
  }

  hash_free( ex_keys );
  stringlist_sort( keys , (string_cmp_ftype *) util_strcmp_int );
}


/**
   Allocates a new stringlist and initializes it with the
   ecl_smspec_select_matching_general_var_list() function.
*/

stringlist_type * ecl_smspec_alloc_matching_general_var_list(const ecl_smspec_type * smspec , const char * pattern) {
  stringlist_type * keys = stringlist_alloc_new();
  ecl_smspec_select_matching_general_var_list( smspec , pattern , keys );
  return keys;
}



const char * ecl_smspec_get_join_string( const ecl_smspec_type * smspec) {
  return smspec->key_join_string;
}



/**
    Returns a stringlist instance with all the (valid) well names. It
    is the responsability of the calling scope to free the stringlist
    with stringlist_free();


    If @pattern is different from NULL only wells which 'match' the
    pattern is included; if @pattern == NULL all wells are
    included. The match is done with function fnmatch() -
    i.e. standard shell wildcards.
*/

stringlist_type * ecl_smspec_alloc_well_list( const ecl_smspec_type * smspec , const char * pattern) {
  stringlist_type * well_list = stringlist_alloc_new( );
  {
    hash_iter_type * iter = hash_iter_alloc( smspec->well_var_index );

    while (!hash_iter_is_complete( iter )) {
      const char * well_name = hash_iter_get_next_key( iter );
      if (pattern == NULL)
        stringlist_append_copy( well_list , well_name );
      else if (util_fnmatch( pattern , well_name) == 0)
        stringlist_append_copy( well_list , well_name );
    }
    hash_iter_free(iter);
  }
  stringlist_sort( well_list , (string_cmp_ftype *) util_strcmp_int );
  return well_list;
}


/**
    Returns a stringlist instance with all the (valid) group names. It
    is the responsability of the calling scope to free the stringlist
    with stringlist_free();
*/

stringlist_type * ecl_smspec_alloc_group_list( const ecl_smspec_type * smspec , const char * pattern) {
  stringlist_type * group_list = stringlist_alloc_new( );
  {
    hash_iter_type * iter = hash_iter_alloc( smspec->group_var_index );

    while (!hash_iter_is_complete( iter )) {
      const char * group_name = hash_iter_get_next_key( iter );
      if (pattern == NULL)
        stringlist_append_copy( group_list , group_name );
      else if (util_fnmatch( pattern , group_name) == 0)
        stringlist_append_copy( group_list , group_name );
    }
    hash_iter_free(iter);
  }
  stringlist_sort( group_list , (string_cmp_ftype *) util_strcmp_int );
  return group_list;
}



/**
    Returns a stringlist instance with all the well variables.  It is
    the responsability of the calling scope to free the stringlist
    with stringlist_free();
*/

stringlist_type * ecl_smspec_alloc_well_var_list( const ecl_smspec_type * smspec ) {
  hash_iter_type * well_iter = hash_iter_alloc( smspec->well_var_index );
  hash_type      * var_hash = (hash_type*)hash_iter_get_next_value( well_iter );
  hash_iter_free( well_iter );
  return hash_alloc_stringlist( var_hash );
}



int ecl_smspec_get_params_size( const ecl_smspec_type * smspec ) {
  return smspec->params_size;
}



const int * ecl_smspec_get_grid_dims( const ecl_smspec_type * smspec ) {
  return smspec->grid_dims;
}




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

char * ecl_smspec_alloc_well_key( const ecl_smspec_type * smspec , const char * keyword , const char * wgname) {
  return smspec_alloc_well_key( smspec->key_join_string , keyword , wgname );
}


void ecl_smspec_sort( ecl_smspec_type * smspec ) {
  vector_sort( smspec->smspec_nodes , smspec_node_cmp__);

  for (int i=0; i < vector_get_size( smspec->smspec_nodes ); i++) {
    smspec_node_type * node = (smspec_node_type*)vector_iget( smspec->smspec_nodes , i );
    smspec_node_set_params_index( node , i );
  }

}

ert_ecl_unit_enum ecl_smspec_get_unit_system(const ecl_smspec_type * smspec) {
  return smspec->unit_system;
}
