/* -*- 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 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.
  
  *****************************************************************************
  This file implements a checkin [test] app using the libfossil API.
*/

#include "fossil-scm/fossil-cli.h" /* Fossil App mini-framework */
#include "fossil-scm/fossil-internal.h" /* only for testing/debugging */

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

  puts("INCOMPLETE app for developing the libfossil checking features. Options:\n");
#if 1
  puts("\t--do-it|-y Disables automatic/default dry-run mode.\n");
#else
  puts("\t--dry-run|-n runs the operation in a transaction and then rolls it back.\n");
#endif
  puts("\t--message|-m=TEXT specifies the commit message.\n");
  puts("\t--mime-type|-mt=TEXT specifies the mime-type of the commit message.\n");
  puts("\t--no-r-card|-r specifies not to calculate an R-card (use with care).\n");
  puts("\t--dump|-d=FILENAME dumps the generated manifest to the given file.\n");
  puts("\t--bg-color|-bg=#RRGGBB one-time color for commit or propagating color of --branch\n");
  puts("\t--branch|-b=NAME Creates a new branch. ONLY LIGHTLY TESTED!\n");
  puts("\t--integrate|-i specifies to close all merged-in branches, not just "
       "integrate-merges.\n");
  puts("\t--baseline forces generation of a baseline manifest. By default "
       "a delta is generated if possible.\n");

  puts("KNOWN BUGS:\n");
  puts("- Leaf status in the timeline is not updated properly in the local "
       "repo, but is okay on the remote after push or a local rebuild "
       "(so the metadata is okay).\n");
}

/**
   A fsl_stmt_each_f() impl which simply outputs row data in tabular
   form via f_out(). If state is not NULL then it must be
   a (fsl_size_t*), to which the row count is written.
 */
static int stmt_each_dump( fsl_stmt * stmt, void * state ){
  int i;
  char const * sep = "\t";
  fsl_size_t * counter = (fsl_size_t*)state;
  if(1==stmt->rowCount){
    for( i = 0; i < stmt->colCount; ++i ){
      f_out("%s%s", fsl_stmt_col_name(stmt, i),
            (i==stmt->colCount-1) ? "" : sep);
    }
    f_out("\n");
  }
  for( i = 0; i < stmt->colCount; ++i ){
    char const * val = fsl_stmt_g_text(stmt, i, NULL);
    f_out("%s%s", val ? val : "NULL",
          (i==stmt->colCount-1) ? "" : sep);
  }
  f_out("\n");
  if(counter) *counter = stmt->rowCount;
  return 0;
}


/**
    Just experimenting with fsl_xlink_listener() and friends.
*/
static int my_xlink_f(fsl_deck * d, void * state){
  FCLI_V(("Crosslink callback for %s artifact [%.12s] (RID %"FSL_ID_T_PFMT")\n",
           fsl_satype_cstr(d->type), d->uuid, d->rid));
  return 0;
}


