/*
 * Copyright (c) 2019 Clementine Computing LLC.
 *
 * This file is part of PopuFare.
 *
 * PopuFare is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * PopuFare 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with PopuFare.  If not, see <https://www.gnu.org/licenses/>.
 *
 */

#include "passdb_slim.h"

// SHALLOW COPY
// passdb_slim_config is a shallow container.  All memory management
// should be done from ctx.
//
void passdb_slim_copy_config( passdb_slim_config *cfg, passdb_slim_context *ctx )
{
    cfg->hash_modulus             = ctx->hash_modulus;
    cfg->n_one_cred_bank          = ctx->n_one_cred_bank;
    cfg->n_two_cred_bank          = ctx->n_two_cred_bank;
    cfg->n_spillover_bank          = ctx->n_spillover_bank;

    cfg->n_one_cred_bank_size     = ctx->n_one_cred_bank_size;
    cfg->n_two_cred_bank_size     = ctx->n_two_cred_bank_size;
    cfg->n_spillover_bank_size     = ctx->n_spillover_bank_size;

    cfg->n_one_cred_max_bank      = ctx->n_one_cred_max_bank;
    cfg->n_two_cred_max_bank      = ctx->n_two_cred_max_bank;
    cfg->n_spillover_max_bank      = ctx->n_spillover_max_bank;

    cfg->one_cred_db_base_fn      = ctx->one_cred_db_base_fn;
    cfg->two_cred_db_base_fn      = ctx->two_cred_db_base_fn;
    cfg->spillover_db_base_fn      = ctx->spillover_db_base_fn;

    cfg->db_fn_suffix             = ctx->db_fn_suffix;
    cfg->ruleparam_db_fn          = ctx->ruleparam_db_fn;
    cfg->high_watermark_threshold = ctx->high_watermark_threshold;
    cfg->low_watermark_threshold  = ctx->low_watermark_threshold;
}

#define DEFAULT_HASH_MODULUS 131101

#define DEFAULT_ONE_CRED_MAX_BANK 25
#define DEFAULT_TWO_CRED_MAX_BANK 25
#define DEFAULT_SPILLOVER_MAX_BANK 25

#define DEFAULT_ONE_CRED_BANK_SIZE 10012
#define DEFAULT_TWO_CRED_BANK_SIZE 10012
#define DEFAULT_SPILLOVER_BANK_SIZE 32

#define DEFAULT_ONE_CRED_BANK 1
#define DEFAULT_TWO_CRED_BANK 1
#define DEFAULT_SPILLOVER_BANK 1

#define DEFAULT_ONE_CRED_DB_BASE DATABASE_FILE_PATH "passes_one_bank"
#define DEFAULT_TWO_CRED_DB_BASE DATABASE_FILE_PATH "passes_two_bank"
#define DEFAULT_SPILLOVER_DB_BASE DATABASE_FILE_PATH "passes_spillover_bank"

#define DEFAULT_DB_SUFFIX ".mem"

#define DEFAULT_RULEPARAM_DB DATABASE_FILE_PATH "ruleparam.db"

#define DEFAULT_HIGH_WATERMARK 0.95

//unused
//
#define DEFAULT_LOW_WATERMARK 0.25


