/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
/*
  Copyright 2013-2021 Stephan Beal (https://wanderinghorse.net).

  Derived heavily from previous work:

  Copyright (c) 2013 D. Richard Hipp (https://www.hwaci.com/drh/)

  
  This program is free software; you can redistribute it and/or
  modify it under the terms of the Simplified BSD License (also
  known as the "2-Clause License" or "FreeBSD License".)
  
  This program 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.
  
  *****************************************************************************
  A test/demo app for doing mass parsing tests on all artifacts in a
  repository.
*/

#include "fossil-scm/fossil-cli.h" /* Fossil App mini-framework */
#include "fossil-scm/fossil-internal.h"
#include <time.h>

#ifndef _WIN32
#include <unistd.h> /*isatty()*/
#endif

static struct App_{
  int checkRCard;
  int failFast;
  int crosslink;
  int quiet;
  const char * eventTypes;
} App = {
0/*checkRCard*/,
0/*failFast*/,
0/*crosslink*/,
0/*quiet*/,
0/*eventTypes*/
};

static int my_xlink_f(fsl_deck * d, void * state){
  if(fcli.verbose){
    FCLI_VN(1,("Crosslinking rid %"FSL_ID_T_PFMT", uuid %s ...\n",
               d->rid, d->uuid));
  }else if(!App.quiet){
    f_out("x");
  }
  return 0;
}

static int test_parse_all(void){
  fsl_buffer content = fsl_buffer_empty;
  fsl_deck mf = fsl_deck_empty;
  fsl_cx * const f = fcli.f;
  fsl_stmt q1 = fsl_stmt_empty;
  fsl_db * const db = fsl_cx_db_repo(f);
  int rc = 0;
  int counter = 0;
  int errCount = 0;
  int counters[FSL_SATYPE_count] = {0,0,0,0,0,0,0,0,0};

  if(!db){
    return fsl_cx_err_set(f, FSL_RC_MISUSE,
                          "This app requires a repository db.");
  }
  if(App.crosslink){
    rc = fsl_xlink_listener( f, "parseparty", my_xlink_f, 0 );
    if(!rc) rc = fsl_crosslink_begin(f);
    if(rc) goto end;
  }

#define RC if(rc) goto end
  if(App.eventTypes && *App.eventTypes){
    fsl_buffer q = fsl_buffer_empty;
    const char * c = App.eventTypes;
    fsl_buffer_append(&q, "SELECT e.objid, b.uuid FROM event e, blob b "
                      "WHERE e.objid=b.rid AND type in ", -1);
    for(; *c; ++c){
      const char * eType = 0;
      switch(*c){
        case 'c': eType = "ci"; break;
        case 'g': eType = "g"; break;
        case 't': eType = "t"; break;
        case 'n': eType = "e"; break;
        case 'w': eType = "w"; break;
        case 'f': eType = "f"; break;
        default:
          fsl_cx_err_set(f, FSL_RC_MISUSE, "Unknown --types value '%c'.", *c);
          fsl_buffer_clear(&q);
          goto end;
      }
      fsl_buffer_append(&q, (c==App.eventTypes ? "(" : ","), 1);
      fsl_buffer_appendf(&q, "%Q", eType);
    }
    fsl_buffer_append(&q, ") ORDER BY mtime", -1);
    FCLI_V(("Query=%b\n", &q));
    rc = fsl_db_prepare(db, &q1, fsl_buffer_cstr(&q));
    fsl_buffer_clear(&q);
  }else{
    rc = fsl_db_prepare(db, &q1, "SELECT e.objid, b.uuid FROM event e, blob b "
                        "WHERE e.objid=b.rid ORDER BY RANDOM()");
  }
  if(rc){
    fsl_cx_uplift_db_error(f, db);
    goto end;
  }
  f_out("Here we go...\n");
  mf.f = f;
  while(FSL_RC_STEP_ROW==fsl_stmt_step(&q1)){
    fsl_id_t const rid = fsl_stmt_g_id(&q1, 0);
    const char * zUuid = fsl_stmt_g_text(&q1, 1, 0);
    const char * lineBreak = !fcli.verbose ? "\n" : "";
    assert(rid>0);
    fsl_buffer_reset(&content);
    rc = fsl_content_get(f, rid, &content);
    RC;
    fsl_deck_clean(&mf);
    assert(mf.f);
    rc = fsl_deck_parse(&mf, &content);
    if(!fcli.verbose && !App.quiet){
      f_out(".");
      fflush(stdout);
    }
    if(rc){
      ++errCount;
      f_out("%sparse-offending artifact: %d / %s\n",
            lineBreak, (int)rid, zUuid);
      if(App.failFast){
        goto end;
      }
      fcli_err_report(1);
      continue;
    }
    assert(mf.rid);
    assert(mf.uuid);
    assert(mf.type>=0 && mf.type<FSL_SATYPE_count);
    assert(!content.mem && "Was handed off to mf.");
#if 0
    /* These assertions are wrong for phantom artifact cases. The
       libfossil tree contains some artifacts, for testing purposes,
       from the main fossil tree, which results in phantoms (the
       hashes those artifacts reference but which we don't have). */
    assert(mf.rid);
    assert(mf.uuid);
#endif
    FCLI_VN(1,("Parsed rid %d, uuid %s\n", (int)mf.rid, mf.uuid));
    if(App.crosslink){
      rc = fsl_deck_crosslink(&mf);
      if(rc){
        if(FSL_RC_NOT_FOUND==rc){
          /* Assume this is an artifact, like 4b05c2c59fa61f1240d41949b305173c76d1395d,
             which exists as an artifact file but is not part of this project. */
          f_out("%sFAILED NON-FATALLY crosslinking rid %d, uuid %s\n",
                lineBreak, (int)mf.rid, mf.uuid, fsl_rc_cstr(rc));
          fcli_err_report(1);
          rc = 0;
        }else{
          f_out("%sFAILED crosslinking rid %d, uuid %s w/ rc=%s\n",
                lineBreak, (int)mf.rid, mf.uuid, fsl_rc_cstr(rc));
          fcli_err_report(1);
          if(App.failFast){
            break;
          }
        }
      }
    }
    ++counter;
    ++counters[mf.type];
    /* TODO? Optionally check R-card calculation if
       mf.type==FSL_SATYPE_CHECKIN and mf.R is not NULL. */
  }
  f_out("\nSuccessfully processed %d artifact(s):\n", counter);
  assert(0==counters[FSL_SATYPE_ANY]);
#define CAT(T) if(counters[T]){f_out("%-22s = %d\n", #T, counters[T]);} (void)0
  CAT(FSL_SATYPE_CHECKIN);
  CAT(FSL_SATYPE_CLUSTER);
  CAT(FSL_SATYPE_CONTROL);
  CAT(FSL_SATYPE_WIKI);
  CAT(FSL_SATYPE_TICKET);
  CAT(FSL_SATYPE_ATTACHMENT);
  CAT(FSL_SATYPE_EVENT);
  CAT(FSL_SATYPE_FORUMPOST);
#undef CAT
  if(errCount){
    f_out("ERROR count: %d\n", errCount);
  }
  end:
  if(App.crosslink){
    if(rc){
      f_out("Something failed. Rolling back crosslink-started transaction.\n");
      fsl_db_transaction_rollback(db);
    }else{
      fsl_cx_err_reset(f);
      f_out("Ending crosslinking process (might take a few ms)...\n");
      rc = fsl_crosslink_end(f);
    }
  }
#undef RC
  fsl_stmt_finalize(&q1);
  fsl_buffer_clear(&content);
  fsl_deck_finalize(&mf);
  return rc;

}


