/* -*- 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
*/
/**
   This app implements a feature analog to fossil(1)'s "rebuild"
   command. It destroys all transient repository state which can
   be reconstructed from the `blob` and `delta` tables and then
   recreates that state.
*/
#include "libfossil.h"
#include <string.h> /* memset() */

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


/***

For reference (noting that there are no plans to to implement all of
these) options...

$ fossil help rebuild
Usage: fossil rebuild ?REPOSITORY? ?OPTIONS?

Reconstruct the named repository database from the core
records.  Run this command after updating the fossil
executable in a way that changes the database schema.

Options:
  --analyze         Run ANALYZE on the database after rebuilding
  --cluster         Compute clusters for unclustered artifacts
  --compress        Strive to make the database as small as possible
  --compress-only   Skip the rebuilding step. Do --compress only
  --deanalyze       Remove ANALYZE tables from the database
  --force           Force the rebuild to complete even if errors are seen
  --ifneeded        Only do the rebuild if it would change the schema version
  --index           Always add in the full-text search index
  --noverify        Skip the verification of changes to the BLOB table
  --noindex         Always omit the full-text search index
  --pagesize N      Set the database pagesize to N. (512..65536 and power of 2)
  --quiet           Only show output if there are errors
  --randomize       Scan artifacts in a random order
  --stats           Show artifact statistics after rebuilding
  --vacuum          Run VACUUM on the database after rebuilding
  --wal             Set Write-Ahead-Log journalling mode on the database

***/


const fsl_rebuild_opt fsl_rebuild_opt_empty = fsl_rebuild_opt_empty_m;

typedef struct RebuildState {
  bool quiet;
  fsl_size_t phantomCount;
  fsl_size_t counts[FSL_SATYPE_count];
  fsl_size_t sizes[FSL_SATYPE_count];
} RebuildState;


static void dump_db_info(char const *header){
  f_out("%.50c\n%s\n", '=', header);
  char const * queries[] = {
  "SELECT count(*) '#' FROM plink",
  "SELECT count(*) '#' FROM mlink",
  "SELECT count(*) '#' FROM event",
  "SELECT count(*) '#' FROM filename",
  NULL
  };
  int i = 0;
  const char * q = queries[0];
  fsl_cx * const f = fcli_cx();
  for( ; q; q=queries[++i] ){
    f_out("%.50c\n%s\n", '~', q);
    fsl_db_each( fsl_cx_db(f), fsl_stmt_each_f_dump, NULL, q );
  }
  f_out("%.50c\n", '=');
}

static int fsl_rebuild_f_my(fsl_rebuild_step const * const step){
  RebuildState * const rs = (RebuildState *)step->opt->callbackState;
  if(step->stepNumber){
    int const ndx = (step->artifactType==FSL_SATYPE_INVALID ? 0 : step->artifactType);
    assert(ndx>=0 && ndx<FSL_SATYPE_count);
    ++rs->counts[ndx];
    if(step->blobSize>0){
      rs->sizes[ndx] += step->blobSize;
    }else if(step->blobSize<0){
      ++rs->phantomCount;
    }
    if(!rs->quiet){
      f_out("\r%" PRIu32 " of %" PRIu32,
            step->stepNumber, step->artifactCount);
    }
  }
  return 0;
}