/*
Reminder: name of a file by its content RID:

SELECT fn.name
FROM filename fn, mlink ml
WHERE fn.fnid=ml.fnid
AND ml.fid=$contentRid
*/
int main(int argc, char * const * argv ){
  int rc = 0;
  fsl_cx * f;
  char inTrans = 0;
  fsl_db * db;
  char dryRun = 0;
  fsl_id_t ckoutId = 0;
  const char * cMsg = NULL;
  const char * cBranch = NULL;
  const char * fname = NULL;
  const char * cMimeType = NULL;
  const char * cDumpMf = NULL;
  const char * cColor = NULL;
  fsl_checkin_opt opt = fsl_checkin_opt_empty;
  fsl_id_t newRid = 0;
  fsl_uuid_str newUuid = NULL;
  fsl_size_t fileArgCount = 0;
  fsl_size_t enqueuedArgCount = 0;
  fcli.appHelp = fapp_local_help;
  rc = fcli_setup(argc, argv);
  if(FSL_RC_BREAK==rc) /* --help */ return 0;
  else if(rc) goto end;
  f = fcli.f;
  db = fsl_cx_db_checkout(f);
  if(!db){
    rc = fsl_cx_err_set(f, FSL_RC_NOT_A_CHECKOUT,
                        "This app requires a checkout db.");
    goto end;
  }

#if 1
  dryRun = !fcli_flag2("y","do-it", NULL);
#else
  dryRun = fcli.fDryRun || fcli_flag("n",NULL);
#endif

  if(dryRun){
    f_out("For safety reasons (until this code is stable), "
          "checking in runs in dry-run mode unless the "
          "-y|--do-it flag is provided.\n");
  }

  opt.calcRCard = !fcli_flag2("r","no-r-card", NULL);
  fcli_flag2("m", "message", &cMsg);
  fcli_flag2("mt", "mime-type", &cMimeType);
  fcli_flag2("b", "branch", &cBranch);
  fcli_flag2("bg", "bg-color", &cColor);
  fcli_flag2("d", "dump", &cDumpMf);
  opt.integrate = fcli_flag2("i", "integrate", NULL);
  if(fcli_flag("baseline", NULL)){
    opt.deltaPolicy = 0;
  }
  if(fcli_has_unused_flags(0)) goto end;

  if(!cMsg){
    rc = fcli_err_set(FSL_RC_MISUSE,
                      "Commit message (-m|--message MSG) is required.");
    goto end;
  }

  fsl_xlink_listener( f, fcli.appName, my_xlink_f, NULL );

  opt.user = fsl_cx_user_get(f);

  if(!opt.user){
    rc = fcli_err_set(FSL_RC_MISUSE,
                      "Could not figure out user name to commit as.");
    goto end;
  }
  opt.message = cMsg;
  opt.messageMimeType = cMimeType;
  opt.dumpManifestFile = cDumpMf;
  opt.branch = cBranch;
  opt.bgColor = cColor;

  rc = fsl_db_transaction_begin(db);
  if(rc){
    fsl_cx_uplift_db_error(f, db);
    goto end;
  }
  inTrans = 1;
  fsl_checkout_version_info(f, &ckoutId, NULL);
  rc = fsl_vfile_changes_scan(f, ckoutId, 0);
  if(rc) goto end;

  while((fname = fcli_next_arg(1))){
    const char * gmatch;
    ++fileArgCount;
    gmatch = fsl_cx_glob_matches(f, FSL_GLOBS_IGNORE, fname);
    if(gmatch){
      FCLI_V(("Skipping file due to ignore-glob (%s): %s\n",
              gmatch, fname));
      continue;
    }
    rc = fsl_checkin_file_enqueue( f, fname, 1 );
    if(!rc){
      ++enqueuedArgCount;
      f_out("Queued for commit: %s\n", fname);
    }
    if(rc) goto end;
  }

  /* rc = fsl_checkout_changes_scan(f); */
  /* if(rc) goto end; */

  if(fileArgCount && !enqueuedArgCount){
    rc = fcli_err_set(FSL_RC_NOOP,
                 "All would-be-enqueued files were skipped over due to ignore-glob.");
    goto end;
  }else{
    f_out("vfile selected contents:\n");
    fsl_db_each( fsl_cx_db_checkout(f), stmt_each_dump, NULL,
                 "SELECT vf.id, substr(b.uuid,0,12), vf.pathname "
                 "FROM vfile vf LEFT JOIN blob b "
                 "ON b.rid=vf.rid "
                 "WHERE vf.vid=%"FSL_ID_T_PFMT" "
                 "AND (chnged<>0 OR pathname<>origname)"
                 "AND fsl_is_enqueued(vf.id) "
                 "ORDER BY vf.id", ckoutId);
    f_out("f->ckin.selectedIds count: %d\n",
          (int)f->ckin.selectedIds.entryCount);
  }

  rc = fsl_checkin_commit(f, &opt, &newRid, &newUuid);
  if(rc){
    f_out("rc=%s\n", fsl_rc_cstr(rc));
    goto end;
  }
  f_out("New version: %s (%"FSL_ID_T_PFMT")\n",
        newUuid, (fsl_id_t)newRid);


  if(0){
    f_out("vfile contents:\n");
    fsl_db_each( fsl_cx_db_checkout(f), stmt_each_dump, NULL,
                 "SELECT * from vfile "
                 "WHERE vid=%"FSL_ID_T_PFMT
                 " AND (chnged OR deleted "
                 "      OR (origname IS NOT NULL AND origname<>pathname)"
                 " )"
                 " ORDER BY pathname",
                 (fsl_id_t)newRid );
  }

  if(dryRun){
    f_out("Dry-run mode. Rolling back transaction.\n");
    fsl_db_transaction_rollback(db);
  }else{
    rc = fsl_db_transaction_end(db, 0);
#if 1
    if(!rc) rc = fsl_repo_leaves_rebuild(f)
              /* KLUDGE: as leaves are only checked during commit, we have to do this
                 after the commit :/. */;
#endif
    f_out("Note that libfossil currently has no remote sync support, "
          "so to push your changes you will need to learn the Fossil "
          "sync protocol and speak it to the remote server of your "
          "choice over a telnet connection."
          "\nOr you can just use "
          "fossil(1)'s \"push\" feature.\n");
  }
  inTrans = 0;

  end:
  if(inTrans){
    fsl_db_transaction_rollback(db);
  }
  fsl_free(newUuid);
  return fcli_end_of_main(rc);
}