static void fcli_local_help(){
  printf("Usage:\n\t%s [options]\n\n", fcli.appName);

  puts("Tests the libfossil artifact/manifest parser by parsing "
       "ALL control artifacts in a repository.\n");

  puts("\t-F|--fail-fast stops processing on the first error.\n");
  puts("\t-c|--crosslink Re-crosslinks each artifact.\n");
  puts("\t-t|--types = list of artifact types:");
  puts("\t  c=checkin, g=control (tags), w=wiki, "
       "t=ticket, n=technote, f=forum");
  puts("\t-s|--skip-unknown when crosslinking, skip over type(s) we "
       "don't know how to crosslink (tickets).\n");
  puts("\t  e.g. --types=cgw");

}

int main(int argc, char * const * argv ){
  int rc = 0;

  fcli.appHelp = fcli_local_help;
  rc = fcli_setup(argc, argv);
  if(FSL_RC_BREAK==rc) /* --help */ return EXIT_SUCCESS;
  else if(rc) goto end;

  App.crosslink = fcli_flag2("c","crosslink", NULL);
  App.failFast = fcli_flag2("F","fail-fast", NULL);
  App.quiet = fcli_flag2("q","quiet", NULL);
  fcli_flag2("t","types", &App.eventTypes);
  if(fcli_flag2("s","skip-unknown",0)){
    fsl_cx_flag_set(fcli_cx(), FSL_CX_SKIP_UNKNOWN_CROSSLINKS, 1);
  }
  if(fcli_has_unused_flags(0)) goto end;
  rc = test_parse_all();
  end:
  return (fcli_err_report(1) || rc) ? EXIT_FAILURE : EXIT_SUCCESS;
}
