/* -*- 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 implements the code to checkout a Fossil repository.
*/

#include "libfossil.h"
#include <string.h>
#include <errno.h>
#include <sys/stat.h>
#define MARKER(pfexp)                                               \
  do{ printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__);   \
    printf pfexp;                                                   \
  } while(0)

typedef struct {
  fsl_cx * f;
  int  coCount;
  int  written;
  int  kept;
  int  removed;
  bool quiet;
  bool overwriteLocalMods;
} extract_state;

/**
   A basic fsl_confirm_callback_f() implementation which gets "asked
   questions" by the checkout process regarding how to handle certain
   situation which could either lead to data loss or extraneous files
   lying about.

   TODO: connect an interactive and/or flag-driven yes/no/all/cancel
   mechanism to this.
*/
static int
fsl_confirm_callback_f_my(fsl_confirm_detail const * detail,
                          fsl_confirm_response *answer,
                          void * clientState){
  extract_state  const *state = (extract_state const *)clientState;
  if(0 && fcli_is_verbose()){
    MARKER(("Asking for confirmation about event #%d: %s\n",
            detail->eventId, detail->filename));
  }
  switch(detail->eventId){
    case FSL_CEVENT_RM_MOD_UNMGD_FILE:
      // MODIFIED newly-unmanaged files are never removed
      answer->response = FSL_CRESPONSE_YES;
      break;
    case FSL_CEVENT_OVERWRITE_MOD_FILE:
    case FSL_CEVENT_OVERWRITE_UNMGD_FILE:
      answer->response = state->overwriteLocalMods
        ? FSL_CRESPONSE_YES//ALWAYS
        : FSL_CRESPONSE_NEVER
        /* The difference between FSL_CRESPONSE_YES and
           FSL_CRESPONSE_ALWAYS is that the latter will cause the
           checkout process to keep that answer and stop asking
           for purposes of the current phase of the checkout.
        */;
      break;
    default:
      return fsl_cx_err_set(state->f, FSL_RC_UNSUPPORTED,
                            "Unhanded event ID type %d\n",
                            detail->eventId);
  }
  if(0 && fcli_is_verbose()){
    MARKER(("eventId %d answer=%d\n", detail->eventId,
            answer->response));
  }
  return 0;
}

static int write_manifest(fsl_cx * f, int m, int mUuid, int mTags){
  int wrote = 0;
  int const rc = fsl_ckout_manifest_write(f, m, mUuid, mTags, &wrote);
  if(!rc){
    if(wrote) f_out("\n");
    if(0x001 & wrote) f_out("Wrote manifest.\n");
    if(0x010 & wrote) f_out("Wrote manifest.uuid.\n");
    if(0x100 & wrote) f_out("Wrote manifest.tags.\n");
  }
  return rc;
}

/**
   fsl_repo_ckout() callback. Called once for each row of
   the checkout, including file removals.
*/
static int fsl_ckup_f_my(fsl_ckup_state const *cuState){
  enum {DATE_SHORT = 16};
  //fsl_repo_extract_state const * xs = cuState->extractState;
  extract_state  *state = (extract_state *)cuState->callbackState;
  char    tbuf[DATE_SHORT];
  char const * mode = "? ";
  bool noteworthy = false;
  char const * nonTimestamp = 0;
  ++state->coCount;
  switch(cuState->fileChangeType){
    case FSL_CKUP_FCHANGE_NONE: mode = "  ";
      ++state->kept;
      break;
    case FSL_CKUP_FCHANGE_UPDATED: mode = "w ";
      ++state->written;
      break;
    case FSL_CKUP_FCHANGE_RM: mode = "- ";
      ++state->removed;
      switch(cuState->fileRmInfo){
        case FSL_CKUP_RM_KEPT:
          ++state->kept;
          mode = "-U";
          noteworthy = true;
          nonTimestamp = "<UNMANAGED>";
          break;
        case FSL_CKUP_RM:
          mode = "- ";
          assert(cuState->size<0);
          noteworthy = true;
          nonTimestamp = "<REMOVED>  ";
          break;
        default: break;
      }
      break;
    default:
      MARKER(("Unexpected FSL_CKUP_FCHANGE value for ckout op: #%d\n",
              cuState->fileChangeType));
      break;
  }
  if(noteworthy || !state->quiet){
    fsl_strftime_unix(tbuf, DATE_SHORT, "%d %b %Y", cuState->mtime, 1);
    f_out("[%s] %8"FSL_INT_T_PFMT"   %s  %s\n",
          mode, cuState->size,
          nonTimestamp ? nonTimestamp : tbuf,
          cuState->extractState->fCard->name);
  }
  return 0;
}

