/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/*
 * Copyright 2022 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 files-of-checkin (foci) API used to construct a
 * SQLite3 virtual table via a table-valued function to aggregate all files
 * pertaining to a specific check-in. This table is used in repository
 * queries such as listing all files belonging to a specific version.
 *
 * Usage (from fossil(1) /src/foci.c:24):
 *
 *    SELECT * FROM fsl_foci('trunk');
 *
 * temp.foci table schema:
 *
 *     CREATE TABLE fsl_foci(
 *       checkinID    INTEGER,    -- RID for the check-in manifest
 *       filename     TEXT,       -- Name of a file
 *       uuid         TEXT,       -- hash of the file
 *       previousName TEXT,       -- Name of the file in previous check-in
 *       perm         TEXT,       -- Permissions on the file
 *       symname      TEXT HIDDEN -- Symbolic name of the check-in.
 *     );
 *
 * The hidden symname column is (optionally) used as a query parameter to
 * identify the particular check-in to parse.  The checkinID parameter
 * (such is a unique numeric RID rather than symbolic name) can also be used
 * to identify the check-in.  Example:
 *
 *    SELECT * FROM fsl_foci
 *     WHERE checkinID=fsl_sym2rid('trunk');
 *
 */
#include "fossil-scm/cli.h"
#include "fossil-scm/core.h"
#include "fossil-scm/internal.h"
#include "fossil-scm/config.h"
#include "fossil-scm/checkout.h"
#include <string.h>/*memset()*/


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

enum {
FOCI_CHECKINID = 0,
FOCI_FILENAME = 1,
FOCI_UUID = 2,
FOCI_PREVNAME = 3,
FOCI_PERM = 4,
FOCI_SYMNAME = 5
};

typedef struct FociCursor FociCursor;
struct FociCursor {
  sqlite3_vtab_cursor base; /* Base class - must be first */
  fsl_deck d;           /* Current manifest */
  const fsl_card_F *cf;  /* Current file */
  int idx;                /* File index */
};

typedef struct FociTable FociTable;
struct FociTable {
  sqlite3_vtab base;        /* Base class - must be first */
};

/*
 * The schema for the virtual table:
 */
static const char zFociSchema[] =
  " CREATE TABLE fsl_foci("
  "  checkinID    INTEGER,    -- RID for the check-in manifest\n"
  "  filename     TEXT,       -- Name of a file\n"
  "  uuid         TEXT,       -- hash of the file\n"
  "  previousName TEXT,       -- Name of the file in previous check-in\n"
  "  perm         TEXT,       -- Permissions on the file\n"
  "  symname      TEXT HIDDEN -- Symbolic name of the check-in\n"
  " );";

/*
 * Connect to or create a foci virtual table.
 */
static int fociConnect(
  sqlite3 *db,
  void *pAux,
  int argc,
  const char * const * argv,
  sqlite3_vtab **ppVtab,
  char **pzErr
){
  FociTable *pTab;
  int rc = SQLITE_OK;

  pTab = (FociTable *)sqlite3_malloc(sizeof(FociTable));
  if( !pTab ){
    return SQLITE_NOMEM;
  }
  memset(pTab, 0, sizeof(FociTable));
  rc = sqlite3_declare_vtab(db, zFociSchema);
  if( rc==SQLITE_OK ){
    *ppVtab = &pTab->base;
  }
  return rc;
}

/*
 * Disconnect from or destroy a focivfs virtual table.
 */
static int fociDisconnect(sqlite3_vtab *pVtab){
  sqlite3_free(pVtab);
  return SQLITE_OK;
}

/*
 * Available scan methods:
 *
 *   (0)     A full scan.  Visit every manifest in the repo.  (Slow)
 *   (1)     checkinID=?.  visit only the single manifest specified.
 *   (2)     symName=?     visit only the single manifest specified.
 */
static int fociBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){
  int i;
  pIdxInfo->estimatedCost = 1000000000.0;
  for( i=0; i<pIdxInfo->nConstraint; i++ ){
    if( !pIdxInfo->aConstraint[i].usable ) continue;
    if( pIdxInfo->aConstraint[i].op==SQLITE_INDEX_CONSTRAINT_EQ
     && (pIdxInfo->aConstraint[i].iColumn==FOCI_CHECKINID
            || pIdxInfo->aConstraint[i].iColumn==FOCI_SYMNAME)
    ){
      if( pIdxInfo->aConstraint[i].iColumn==FOCI_CHECKINID ){
        pIdxInfo->idxNum = 1;
      }else{
        pIdxInfo->idxNum = 2;
      }
      pIdxInfo->estimatedCost = 1.0;
      pIdxInfo->aConstraintUsage[i].argvIndex = 1;
      pIdxInfo->aConstraintUsage[i].omit = 1;
      break;
    }
  }
  return SQLITE_OK;
}

/*
 * Open a new focivfs cursor.
 */
static int fociOpen(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor){
  FociCursor *pCsr;
  pCsr = (FociCursor *)sqlite3_malloc(sizeof(FociCursor));
  if( !pCsr ){
    return SQLITE_NOMEM;
  }
  memset(pCsr, 0, sizeof(FociCursor));
  pCsr->d = fsl_deck_empty;
  pCsr->base.pVtab = pVTab;
  *ppCursor = (sqlite3_vtab_cursor *)pCsr;
  return SQLITE_OK;
}