int main(int argc, const char * const * argv ){
  fsl_rebuild_opt ropt = fsl_rebuild_opt_empty;
  fsl_timer_state timer = fsl_timer_state_empty;
  bool blobCache = true;
  bool mcache = true;
  RebuildState rs;
  memset(&rs, 0, sizeof(RebuildState));
  rs.quiet = !fsl_isatty(1);
  const fcli_cliflag FCliFlags[] = {
    FCLI_FLAG_BOOL("r","randomize", &ropt.randomize,
                   "Randomize artifact order."),
    FCLI_FLAG_BOOL("q","quiet", &rs.quiet,
                   "Disable progress output. "
                   "(Implied when not running on a terminal.)"),
    FCLI_FLAG_BOOL_INVERT(NULL,"no-blob-cache", &blobCache,
                          "Disable fsl_cx blob cache. "
                          "For library testing/debugging."),
    FCLI_FLAG_BOOL_INVERT(NULL,"no-mcache", &mcache,
                          "Disable fsl_cx manifest cache. "
                          "For library testing/debugging."),
    FCLI_FLAG_BOOL(NULL,"dry-run", &ropt.dryRun,
                   "Dry-run mode."),
    fcli_cliflag_empty_m // list MUST end with this (or equivalent)
  };
  const fcli_help_info FCliHelp = {
    "\"Rebuilds\" a fossil SCM repository database from its low-level data.",
    NULL, // very brief usage text, e.g. "file1 [...fileN]"
    NULL // optional callback which outputs app-specific help
  };
  int rc = fcli_setup_v2(argc, argv, FCliFlags, &FCliHelp);
  if(rc) goto end;
  if((rc=fcli_has_unused_args(false))) goto end;

  fsl_cx * const f = fcli_cx();
  if(!fsl_needs_repo(f)){
    rc = FSL_RC_NOT_A_REPO;
    goto end;
  }
  //fsl_cx_flag_set(f, FSL_CX_F_SKIP_UNKNOWN_CROSSLINKS, true);
  fsl_cx_flag_set(f, FSL_CX_F_BLOB_CACHE, blobCache);
  fsl_cx_flag_set(f, FSL_CX_F_MANIFEST_CACHE, mcache);
  ropt.callback = fsl_rebuild_f_my;
  ropt.callbackState = &rs;
  if(fcli_is_verbose()){
    dump_db_info("Before rebuild...");
  }
  fsl_timer_start(&timer);
  rc = fsl_repo_rebuild(fcli_cx(), &ropt);
  end:
  if(0==rc){
    if(fcli_is_verbose()){
      dump_db_info("After rebuild...");
    }
    struct {
      int type;
      char const * label;
    } aType[] = {
    {0,"Non-artifacts"},
    {FSL_SATYPE_CHECKIN,"Checkin"},
    {FSL_SATYPE_CLUSTER,"Cluster"},
    {FSL_SATYPE_CONTROL,"Control (tag)"},
    {FSL_SATYPE_WIKI,"Wiki"},
    {FSL_SATYPE_TICKET,"Ticket"},
    {FSL_SATYPE_ATTACHMENT,"Attachment"},
    {FSL_SATYPE_TECHNOTE,"Technote"},
    {FSL_SATYPE_FORUMPOST,"Forum post"},
    {0,NULL}
    };
    f_out("\rCounts per blob type:\n");
    fsl_size_t n = 0, sz = 0;
    for(int i = 0; aType[i].label; ++i){
      if(rs.counts[i]){
        n += rs.counts[i];
        sz += rs.sizes[i];
        f_out("  %-13s: %,-9" FSL_SIZE_T_PFMT
              "bytes: %," FSL_SIZE_T_PFMT "\n",
              aType[i].label, rs.counts[i],
              rs.sizes[i]);
      }
    }
    if(rs.phantomCount){
      n += rs.phantomCount;
      f_out("  %-13s: %,-9" FSL_SIZE_T_PFMT "\n",
            "Phantoms", rs.phantomCount);
    }
    if(n){
      f_out("  %-13s: %,-9" FSL_SIZE_T_PFMT
            "bytes: %," FSL_SIZE_T_PFMT "\n\n",
            "Total", n, sz);
    }
    if(ropt.dryRun){
      f_out("DRY RUN MODE - will be rolled back momentarily.\n");
    }else{
      f_out("If this made a mess of things, run (fossil rebuild) "
            "to get the repository back in a working state.\n");
    }

    f_out("Work took a total of %0.3f ms of CPU time.\n",
          (double)(fsl_timer_reset(&timer) / 1000.0));
    fcli_dump_cache_metrics();
  }else{
    f_out("\n");
  }
  return fcli_end_of_main(rc);
}