int main(int argc, char const * const *argv) {
  fsl_db *db = 0;
  fsl_cx *f = NULL;
  fsl_id_t prevId = 0, rid = 0;
  const char *sym = NULL;
  bool force = false, keep = false, manifest = false,
    mtime = false, fDryRun = false;
  int rc = 0;
  extract_state ex;
  memset(&ex, 0, sizeof(extract_state));
  ex.quiet = true;

  fcli_cliflag	         fcli_flags[] = {
    FCLI_FLAG_BOOL("f", "force", &force, "Continue with the checkout "
     "irrespective of any unsaved changes in the current checkout."),
#if 0
    FCLI_FLAG_BOOL(0, "keep", &keep,
     "Only checkout files from the requested "
     "<version> that do not have a file of the same name already present on "
     "disk. Files with the same name as those from the requested <version> will "
     "remain unmodified irrespective of whether their content is consistent "
     "with that of the requested <version>. In such a case, the checkout will "
                   "immediately be in a changed state, which 'f-status' will report."),
#else
    FCLI_FLAG_BOOL(0, "keep", &keep,
                   "Modified files in the checkout are retained in "
                   "the new checkout version, rather than being "
                   "overwritten. If a locally-modified file becomes "
                   "unmanaged in the new checkout version, it is kept "
                   "in the filesystem regardless of this flag."),
#endif
    FCLI_FLAG_BOOL(0, "manifest", &manifest,
                   "Only generate the manifest and manifest.uuid files "
                   "for the current checkout version."),
    FCLI_FLAG_BOOL_INVERT("Q","not-quiet",&ex.quiet,
                          "When checking out, list all files, not just "
                          "noteworthy changes."),
    FCLI_FLAG_BOOL(0, "setmtime", &mtime, "Set timestamps of all files to that "
                   "of the last check-in in which they were modified "
                   "(i.e., manifest time). FIXME: this does not apply to the "
                   "optional manifest file(s)."),
    FCLI_FLAG_BOOL("n","dry-run",&fDryRun, "Dry-run mode."),
#if 0
    FCLI_FLAG_BOOL_INVERT(0,"no-rm", &doRm,
                          "Do not delete files which were removed between the "
                          "original and new checkout versions. By default, only "
                          "locally-unmodified files will be removed."),
#endif
    fcli_cliflag_empty_m
  };
  fcli_help_info	 fcli_help = {
    "Change the current checkout to the requested <version> or to the tip of "
    "the trunk if no <version> is specified.",
    "[<version>]",
    NULL
  };

  fcli.cliFlags = fcli_flags;
  fcli.appHelp = &fcli_help;
  rc = fcli_setup(argc, argv);
  if(rc) goto end;

  f = fcli_cx();
  db = fsl_needs_ckout(f);
  if(!db){
    goto end;
  }

  if(fcli_has_unused_flags(0)){
    goto end;
  }

  rc = fsl_cx_transaction_begin(f);
  if(rc) goto end;
  fsl_ckout_version_info(f, &prevId, NULL);
 
  if(manifest){
    if(!prevId){
      rc = fcli_err_set(FSL_RC_RANGE,"This checkout is completely empty, "
                        "so cannot generate manifest.");
      goto end;
    }
    assert(prevId>0);
    rc = write_manifest(f, 1, 1, -1);
    goto end;
  }

  if(prevId>0){
    fsl_vfile_changes_scan(f, prevId, mtime
                           ? FSL_VFILE_CKSIG_SETMTIME
                           : 0);
  }
  if(!force && fsl_ckout_has_changes(f)) {
    fcli_err_set(FSL_RC_MISUSE,
                 "The current checkout contains unsaved changes.");
    goto end;
  }
  
  if(!(sym = fcli_next_arg(1))){
    char * mainBranch = fsl_config_get_text(f, FSL_CONFDB_REPO,
                                            "main-branch", 0);
    if(mainBranch){
      fcli_fax(mainBranch);
      sym = mainBranch;
    }else{
      sym = "trunk";
    }
  }
  rc = fsl_sym_to_rid(f, sym, FSL_SATYPE_CHECKIN, &rid);
  if(rc) goto end;
  if(prevId == rid){
    f_out("Same version - nothing to do.\n");
    goto end;
  }else{
    fsl_confirmer fcon = fsl_confirmer_empty;
    fcon.callback = fsl_confirm_callback_f_my;
    fcon.callbackState = &ex;
    fsl_cx_confirmer(f, &fcon, NULL);
  }
  ex.f = f;
  ex.coCount = ex.written =
    ex.kept = ex.removed = 0;
  ex.overwriteLocalMods = !keep;
  fsl_ckup_opt cOpt = fsl_ckup_opt_empty;
  cOpt.dryRun = fDryRun;
  cOpt.scanForChanges = false/*we just did a scan*/;
  cOpt.checkinRid = rid;
  cOpt.callback = fsl_ckup_f_my;
  cOpt.callbackState = &ex;
  rc = fsl_repo_ckout(f, &cOpt);
  if(!rc) rc = write_manifest(f, -1, -1, -1);
  if(rc) goto end;

  const char * uuid = 0;
  fsl_ckout_version_info(f, NULL, &uuid);
  if(ex.written || ex.removed){
    f_out("\n");
  }
  f_out("Processed %d file(s) from [%s] [%.16s].\n",
        ex.coCount, sym, uuid);
  if(ex.written){
    f_out("%d SCM'd file(s) [w]ritten to disk.\n", ex.written);
  }
  if(ex.kept){
    f_out("%d file(s) left unchanged on disk.\n", ex.kept);
  }
  if(ex.removed){
    f_out("%d file(s) removed from checkout.\n", ex.removed);
  }
  f_out("\n");
  fcli_ckout_show_info(false);
  if(cOpt.dryRun){
    f_out("Dry-run mode. Rolling back transaction.\n");
    rc = fsl_cx_transaction_end(f, true);
  }
  end:
  if(f && fsl_cx_transaction_level(f)){
    int const rc2 = fsl_cx_transaction_end(f, !!rc);
    rc = rc ? rc : fsl_cx_uplift_db_error2(f, db, rc2);
  }
  return fcli_end_of_main(rc);
}