/*
 * Close a focivfs cursor.
 */
static int fociClose(sqlite3_vtab_cursor *pCursor){
  FociCursor *pCsr = (FociCursor *)pCursor;
  fsl_deck_finalize(&pCsr->d);
  sqlite3_free(pCsr);
  return SQLITE_OK;
}

/*
 * Move a focivfs cursor to the next F card entry in the deck. If this fails,
 * pass the vtab cursor to fociClose and return the failing result code.
 */
static int fociNext(sqlite3_vtab_cursor *pCursor){
  int rc = SQLITE_OK;

  FociCursor *pCsr = (FociCursor *)pCursor;
  rc = fsl_deck_F_next(&pCsr->d, &pCsr->cf);
  if( !rc ){
    pCsr->idx++;
  }else{
    fociClose(pCursor);
  }
  return rc;
}

static int fociEof(sqlite3_vtab_cursor *pCursor){
  FociCursor *pCsr = (FociCursor *)pCursor;
  return pCsr->cf==0;
}

static int fociFilter(
  sqlite3_vtab_cursor *pCursor,
  int idxNum, const char *idxStr,
  int argc, sqlite3_value **argv
){
  fsl_cx *const f = fcli_cx();
  int rc = SQLITE_OK;
  FociCursor *pCur = (FociCursor *)pCursor;
  fsl_deck_finalize(&pCur->d);
  if( idxNum ){
    fsl_id_t rid;
    if( idxNum==1 ){
      rid = sqlite3_value_int(argv[0]);
    }else{
      rc = fsl_sym_to_rid(f, (const char *)sqlite3_value_text(argv[0]),
       FSL_SATYPE_CHECKIN, &rid);
      if( rc ){
        goto end;
      }
    }
    rc = fsl_deck_load_rid(f, &pCur->d, rid, FSL_SATYPE_CHECKIN);
    if( rc ){
      goto end;
    }
    if( pCur->d.rid ){
      rc = fsl_deck_F_rewind(&pCur->d);
      if( !rc ){
        rc = fsl_deck_F_next(&pCur->d, &pCur->cf);
      }
      if( rc ){
        goto end;
      }
    }
  }
  pCur->idx = 0;
end:
  if( rc ){
    fsl_deck_finalize(&pCur->d);
  }
  return rc;
}

static int fociColumn(
  sqlite3_vtab_cursor *pCursor,
  sqlite3_context *ctx,
  int i
){
  FociCursor *pCsr = (FociCursor *)pCursor;
  switch( i ){
    case FOCI_CHECKINID:
      sqlite3_result_int(ctx, pCsr->d.rid);
      break;
    case FOCI_FILENAME:
      sqlite3_result_text(ctx, pCsr->cf->name, -1, SQLITE_TRANSIENT);
      break;
    case FOCI_UUID:
      sqlite3_result_text(ctx, pCsr->cf->uuid, -1, SQLITE_TRANSIENT);
      break;
    case FOCI_PREVNAME:
      sqlite3_result_text(ctx, pCsr->cf->priorName, -1, SQLITE_TRANSIENT);
      break;
    case FOCI_PERM: {
      char *perm[3] = {"l", "w", "x"};
      int i = 1;
      switch( pCsr->cf->perm ){
        case FSL_FILE_PERM_LINK:
          i = 0; break;
        case FSL_FILE_PERM_EXE:
          i = 2; break;
        default:
          break;
      }
      sqlite3_result_text(ctx, perm[i], 1, SQLITE_TRANSIENT);
      break;
    }
    case FOCI_SYMNAME:
      break;
  }
  return SQLITE_OK;
}

static int fociRowid(sqlite3_vtab_cursor *pCursor, sqlite_int64 *pRowid){
  FociCursor *pCsr = (FociCursor *)pCursor;
  *pRowid = pCsr->idx;
  return SQLITE_OK;
}

int fsl__foci_register(fsl_db * const db){
  static sqlite3_module foci_module = {
    0,                            /* iVersion */
    fociConnect,                  /* xCreate */
    fociConnect,                  /* xConnect */
    fociBestIndex,                /* xBestIndex */
    fociDisconnect,               /* xDisconnect */
    fociDisconnect,               /* xDestroy */
    fociOpen,                     /* xOpen - open a cursor */
    fociClose,                    /* xClose - close a cursor */
    fociFilter,                   /* xFilter - configure scan constraints */
    fociNext,                     /* xNext - advance a cursor */
    fociEof,                      /* xEof - check for end of scan */
    fociColumn,                   /* xColumn - read data */
    fociRowid,                    /* xRowid - read data */
    0,                            /* xUpdate */
    0,                            /* xBegin */
    0,                            /* xSync */
    0,                            /* xCommit */
    0,                            /* xRollback */
    0,                            /* xFindMethod */
    0,                            /* xRename */
    0,                            /* xSavepoint */
    0,                            /* xRelease */
    0                             /* xRollbackTo */
  };
  int rc = sqlite3_create_module(db->dbh, "fsl_foci", &foci_module, 0);
  return fsl__db_errcode(db, rc);
}

#undef MARKER