int make_default_config( char *config_fn )
{
  FILE *fp;

  struct tm tm_tim;
  struct timeval timval;
  char str_tim[FN_SZ];

  gettimeofday(&timval, NULL);
  localtime_r(&(timval.tv_sec), &tm_tim);
  asctime_r(&tm_tim, str_tim);


  fp = fopen(config_fn, "w");
  if (!fp)
    return -1;

  fprintf(fp, "# %s", str_tim );
  fprintf(fp, "#\n");
  fprintf(fp, "#\n");

  fprintf(fp, "hash_modulus %i\n", DEFAULT_HASH_MODULUS);
  fprintf(fp, "\n");

  fprintf(fp, "n_one_cred_max_bank %i\n", DEFAULT_ONE_CRED_MAX_BANK );
  fprintf(fp, "n_two_cred_max_bank %i\n", DEFAULT_TWO_CRED_MAX_BANK );
  fprintf(fp, "n_spillover_max_bank %i\n", DEFAULT_SPILLOVER_MAX_BANK );
  fprintf(fp, "\n");

  fprintf(fp, "n_one_cred_bank_size %i\n", DEFAULT_ONE_CRED_BANK_SIZE );
  fprintf(fp, "n_two_cred_bank_size %i\n", DEFAULT_TWO_CRED_BANK_SIZE );
  fprintf(fp, "n_spillover_bank_size %i\n", DEFAULT_SPILLOVER_BANK_SIZE );
  fprintf(fp, "\n");

  fprintf(fp, "n_one_cred_bank %i\n", DEFAULT_ONE_CRED_BANK );
  fprintf(fp, "n_two_cred_bank %i\n", DEFAULT_TWO_CRED_BANK );
  fprintf(fp, "n_spillover_bank %i\n", DEFAULT_SPILLOVER_BANK );
  fprintf(fp, "\n");

  fprintf(fp, "one_cred_db_base %s\n", DEFAULT_ONE_CRED_DB_BASE );
  fprintf(fp, "two_cred_db_base %s\n", DEFAULT_TWO_CRED_DB_BASE );
  fprintf(fp, "spillover_db_base %s\n", DEFAULT_SPILLOVER_DB_BASE );
  fprintf(fp, "\n");

  fprintf(fp, "db_suffix %s\n", DEFAULT_DB_SUFFIX );
  fprintf(fp, "\n");

  fprintf(fp, "ruleparam_db %s\n", DEFAULT_RULEPARAM_DB );
  fprintf(fp, "\n");

  fprintf(fp, "high_watermark_threshold %f\n", DEFAULT_HIGH_WATERMARK );
  fprintf(fp, "low_watermark_threshold %f\n", DEFAULT_LOW_WATERMARK );
  fprintf(fp, "\n");

  fclose(fp);

  return 0;
}

int save_config( passdb_slim_config *cfg, char *config_fn )
{
  FILE *fp;

  struct tm tm_tim;
  struct timeval timval;
  char str_tim[FN_SZ];

  gettimeofday(&timval, NULL);
  localtime_r(&(timval.tv_sec), &tm_tim);
  asctime_r(&tm_tim, str_tim);


  fp = fopen( config_fn, "w" );
  if (!fp)
  {
    //DEBUG
    //fprintf(stderr, "ERROR: passdb.save_config: could not open file %s\n", config_fn);
    //exit(1);

    perror(config_fn);
    return -1;
  }

  fprintf(fp, "# %s", str_tim );
  fprintf(fp, "#\n");
  fprintf(fp, "#\n");

  fprintf(fp, "hash_modulus %i\n", cfg->hash_modulus);
  fprintf(fp, "\n");

  fprintf(fp, "n_one_cred_max_bank %i\n", cfg->n_one_cred_max_bank);
  fprintf(fp, "n_two_cred_max_bank %i\n", cfg->n_two_cred_max_bank);
  fprintf(fp, "n_spillover_max_bank %i\n", cfg->n_spillover_max_bank);
  fprintf(fp, "\n");

  fprintf(fp, "n_one_cred_bank_size %i\n", cfg->n_one_cred_bank_size);
  fprintf(fp, "n_two_cred_bank_size %i\n", cfg->n_two_cred_bank_size);
  fprintf(fp, "n_spillover_bank_size %i\n", cfg->n_spillover_bank_size);
  fprintf(fp, "\n");

  fprintf(fp, "n_one_cred_bank %i\n", cfg->n_one_cred_bank);
  fprintf(fp, "n_two_cred_bank %i\n", cfg->n_two_cred_bank);
  fprintf(fp, "n_spillover_bank %i\n", cfg->n_spillover_bank);
  fprintf(fp, "\n");

  fprintf(fp, "one_cred_db_base %s\n", cfg->one_cred_db_base_fn);
  fprintf(fp, "two_cred_db_base %s\n", cfg->two_cred_db_base_fn);
  fprintf(fp, "spillover_db_base %s\n", cfg->spillover_db_base_fn);
  fprintf(fp, "\n");

  fprintf(fp, "db_suffix %s\n", cfg->db_fn_suffix);
  fprintf(fp, "\n");

  fprintf(fp, "ruleparam_db %s\n", cfg->ruleparam_db_fn);
  fprintf(fp, "\n");

  fprintf(fp, "high_watermark_threshold %f\n", (double)cfg->high_watermark_threshold );
  fprintf(fp, "low_watermark_threshold %f\n", (double)cfg->low_watermark_threshold );
  fprintf(fp, "\n");

  fclose(fp);

  return 0;

}




