/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
/*
  Copyright 2013-2021 The Libfossil Authors, see LICENSES/BSD-2-Clause.txt

  SPDX-License-Identifier: BSD-2-Clause-FreeBSD
  SPDX-FileCopyrightText: 2021 The Libfossil Authors
  SPDX-ArtifactOfProjectName: Libfossil
  SPDX-FileType: Code

  Heavily indebted to the Fossil SCM project (https://fossil-scm.org).
*/
/*****************************************************************************
  This file some of the caching-related APIs.
*/
#include "fossil-scm/fossil-internal.h"
#include <assert.h>

/* Only for debugging */
#include <stdio.h>
#define MARKER(pfexp)                                               \
  do{ printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__);   \
    printf pfexp;                                                   \
  } while(0)

bool fsl__bccache_expire_oldest(fsl__bccache * const c){
  static uint16_t const sentinel = 0xFFFF;
  uint16_t i;
  fsl_uint_t mnAge = c->nextAge;
  uint16_t mn = sentinel;
  for(i=0; i<c->used; i++){
    if( c->list[i].age<mnAge ){
      mnAge = c->list[i].age;
      mn = i;
    }
  }
  if( mn<sentinel ){
    fsl_id_bag_remove(&c->inCache, c->list[mn].rid);
    c->szTotal -= (unsigned)c->list[mn].content.capacity;
    fsl_buffer_clear(&c->list[mn].content);
    --c->used;
    c->list[mn] = c->list[c->used];
  }
  return sentinel!=mn;
}

int fsl__bccache_insert(fsl__bccache * const c, fsl_id_t rid, fsl_buffer * const pBlob){
  fsl__bccache_line *p;
  if( c->used>c->usedLimit || c->szTotal>c->szLimit ){
    fsl_size_t szBefore;
    do{
      szBefore = c->szTotal;
      fsl__bccache_expire_oldest(c);
    }while( c->szTotal>c->szLimit && c->szTotal<szBefore );
  }
  if((!c->usedLimit || !c->szLimit)
     || (c->used+1 >= c->usedLimit)){
    fsl_buffer_clear(pBlob);
    return 0;
  }
  if( c->used>=c->capacity ){
    uint16_t const cap = c->capacity ? (c->capacity*2) : 10;
    void * remem = c->list
      ? fsl_realloc(c->list, cap*sizeof(c->list[0]))
      : fsl_malloc( cap*sizeof(c->list[0]) );
    assert((c->capacity && cap<c->capacity) ? !"Numeric overflow" : 1);
    if(c->capacity && cap<c->capacity){
      fsl__fatal(FSL_RC_RANGE,"Numeric overflow. Bump "
                 "fsl__bccache::capacity to a larger int type.");
    }
    if(!remem){
      fsl_buffer_clear(pBlob) /* for consistency */;
      return FSL_RC_OOM;
    }
    c->capacity = cap;
    c->list = (fsl__bccache_line*)remem;
  }
  int const rc = fsl_id_bag_insert(&c->inCache, rid);
  if(0==rc){
    p = &c->list[c->used++];
    p->rid = rid;
    p->age = c->nextAge++;
    c->szTotal += pBlob->capacity;
    p->content = *pBlob /* Transfer ownership */;
    *pBlob = fsl_buffer_empty;
  }else{
    fsl_buffer_clear(pBlob);
  }
  return rc;
}


void fsl__bccache_clear(fsl__bccache * const c){
#if 0
  while(fsl__bccache_expire_oldest(c)){}
#else
  fsl_size_t i;
  for(i=0; i<c->used; ++i){
    fsl_buffer_clear(&c->list[i].content);
  }
#endif
  fsl_free(c->list);
  fsl_id_bag_clear(&c->missing);
  fsl_id_bag_clear(&c->available);
  fsl_id_bag_clear(&c->inCache);
  *c = fsl__bccache_empty;
}

void fsl__bccache_reset(fsl__bccache * const c){
  static const fsl__bccache_line line_empty = fsl__bccache_line_empty_m;
  fsl_size_t i;
  for(i=0; i<c->used; ++i){
    fsl_buffer_clear(&c->list[i].content);
    c->list[i] = line_empty;
  }
  c->used = 0;
  c->szTotal = 0;
  c->nextAge = 0;
  fsl_id_bag_reset(&c->missing);
  fsl_id_bag_reset(&c->available);
  fsl_id_bag_reset(&c->inCache);
}


int fsl__bccache_check_available(fsl_cx * const f, fsl_id_t rid){
  fsl_id_t srcid;
  int depth = 0;  /* Limit to recursion depth */
  static const int limit = 10000000 /* historical value */;
  int rc;
  fsl__bccache * const c = &f->cache.blobContent;
  assert(f);
  assert(c);
  assert(rid>0);
  assert(fsl_cx_db_repo(f));
  while( depth++ < limit ){  
    fsl_int_t cSize = -1;
    if( fsl_id_bag_contains(&c->missing, rid) ){
      return FSL_RC_NOT_FOUND;
    }
    else if( fsl_id_bag_contains(&c->available, rid) ){
      return 0;
    }
    else if( (cSize=fsl_content_size(f, rid)) <0){
      rc = fsl_id_bag_insert(&c->missing, rid);
      return rc ? rc : FSL_RC_NOT_FOUND;
    }
    srcid = 0;
    rc = fsl_delta_src_id(f, rid, &srcid);
    if(rc) return rc;
    else if( srcid==0 ){
      rc = fsl_id_bag_insert(&c->available, rid);
      return rc ? rc : 0;
    }
    rid = srcid;
  }
  assert(!"delta-loop in repository");
  return fsl_cx_err_set(f, FSL_RC_CONSISTENCY,
                        "Serious problem: delta-loop in repository");
}

#undef MARKER