int read_config( passdb_slim_config *cfg, char *config_fn )
{
  FILE *fp;
  char buf[FN_SZ];
  char keyword[FN_SZ], rvalue[FN_SZ];
  int r;

  cfg->hash_modulus = -1;

  cfg->n_one_cred_bank_size = -1;
  cfg->n_two_cred_bank_size = -1;
  cfg->n_spillover_bank_size = -1;

  cfg->n_one_cred_bank = -1;
  cfg->n_two_cred_bank = -1;
  cfg->n_spillover_bank = -1;

  cfg->one_cred_db_base_fn = NULL;
  cfg->two_cred_db_base_fn = NULL;
  cfg->spillover_db_base_fn = NULL;
  cfg->db_fn_suffix = NULL;

  cfg->high_watermark_threshold = 0.99;
  cfg->low_watermark_threshold = 0.1;

  cfg->ruleparam_db_fn = NULL;

  fp = fopen(config_fn, "r");
  if (!fp)
    return -1;

  while ( fgets(buf, FN_SZ-1, fp) )
  {
    if (buf[0] == '#')
      continue;
    r = sscanf(buf, "%s %s", keyword, rvalue);

    if (keyword[0] == '#')
      continue;

    if (r!=2)
      continue;

    if ( strncmp(keyword, "hash_modulus", FN_SZ-1) == 0 )
    {
      cfg->hash_modulus = atoi(rvalue);
    }

    else if (strncmp(keyword, "n_one_cred_bank_size", FN_SZ-1) == 0 )
    {
      cfg->n_one_cred_bank_size = atoi(rvalue);
    }
    else if (strncmp(keyword, "n_two_cred_bank_size", FN_SZ-1) == 0 )
    {
      cfg->n_two_cred_bank_size = atoi(rvalue);
    }
    else if (strncmp(keyword, "n_spillover_bank_size", FN_SZ-1) == 0 )
    {
      cfg->n_spillover_bank_size = atoi(rvalue);
    }


    else if (strncmp(keyword, "n_one_cred_bank", FN_SZ-1) == 0 )
    {
      cfg->n_one_cred_bank = atoi(rvalue);
    }
    else if (strncmp(keyword, "n_two_cred_bank", FN_SZ-1) == 0 )
    {
      cfg->n_two_cred_bank = atoi(rvalue);
    }
    else if (strncmp(keyword, "n_spillover_bank", FN_SZ-1) == 0 )
    {
      cfg->n_spillover_bank = atoi(rvalue);
    }

    else if (strncmp(keyword, "n_one_cred_max_bank", FN_SZ-1) == 0 )
    {
      cfg->n_one_cred_max_bank = atoi(rvalue);
    }
    else if (strncmp(keyword, "n_two_cred_max_bank", FN_SZ-1) == 0 )
    {
      cfg->n_two_cred_max_bank = atoi(rvalue);
    }
    else if (strncmp(keyword, "n_spillover_max_bank", FN_SZ-1) == 0 )
    {
      cfg->n_spillover_max_bank = atoi(rvalue);
    }

    else if (strncmp(keyword, "one_cred_db_base", FN_SZ-1) == 0)
    {
      cfg->one_cred_db_base_fn = strdup(rvalue);
    }
    else if (strncmp(keyword, "two_cred_db_base", FN_SZ-1) == 0)
    {
      cfg->two_cred_db_base_fn = strdup(rvalue);
    }
    else if (strncmp(keyword, "spillover_db_base", FN_SZ-1) == 0)
    {
      cfg->spillover_db_base_fn = strdup(rvalue);
    }

    else if (strncmp(keyword, "db_suffix", FN_SZ-1) == 0)
    {
      cfg->db_fn_suffix = strdup(rvalue);
    }
    else if (strncmp(keyword, "ruleparam_db", FN_SZ-1) == 0)
    {
      cfg->ruleparam_db_fn = strdup(rvalue);
    }

    else if (strncmp(keyword, "high_watermark_threshold", FN_SZ-1) == 0)
    {
      cfg->high_watermark_threshold = atof(rvalue);
    }
    else if (strncmp(keyword, "low_watermark_threshold", FN_SZ-1) == 0)
    {
      cfg->low_watermark_threshold = atof(rvalue);
    }

  }
  fclose(fp);


  if (!cfg->ruleparam_db_fn)
    cfg->ruleparam_db_fn = strdup( RULEPARAM_DB_FILE );


  // If there were any errors, bail out
  //
  if ( (cfg->hash_modulus <= 0) ||

       (cfg->n_one_cred_max_bank <= 0) ||
       (cfg->n_two_cred_max_bank <= 0) ||
       (cfg->n_spillover_max_bank <= 0) ||

       (cfg->n_one_cred_bank_size <= 0) ||
       (cfg->n_two_cred_bank_size <= 0) ||
       (cfg->n_spillover_bank_size <= 0) ||

       (cfg->n_one_cred_bank <= 0) ||
       (cfg->n_two_cred_bank <= 0) ||
       (cfg->n_spillover_bank <= 0) ||

       (cfg->high_watermark_threshold <= 0.0) ||
       (cfg->high_watermark_threshold >= 1.0) ||

       (cfg->low_watermark_threshold <= 0.0) ||
       (cfg->low_watermark_threshold >= cfg->high_watermark_threshold) ||

       (!cfg->one_cred_db_base_fn) ||
       (!cfg->two_cred_db_base_fn) ||
       (!cfg->spillover_db_base_fn) ||
       (!cfg->db_fn_suffix) ||

       (!cfg->ruleparam_db_fn)
       )
  {

    if (cfg->one_cred_db_base_fn) free(cfg->one_cred_db_base_fn);
    if (cfg->two_cred_db_base_fn) free(cfg->two_cred_db_base_fn);
    if (cfg->spillover_db_base_fn) free(cfg->spillover_db_base_fn);
    if (cfg->db_fn_suffix)        free(cfg->db_fn_suffix);

    if (cfg->ruleparam_db_fn) free(cfg->ruleparam_db_fn);
    return -1;
  }


  return 0;
}


// Dead simple config file parsing
//
int init_context_from_config( passdb_slim_context *ctx, char *config_fn )
{

  int i, r;

  // Don't copy new values over until we're sure config file read
  // was successful.  Store in local variables and copy over
  // afterwards.
  //
  passdb_slim_config cfg;

  r = read_config(&cfg, config_fn);
  if (r<0)
    return r;

  ctx->n_one_cred_bank = cfg.n_one_cred_bank;
  ctx->n_two_cred_bank = cfg.n_two_cred_bank;
  ctx->n_spillover_bank = cfg.n_spillover_bank;

  ctx->n_one_cred_max_bank = cfg.n_one_cred_max_bank;
  ctx->n_two_cred_max_bank = cfg.n_two_cred_max_bank;
  ctx->n_spillover_max_bank = cfg.n_spillover_max_bank;

  ctx->n_one_cred_bank_size = cfg.n_one_cred_bank_size;
  ctx->n_two_cred_bank_size = cfg.n_two_cred_bank_size;
  ctx->n_spillover_bank_size = cfg.n_spillover_bank_size;

  ctx->hash_modulus = cfg.hash_modulus;

  // Get rid of stale filenames
  //
  if (ctx->one_cred_db_bank_fn)
  {
    for (i=0; i<ctx->n_one_cred_max_bank; i++)
      if (ctx->one_cred_db_bank_fn[i])
        free(ctx->one_cred_db_bank_fn[i]);
    free(ctx->one_cred_db_bank_fn);
  }

  if (ctx->two_cred_db_bank_fn)
  {
    for (i=0; i<ctx->n_two_cred_max_bank; i++)
      if (ctx->two_cred_db_bank_fn[i])
        free(ctx->two_cred_db_bank_fn[i]);
    free(ctx->two_cred_db_bank_fn);
  }

  if (ctx->spillover_db_bank_fn)
  {
    for (i=0; i<ctx->n_spillover_max_bank; i++)
      if (ctx->spillover_db_bank_fn[i])
        free(ctx->spillover_db_bank_fn[i]);
    free(ctx->spillover_db_bank_fn);
  }

  if (ctx->one_cred_db_base_fn) free(ctx->one_cred_db_base_fn);
  if (ctx->two_cred_db_base_fn) free(ctx->two_cred_db_base_fn);
  if (ctx->spillover_db_base_fn) free(ctx->spillover_db_base_fn);
  if (ctx->db_fn_suffix) free(ctx->db_fn_suffix);

  if (ctx->ruleparam_db_fn) free(ctx->ruleparam_db_fn);

  if (ctx->logical_card_id_hash) free(ctx->logical_card_id_hash);
  if (ctx->rider_mag_hash) free(ctx->rider_mag_hash);
  if (ctx->rider_rf_hash) free(ctx->rider_rf_hash);

  ctx->one_cred_db_base_fn = cfg.one_cred_db_base_fn;
  ctx->two_cred_db_base_fn = cfg.two_cred_db_base_fn;
  ctx->spillover_db_base_fn = cfg.spillover_db_base_fn;
  ctx->db_fn_suffix = cfg.db_fn_suffix;

  ctx->ruleparam_db_fn = cfg.ruleparam_db_fn;

  ctx->high_watermark_threshold = cfg.high_watermark_threshold;
  ctx->low_watermark_threshold = cfg.low_watermark_threshold;


  //---------------------------------------
  // ONE CREDENTIAL bank
  //  - filenames
  //  - file descriptors
  //  - bank (memory)
  //


  // allocate all filenames, even if we don't create the actual DB files
  //
  ctx->one_cred_db_bank_fn = (char **)malloc(sizeof(char *) * (ctx->n_one_cred_max_bank));
  CHECK_ALLOC_FAIL( (ctx->one_cred_db_bank_fn) );

  for (i=0; i<ctx->n_one_cred_max_bank; i++)
  {
    ctx->one_cred_db_bank_fn[i] = (char *)malloc(sizeof(char)*FN_SZ);
    CHECK_ALLOC_FAIL( (ctx->one_cred_db_bank_fn[i]) );

    snprintf(ctx->one_cred_db_bank_fn[i], FN_SZ, "%s%i%s", ctx->one_cred_db_base_fn, i, ctx->db_fn_suffix );
  }

  ctx->passes_one_cred_bank_fd = (int *)malloc(sizeof(int) * ctx->n_one_cred_max_bank );
  CHECK_ALLOC_FAIL( (ctx->passes_one_cred_bank_fd) );

  for (i=0; i<ctx->n_one_cred_max_bank; i++)
    ctx->passes_one_cred_bank_fd[i] = -1;

  ctx->rider_one_cred_bank = (void **)malloc(sizeof(void *) * ctx->n_one_cred_max_bank );
  CHECK_ALLOC_FAIL( (ctx->rider_one_cred_bank) );

  for (i=0; i<ctx->n_one_cred_max_bank; i++)
    ctx->rider_one_cred_bank[i] = NULL;


  //---------------------------------------
  // TWO CREDENTIAL bank
  //  - filenames
  //  - file descriptors
  //  - bank (memory)
  //

  // allocate all filenames, even if we don't create the actual DB files
  //
  ctx->two_cred_db_bank_fn = (char **)malloc(sizeof(char *) * (ctx->n_two_cred_max_bank));
  CHECK_ALLOC_FAIL( (ctx->two_cred_db_bank_fn) );

  for (i=0; i<ctx->n_two_cred_max_bank; i++)
  {
    ctx->two_cred_db_bank_fn[i] = (char *)malloc(sizeof(char)*FN_SZ);
    CHECK_ALLOC_FAIL( (ctx->two_cred_db_bank_fn[i]) );

    snprintf(ctx->two_cred_db_bank_fn[i], FN_SZ, "%s%i%s", ctx->two_cred_db_base_fn, i, ctx->db_fn_suffix );
  }

  ctx->passes_two_cred_bank_fd = (int *)malloc(sizeof(int) * ctx->n_two_cred_max_bank );
  CHECK_ALLOC_FAIL( (ctx->passes_two_cred_bank_fd) );

  for (i=0; i<ctx->n_two_cred_max_bank; i++)
    ctx->passes_two_cred_bank_fd[i] = -1;

  ctx->rider_two_cred_bank = (void **)malloc(sizeof(void *) * ctx->n_two_cred_max_bank );
  CHECK_ALLOC_FAIL( (ctx->rider_two_cred_bank) );

  for (i=0; i<ctx->n_two_cred_max_bank; i++)
    ctx->rider_two_cred_bank[i] = NULL;


  //---------------------------------------
  // SPILLOVER 
  //  - filenames
  //  - file descriptors
  //  - bank (memory)
  //

  // allocate all filenames, even if we don't create the actual DB files
  //
  ctx->spillover_db_bank_fn = (char **)malloc(sizeof(char *) * (ctx->n_spillover_max_bank));
  CHECK_ALLOC_FAIL( (ctx->spillover_db_bank_fn) );

  for (i=0; i<ctx->n_spillover_max_bank; i++)
  {
    ctx->spillover_db_bank_fn[i] = (char *)malloc(sizeof(char)*FN_SZ);
    CHECK_ALLOC_FAIL( (ctx->spillover_db_bank_fn[i]) );

    snprintf(ctx->spillover_db_bank_fn[i], FN_SZ, "%s%i%s", ctx->spillover_db_base_fn, i, ctx->db_fn_suffix );
  }

  ctx->passes_spillover_bank_fd = (int *)malloc(sizeof(int) * ctx->n_spillover_max_bank );
  CHECK_ALLOC_FAIL( (ctx->passes_spillover_bank_fd) );

  for (i=0; i<ctx->n_spillover_max_bank; i++)
    ctx->passes_spillover_bank_fd[i] = -1;

  ctx->rider_spillover_bank = (void **)malloc(sizeof(void *) * ctx->n_spillover_max_bank );
  CHECK_ALLOC_FAIL( (ctx->rider_spillover_bank) );

  for (i=0; i<ctx->n_spillover_max_bank; i++)
    ctx->rider_spillover_bank[i] = NULL;


  ctx->logical_card_id_hash = (rider_node **)malloc(sizeof(rider_node *) * ctx->hash_modulus );
  CHECK_ALLOC_FAIL( (ctx->logical_card_id_hash) );

  ctx->rider_mag_hash       = (rider_node **)malloc(sizeof(rider_node *) * ctx->hash_modulus );
  CHECK_ALLOC_FAIL( (ctx->rider_mag_hash) );

  ctx->rider_rf_hash        = (rider_node **)malloc(sizeof(rider_node *) * ctx->hash_modulus );
  CHECK_ALLOC_FAIL( (ctx->rider_rf_hash) );


  for (i=0; i<ctx->hash_modulus; i++)
  {
    ctx->logical_card_id_hash[i] = NULL;
    ctx->rider_mag_hash[i] = NULL;
    ctx->rider_rf_hash[i] = NULL;
  }

  return 0;
}

