/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
/*
** Copyright (c) 2013 D. Richard Hipp
**
** 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.
**
** Author contact information:
**   drh@hwaci.com
**   http://www.hwaci.com/drh/
**
*******************************************************************************
** This file houses most of main the v2 API routines.
*/

#include "fossil/fossil.h"
#include "fsl_internal.h"
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <string.h>
#include <assert.h>

const fsl_buffer fsl_buffer_empty = fsl_buffer_empty_m;
const fsl_cx fsl_cx_empty = fsl_cx_empty_m;
const fsl_cx_config fsl_cx_config_empty = fsl_cx_config_empty_m;
const fsl_db fsl_db_empty = fsl_db_empty_m;
const fsl_deck fsl_cardset_empty = fsl_deck_empty_m;
const fsl_deck fsl_deck_empty = fsl_deck_empty_m;
const fsl_error fsl_error_empty = fsl_error_empty_m;
const fsl_fstat fsl_fstat_empty = fsl_fstat_empty_m;
const fsl_init_param fsl_init_param_default = fsl_init_param_default_m;
const fsl_init_param fsl_init_param_empty = fsl_init_param_empty_m;
const fsl_list fsl_list_empty = fsl_list_empty_m;
const fsl_mf fsl_mf_empty = fsl_mf_empty_m;
const fsl_mf_file fsl_mf_file_empty = fsl_mf_file_empty_m;
const fsl_mf_tag fsl_mf_tag_empty = fsl_mf_tag_empty_m;
const fsl_outputer fsl_outputer_FILE = fsl_outputer_FILE_m;
const fsl_pair_buf fsl_pair_buf_empty = fsl_pair_buf_empty_m;

const fsl_stmt fsl_stmt_empty = {
NULL/*db*/,
NULL/*stmt*/,
fsl_buffer_empty_m/*sql*/,
0/*colCount*/,
0/*paramCount*/,
NULL/*allocStamp*/
};

#define fsl_db_record_empty_m {                 \
FSL_TYPE_INVALID /*typeId*/,\
0/*dbId*/,\
NULL/*next*/\
}
const fsl_db_record fsl_db_record_empty = fsl_db_record_empty_m;

const fsl_user_r fsl_user_r_empty = {
fsl_db_record_empty_m /*base*/,
fsl_buffer_empty_m /* name */,
FSL_ROLE_GUEST /* roles */,
0/*mtime*/
};

const fsl_tag_r fsl_tag_r_empty = {
fsl_db_record_empty_m /*base*/,
fsl_buffer_empty_m /* key */,
fsl_buffer_empty_m /* value */
};

const fsl_blob_r fsl_blob_r_empty = {
fsl_db_record_empty_m /*base*/,
0/*mtime*/,
{/*sha1*/0}
};

const fsl_allocator fsl_allocator_stdalloc = {
fsl_realloc_f_stdalloc,
NULL
};

fsl_allocator fsl_memory_allocator = {
fsl_realloc_f_stdalloc,
NULL
};



void * fsl_malloc( fsl_size_t n ){
  return n
    ? fsl_memory_allocator.f(fsl_memory_allocator.state, NULL, n)
    : NULL;
}

void fsl_free( void * mem ){
  if(mem) fsl_memory_allocator.f(fsl_memory_allocator.state, mem, 0);
}

void * fsl_realloc( void * mem, fsl_size_t n ){
  if(!mem){
    return fsl_malloc(n);
  }else if(!n){
    fsl_free(mem);
    return NULL;
  }else{
    return fsl_memory_allocator.f(fsl_memory_allocator.state, mem, n);
  }
}

void * fsl_realloc_f_stdalloc(void * state, void * mem, fsl_size_t n){
  if(!mem){
    return malloc(n);
  }else if(!n){
    free(mem);
    return NULL;
  }else{
    return realloc(mem, n);
  }
}



int fsl_flush_f_FILE(void * _FILE){
  return _FILE
    ? (fflush((FILE*)_FILE) ? FSL_RC_IO : 0)
    : FSL_RC_MISUSE;
}

int fsl_output_f_FILE( void * state,
                       void const * src, fsl_size_t n ){
  return !state
    ? FSL_RC_MISUSE
    : ((1 == fwrite(src, n, 1, state ? (FILE*)state : stdout))
       ? 0
       : FSL_RC_IO);
}

int fsl_input_FILE( void * state, void * dest, fsl_size_t * n ){
  FILE * f = (FILE*) state;
  if( !state || !dest || !n ) return FSL_RC_MISUSE;
  else if( !*n ) return FSL_RC_RANGE;
  *n = (fsl_size_t)fread( dest, 1, *n, f );
  return !*n
    ? (feof(f) ? 0 : FSL_RC_IO)
    : 0;
}

void fsl_finalizer_f_FILE( void * state, void * mem ){
  if(mem && (stdout != mem) && (stderr != mem)){
    fclose((FILE*)mem);
  }
}

int fsl_output_f_buffer( void * state,
                       void const * src, fsl_size_t n ){
  return !state
    ? FSL_RC_MISUSE
    : fsl_buffer_append((fsl_buffer*)state, src, n);
}

int fsl_finalizer_f_buffer( void * state, void * mem ){
  fsl_buffer * b = (fsl_buffer*)mem;
  fsl_buffer_reserve(b, 0);
  *b = fsl_buffer_empty;
  return 0;
}

int fsl_cx_init( fsl_cx ** tgt, fsl_init_param * param ){
  static fsl_init_param paramDefaults = fsl_init_param_default_m;
  int rc;
  fsl_cx * f;
  if(!tgt) return FSL_RC_MISUSE;
  else if(!param){
    if(!paramDefaults.output.state.state){
      paramDefaults.output.state.state = stdout;
    }
    param = &paramDefaults;
  }
  f = (fsl_cx*)fsl_malloc(sizeof(fsl_cx));
  if(!f) return FSL_RC_OOM;
  *tgt = f;

  *f = fsl_cx_empty;
  f->output = param->output;
  f->config = param->config;
  f->dbMain.f = f;
  rc = fsl_db_open(&f->dbMain, /*"/tmp/foo.db"*/ ":memory:",
                   FSL_OPEN_F_RWC)
    /* i'd prefer to use a temporary file here, because
       sqlite does not allow us to create TEMP views/tables
       in a non-main db. e.g.

       CREATE TEMPORARY VIEW foo.BAR ...

       is not legal.
    */;
  if(rc){
    fsl_error_move( &f->dbMain.error, &f->error );
  }else{
    f->dbMain.role = FSL_DB_ROLE_MAIN;
  }
  return rc;
}
    

int fsl_cx_finalize( fsl_cx * f ){
  if(!f) return FSL_RC_MISUSE;
  else{
    if(f->clientState.finalize.f){
      f->clientState.finalize.f( f->clientState.finalize.state,
                                 f->clientState.state );
    }
    if(f->output.state.finalize.f){
      f->output.state.finalize.f( f->output.state.finalize.state,
                                  f->output.state.state );
    }
    fsl_error_clean(&f->error);
    fsl_db_close(&f->dbMain);
    fsl_buffer_reserve(&f->fileConfig, 0);
    fsl_buffer_reserve(&f->fileRepo, 0);
    fsl_buffer_reserve(&f->fileCkout, 0);
    fsl_buffer_reserve(&f->scratch, 0);
    fsl_buffer_reserve(&f->dirCkout, 0);
    *f = fsl_cx_empty;
    fsl_free(f);
    return 0;
  }
}

int fsl_output( fsl_cx * cx, void const * src, fsl_size_t n ){
  if(!cx || !src) return FSL_RC_MISUSE;
  else if(!n || !cx->output.out) return 0;
  else return cx->output.out( cx->output.state.state,
                              src, n );
}

void fsl_error_clean( fsl_error * err ){
  if(err){
    if(err->msg.mem) fsl_buffer_reserve(&err->msg, 0);
    *err = fsl_error_empty;
  }
}

void fsl_error_move( fsl_error * lower, fsl_error * higher ){
  fsl_error const err = *lower;
  *lower = *higher;
  lower->code = 0;
  lower->msg.used = 0;
  *higher = err;
}


int fsl_error_setv( fsl_error * err, int code, char const * fmt,
                  va_list args ){
  if(!err) return FSL_RC_MISUSE;
  else if(!code){ /* clear error state */
    err->code = 0;
    err->msg.used = 0;
    if(err->msg.mem){
      err->msg.mem[0] = 0;
    }
    return 0;
  }else{
    int rc = 0;
    err->msg.used = 0;
    err->code = code;
    if(FSL_RC_OOM!=code){
      rc = fmt
        ? fsl_buffer_appendfv(&err->msg, fmt, args)
        : fsl_buffer_append(&err->msg, fsl_rc_cstr(code), -1);
    }
    return rc ? rc : code;
  }
}

int fsl_error_set( fsl_error * err, int code, char const * fmt,
                 ... ){
  int rc;
  va_list args;
  va_start(args,fmt);
  rc = fsl_error_setv(err, code, fmt, args);
  va_end(args);
  return rc;
}


int fsl_error_get( fsl_error * err, char const ** str, fsl_size_t * len ){
  if(!err) return FSL_RC_MISUSE;
  else{
    if(str) *str = err->msg.used
      ? (char const *)err->msg.mem
      : NULL;
    if(len) *len = err->msg.used;
    return err->code;
  }
}

int fsl_cx_err_set_e( fsl_cx * f, fsl_error * err ){
  if(!f) return FSL_RC_MISUSE;
  else if(!err){
    return fsl_cx_err_set(f, 0, NULL);
  }else{
    fsl_error_move(err, &f->error);
    fsl_error_clean(err);
    return f->error.code;
  }
}

int fsl_cx_err_setv( fsl_cx * f, int code, char const * fmt,
                  va_list args ){
  return f
    ? fsl_error_setv( &f->error, code, fmt, args )
    : FSL_RC_MISUSE;
}

int fsl_cx_err_set( fsl_cx * f, int code, char const * fmt,
                 ... ){
  if(!f) return FSL_RC_MISUSE;
  else{
    int rc;
    va_list args;
    va_start(args,fmt);
    rc = fsl_error_setv( &f->error, code, fmt, args );
    va_end(args);
    return rc;
  }
}

int fsl_cx_err_get( fsl_cx * f, char const ** str, fsl_size_t * len ){
  return f
    ? fsl_error_get( &f->error, str, len )
    : FSL_RC_MISUSE;
}

fsl_id_t fsl_cx_last_insert_id(fsl_cx * f){
  return (f && f->dbMain.dbh)
    ? fsl_db_last_insert_id(&f->dbMain)
    : -1;
}

/*
** fsl_appendf_f() impl which sends its output to fsl_output(). state
** must be a (fsl_cx*).
*/
static fsl_int_t fsl_appendf_f_fsl_output( void * state, char const * s,
                                           fsl_int_t n ){
  fsl_cx * f = (fsl_cx *)state;
  return fsl_output( f, s, (fsl_size_t)n )
    ? -1
    : n;
}

int fsl_outputfv( fsl_cx * f, char const * fmt, va_list args ){
  if(!f || !fmt) return FSL_RC_MISUSE;
  else if(!*fmt) return FSL_RC_RANGE;
  else{
    long const prc = fsl_appendfv( fsl_appendf_f_fsl_output,
                                   f, fmt, args );
    return (prc>=0) ? 0 : FSL_RC_IO;
  }
}

    
int fsl_outputf( fsl_cx * f, char const * fmt, ... ){
  if(!f || !fmt) return FSL_RC_MISUSE;
  else if(!*fmt) return FSL_RC_RANGE;
  else{
    int rc;
    va_list args;
    va_start(args,fmt);
    rc = fsl_outputfv( f, fmt, args );
    va_end(args);
    return rc;
  }
}


char const * fsl_rc_cstr(int rc){
  fsl_rc_t const RC = (fsl_rc_t)rc
    /* we do this so that gcc will warn if the switch() below is
       missing any fsl_rc_t entries. */
    ;
  switch(RC){
#define STR(T) case FSL_RC_##T: return "FSL_RC_" #T
    STR(ACCESS);
    STR(ALREADY_EXISTS);
    STR(AMBIGUOUS);
    STR(BREAK);
    STR(CHECKSUM_MISMATCH);
    STR(CONSISTENCY);
    STR(DB);
    STR(DELTA_INVALID_OPERATOR);
    STR(DELTA_INVALID_SEPARATOR);
    STR(DELTA_INVALID_SIZE);
    STR(DELTA_INVALID_TERMINATOR);
    STR(ERROR);
    STR(IO);
    STR(MF_SYNTAX);
    STR(MISUSE);
    STR(NOT_A_CHECKOUT);
    STR(NOT_A_REPO);
    STR(NOT_FOUND);
    STR(NYI);
    STR(OK);
    STR(OOM);
    STR(RANGE);
    STR(REPO_MISMATCH);
    STR(REPO_NEEDS_REBUILD);
    STR(REPO_VERSION);
    STR(SIZE_MISMATCH);
    STR(STEP_DONE);
    STR(STEP_ERROR);
    STR(STEP_ROW);
    STR(TYPE);
    STR(TRAILING_COMMA_KLUDGE);
#undef STR
  }
  return "Unknown result code";
}

char const * fsl_library_version(){
  return FSL_LIBRARY_VERSION;
}

char fsl_library_version_matches(char const * yourLibVersion){
  return 0 == fsl_strcmp(FSL_LIBRARY_VERSION, yourLibVersion);
}

fsl_size_t fsl_list_reserve( fsl_list * self, fsl_size_t n )
{
    if( !self ) return 0;
    else if(0 == n){
      if(0 == self->capacity) return 0;
      fsl_free(self->list);
      self->list = NULL;
      self->capacity = self->used = 0;
      return 0;
    }
    else if( self->capacity >= n ){
      return self->capacity;
    }
    else{
      size_t const sz = sizeof(void*) * n;
      void* * m = (void**)fsl_realloc( self->list, sz );
      if( !m ) return self->capacity;
      memset( m + self->capacity, 0, (sizeof(void*)*(n-self->capacity)));
      self->capacity = n;
      self->list = m;
      return n;
    }
}

int fsl_list_append( fsl_list * self, void* cp ){
    if( !self ) return FSL_RC_MISUSE;
    else if( self->capacity > fsl_list_reserve(self,
                                              self->used+1)){
      return FSL_RC_OOM;
    }
    else{
      self->list[self->used++] = cp;
      if(self->used< self->capacity-1) self->list[self->used]=0;
      return 0;
    }
}

int fsl_list_v_fsl_free(void * obj, void * visitorState ){
  if(obj) fsl_free( obj );
  return 0;
}

int fsl_list_visit( fsl_list const * self, char order,
                    fsl_list_visitor_f visitor,
                    void * visitorState ){
  int rc = FSL_RC_OK;
  if( self && self->used && visitor ){
    int i = 0;
    int pos = (order<0) ? self->used-1 : 0;
    int step = (order<0) ? -1 : 1;
    for( rc = 0; (i < self->used)
           && (0 == rc); ++i, pos+=step ){
      void* obj = self->list[pos];
      if(obj) rc = visitor( obj, visitorState );
      if( obj != self->list[pos] ){
        --i;
        if(order>=0) pos -= step;
      }
    }
  }
  return rc;
}


int fsl_list_visit_p( fsl_list * self, char order,
                      char shiftIfNulled,
                      fsl_list_visitor_f visitor, void * visitorState )
{
  int rc = FSL_RC_OK;
  if( self && self->used && visitor ){
    int i = 0;
    int pos = (order<0) ? self->used-1 : 0;
    int step = (order<0) ? -1 : 1;
    for( rc = 0; (i < (int)self->used)
           && (0 == rc); ++i, pos+=step ){
      void* obj = self->list[pos];
      if(obj) {
        assert((order<0) && "TEST THAT THIS WORKS WITH IN-ORDER!");
        rc = visitor( &self->list[pos], visitorState );
        if( shiftIfNulled && !self->list[pos]){
          int x = pos;
          int const to = self->used-pos;

          assert( to < (int) self->capacity );
          for( ; x < to; ++x ) self->list[x] = self->list[x+1];
          if( x < (int)self->capacity ) self->list[x] = 0;
          --i;
          --self->used;
          if(order>=0) pos -= step;
        }
      }
    }
  }
  return rc;
}

fsl_double_t fsl_unix_to_julian( fsl_time_t unix ){
  return (unix * 1.0 / 86400.0 ) + 2440587.5;
}

int fsl_strcmp(const char *zA, const char *zB){
  if( zA==0 ){
    if( zB==0 ) return 0;
    return -1;
  }else if( zB==0 ){
    return +1;
  }else{
    int a, b;
    do{
      a = *zA++;
      b = *zB++;
    }while( a==b && a!=0 );
    return ((unsigned char)a) - (unsigned char)b;
  }
}
int fsl_strncmp(const char *zA, const char *zB, fsl_size_t nByte){
  if( !zA ) return zB ? -1 : 0;
  else if( !zB ) return +1;
  else if(!nByte) return 0;
  else{
    int a, b;
    do{
      a = *zA++;
      b = *zB++;
    }while( a==b && a!=0 && (--nByte)>0 );
    return ((unsigned char)a) - (unsigned char)b;
  }
}

/*
** Case insensitive string comparison.
*/
int fsl_strnicmp(const char *zA, const char *zB, fsl_int_t nByte){
  if( zA==0 ){
    if( zB==0 ) return 0;
    return -1;
  }else if( zB==0 ){
    return +1;
  }
  if( nByte<0 ) nByte = (fsl_int_t)fsl_strlen(zB);
  return sqlite3_strnicmp(zA, zB, nByte);
}

int fsl_stricmp(const char *zA, const char *zB){
  fsl_int_t nByte;
  int rc;
  if( zA==0 ){
    if( zB==0 ) return 0;
    return -1;
  }else if( zB==0 ){
    return +1;
  }
  nByte = (fsl_int_t)fsl_strlen(zB);
  rc = sqlite3_strnicmp(zA, zB, nByte);
  if( rc==0 && zA[nByte] ) rc = 1;
  return rc;
}

fsl_size_t fsl_strlen( char const * src ){
  fsl_size_t i = 0;
  if(src) for( ; *src; ++i, ++src ){}
  return i;
}

char * fsl_strndup( char const * src, fsl_int_t len ){
  fsl_buffer b = fsl_buffer_empty;
  if(!src) return NULL;
  else if(len<0) len = (fsl_int_t)fsl_strlen(src);
  fsl_buffer_append( &b, len ? src : "\0", len ? len : 1 )
    /*special case for 0-length strings, to avoid returning NULL
      there. */;
  return (char*)b.mem;
}

char * fsl_strdup( char const * src ){
  return fsl_strndup(src, -1);
}

char fsl_str_glob(const char *zGlob, const char *z){
  int c, c2;
  int invert;
  int seen;
  while( (c = (*(zGlob++)))!=0 ){
    if( c=='*' ){
      while( (c=(*(zGlob++))) == '*' || c=='?' ){
        if( c=='?' && (*(z++))==0 ) return 0;
      }
      if( c==0 ){
        return 1;
      }else if( c=='[' ){
        while( *z && fsl_str_glob(zGlob-1,z)==0 ){
          z++;
        }
        return (*z)!=0;
      }
      while( (c2 = (*(z++)))!=0 ){
        while( c2!=c ){
          c2 = *(z++);
          if( c2==0 ) return 0;
        }
        if( fsl_str_glob(zGlob,z) ) return 1;
      }
      return 0;
    }else if( c=='?' ){
      if( (*(z++))==0 ) return 0;
    }else if( c=='[' ){
      int prior_c = 0;
      seen = 0;
      invert = 0;
      c = *(z++);
      if( c==0 ) return 0;
      c2 = *(zGlob++);
      if( c2=='^' ){
        invert = 1;
        c2 = *(zGlob++);
      }
      if( c2==']' ){
        if( c==']' ) seen = 1;
        c2 = *(zGlob++);
      }
      while( c2 && c2!=']' ){
        if( c2=='-' && zGlob[0]!=']' && zGlob[0]!=0 && prior_c>0 ){
          c2 = *(zGlob++);
          if( c>=prior_c && c<=c2 ) seen = 1;
          prior_c = 0;
        }else{
          if( c==c2 ){
            seen = 1;
          }
          prior_c = c2;
        }
        c2 = *(zGlob++);
      }
      if( c2==0 || (seen ^ invert)==0 ) return 0;
    }else{
      if( c!=(*(z++)) ) return 0;
    }
  }
  return *z==0;
}

fsl_int32_t fsl_cx_lget_int32( fsl_cx * f, fsl_int32_t dflt,
                               char const * key ){
  fsl_int32_t rv = dflt;
  fsl_db * db = fsl_cx_db_checkout(f);
  if(db){
    fsl_db_get_int32(db, &rv,
                     "SELECT value FROM %s.vvar WHERE name=%Q",
                     fsl_db_role_label(FSL_DB_ROLE_CHECKOUT),
                     key);
  }
  return rv;
}

fsl_int64_t fsl_cx_lget_int64( fsl_cx * f, fsl_int64_t dflt,
                               char const * key ){
  fsl_int64_t rv = dflt;
  fsl_db * db = fsl_cx_db_checkout(f);
  if(db){
    fsl_db_get_int64(db, &rv,
                     "SELECT value FROM %s.vvar WHERE name=%Q",
                     fsl_db_role_label(FSL_DB_ROLE_CHECKOUT),
                     key);
  }
  return rv;
}

fsl_id_t fsl_cx_lget_id( fsl_cx * f, fsl_id_t dflt,
                         char const * key ){
  fsl_id_t rv = dflt;
  fsl_db * db = fsl_cx_db_checkout(f);
  if(db){
    fsl_db_get_id(db, &rv,
                  "SELECT value FROM %s.vvar WHERE name=%Q",
                  fsl_db_role_label(FSL_DB_ROLE_CHECKOUT),
                  key);
  }
  return rv;
}

fsl_double_t fsl_cx_lget_double( fsl_cx * f, fsl_double_t dflt,
                                 char const * key ){
  fsl_double_t rv = dflt;
  fsl_db * db = fsl_cx_db_checkout(f);
  if(db){
    fsl_db_get_double(db, &rv,
                      "SELECT value FROM %s.vvar WHERE name=%Q",
                     fsl_db_role_label(FSL_DB_ROLE_CHECKOUT),
                      key);
  }
  return rv;
}

char * fsl_cx_lget_text( fsl_cx * f, fsl_size_t * len,
                         char const * key ){
  char * rv = NULL;
  fsl_db * db = fsl_cx_db_checkout(f);
  if(db){
    fsl_db_get_text(db, &rv, len,
                    "SELECT value FROM %s.vvar WHERE name=%Q",
                     fsl_db_role_label(FSL_DB_ROLE_CHECKOUT),
                    key);
  }
  return rv;
}

/*
** Return TRUE if the string begins with something that looks roughly
** like an ISO date/time string.  The SQLite date/time functions will
** have the final say-so about whether or not the date/time string is
** well-formed.
*/
char fsl_str_is_date(const char *z){
  if(!z || !*z) return 0;
  if( !fsl_isdigit(z[0]) ) return 0;
  if( !fsl_isdigit(z[1]) ) return 0;
  if( !fsl_isdigit(z[2]) ) return 0;
  if( !fsl_isdigit(z[3]) ) return 0;
  if( z[4]!='-') return 0;
  if( !fsl_isdigit(z[5]) ) return 0;
  if( !fsl_isdigit(z[6]) ) return 0;
  if( z[7]!='-') return 0;
  if( !fsl_isdigit(z[8]) ) return 0;
  if( !fsl_isdigit(z[9]) ) return 0;
  return 1;
}

char const * fsl_atype_cstr(fsl_artifact_t t){
  switch(t){
    case FSL_ATYPE_ANY: return "*";
    case FSL_ATYPE_CHECKIN: return "ci";
    case FSL_ATYPE_EVENT: return "e";
    case FSL_ATYPE_G: return "g";
    case FSL_ATYPE_TICKET: return "t";
    case FSL_ATYPE_WIKI: return "w";
    default:
      return NULL;
  }
}


int fsl_cx_sym_to_rid( fsl_cx * f, char const * sym, fsl_artifact_t type,
                       fsl_id_t * rv ){
  fsl_id_t rid = 0;
  fsl_id_t vid;
  fsl_size_t symLen;
  /* fsl_int_t i; */
  fsl_db * dbR = fsl_cx_db_repo(f);
  fsl_db * dbC = fsl_cx_db_checkout(f);
  if(!f || !sym || !*sym || !rv) return FSL_RC_MISUSE;
  else if(!dbR) return FSL_RC_NOT_A_REPO;
  /* special keyword: "tip" */
  if( 0==fsl_strcmp(sym,"tip")
      && (FSL_ATYPE_ANY==type || FSL_ATYPE_CHECKIN==type)){
    rid = fsl_db_g_id(dbR, 0,
                      "SELECT objid FROM event"
                      " WHERE type='ci'"
                      " ORDER BY event.mtime DESC"
                      " LIMIT 1");
    if(rid>0) goto gotit;
  }
  /* special keywords: "prev", "previous", "current", and "next".
     These require a checkout.
  */
  vid = dbC
    ? fsl_cx_lget_id(f, 0, "checkout")
    : 0;
  if( vid>0){
    if( 0==fsl_strcmp(sym, "current") ){
      rid = vid;
    }
    else if( 0==fsl_strcmp(sym, "prev")
              || 0==fsl_strcmp(sym, "previous") ){
      rid = fsl_db_g_id(dbR, 0,
                        "SELECT pid FROM plink WHERE "
                        "cid=%"FSL_ID_T_PFMT" AND isprim",
                        vid);
    }
    else if( 0==fsl_strcmp(sym, "next") ){
      rid = fsl_db_g_id(dbR, 0,
                        "SELECT cid FROM plink WHERE "
                        "pid=%"FSL_ID_T_PFMT
                        " ORDER BY isprim DESC, mtime DESC",
                        vid);
    }
    if(rid>0) goto gotit;
  }

  /* Date and times */
  if( 0==memcmp(sym, "date:", 5) ){
    rid = fsl_db_g_id(dbR, 0, 
      "SELECT objid FROM event"
      " WHERE mtime<=julianday(%Q,'utc') AND type GLOB '%q'"
      " ORDER BY mtime DESC LIMIT 1",
      &sym[5], sym);
    *rv = rid;
    return 0;
  }
  if( fsl_str_is_date(sym) ){
    rid = fsl_db_g_id(dbR, 0, 
      "SELECT objid FROM event"
      " WHERE mtime<=julianday(%Q,'utc') AND type GLOB '%q'"
      " ORDER BY mtime DESC LIMIT 1",
      sym, fsl_atype_cstr(type));
    if(rid>0) goto gotit;
  }

  /* Deprecated time formats elided: local:..., utc:... */

  /* "tag:" + symbolic-name */
  if( memcmp(sym, "tag:", 4)==0 ){
    rid = fsl_db_g_id(dbR, 0,
       "SELECT event.objid, max(event.mtime)"
       "  FROM tag, tagxref, event"
       " WHERE tag.tagname='sym-%q' "
       "   AND tagxref.tagid=tag.tagid AND tagxref.tagtype>0 "
       "   AND event.objid=tagxref.rid "
       "   AND event.type GLOB '%q'",
       &sym[4], fsl_atype_cstr(type)
    );
    goto gotit;
  }

#if 0
  /* TODO */
  /* root:TAG -> The origin of the branch */
  if( memcmp(zTag, "root:", 5)==0 ){
    Stmt q;
    int rc;
    char *zBr;
    rid = symbolic_name_to_rid(zTag+5, zType);
    zBr = db_text("trunk","SELECT value FROM tagxref"
                          " WHERE rid=%d AND tagid=%d"
                          " AND tagtype>0",
                          rid, TAG_BRANCH);
    db_prepare(&q,
      "SELECT pid, EXISTS(SELECT 1 FROM tagxref"
                         " WHERE tagid=%d AND tagtype>0"
                         "   AND value=%Q AND rid=plink.pid)"
      "  FROM plink"
      " WHERE cid=:cid AND isprim",
      TAG_BRANCH, zBr
    );
    fossil_free(zBr);
    do{
      db_reset(&q);
      db_bind_int(&q, ":cid", rid);
      rc = db_step(&q);
      if( rc!=SQLITE_ROW ) break;
      rid = db_column_int(&q, 0);
    }while( db_column_int(&q, 1)==1 && rid>0 );
    db_finalize(&q);
    return rid;
  }
#endif

  symLen = fsl_strlen(sym);
  /* SHA1 hash or prefix */
  if( symLen>=4
      && symLen<=FSL_UUID_STRLEN
      && fsl_validate16(sym, symLen) ){
    fsl_stmt q = fsl_stmt_empty;
    char zUuid[FSL_UUID_STRLEN+1];
    memcpy(zUuid, sym, symLen);
    zUuid[symLen] = 0;
    fsl_canonical16(zUuid, symLen);
    rid = 0;
    if( FSL_ATYPE_ANY==type ){
      fsl_stmt_prepare(dbR, &q,
                       "SELECT rid FROM blob WHERE uuid GLOB '%s*'",
                       zUuid);
    }else{
      fsl_stmt_prepare(dbR, &q,
        "SELECT blob.rid"
        "  FROM blob, event"
        " WHERE blob.uuid GLOB '%s*'"
        "   AND event.objid=blob.rid"
        "   AND event.type GLOB '%q'",
        zUuid, fsl_atype_cstr(type)
      );
    }
    if( fsl_stmt_step(&q)==FSL_RC_STEP_ROW ){
      fsl_int64_t r64 = 0;
      fsl_stmt_get_int64(&q, 0, &r64);
      if( fsl_stmt_step(&q)==FSL_RC_STEP_ROW ) rid = -1
        /* Ambiguous results */
        ;
      else rid = (fsl_id_t)r64;
    }
    fsl_stmt_finalize(&q);
    if(rid<0) return FSL_RC_AMBIGUOUS;
    else if(rid>0) goto gotit;
  }

  /* Symbolic name ... */
  rid = fsl_db_g_id(dbR, 0,
    "SELECT event.objid, max(event.mtime)"
    "  FROM tag, tagxref, event"
    " WHERE tag.tagname='sym-%q' "
    "   AND tagxref.tagid=tag.tagid AND tagxref.tagtype>0 "
    "   AND event.objid=tagxref.rid "
    "   AND event.type GLOB '%q'",
    sym, fsl_atype_cstr(type)
  );
  if( rid>0 ) goto gotit;

#if 0
  /*
    v1 has this undocumented feature, but it is being elided for now.
  */
  /* Undocumented:  numeric tags get translated directly into the RID */
  for(i=0; fsl_isdigit(sym[i]); i++){}
  if( sym[i]==0 ){
    if( FSL_ATYPE_ANY==type ){
      rid = atoi(sym);
    }else{
      rid = fsl_db_g_id(dbR, 0, 
        "SELECT event.objid"
        "  FROM event"
        " WHERE event.objid=%s"
        "   AND event.type GLOB '%q'", zTag, zType);
    }
    if( rid>0 ) goto gotit;
  }
#endif

  return FSL_RC_NOT_FOUND;
  gotit:
  *rv = rid;
  return 0;
}

int fsl_cx_sym_to_uuid( fsl_cx * f, char const * sym, fsl_artifact_t type,
                        char ** rv, fsl_id_t * rvId ){
  fsl_id_t rid = 0;
  int rc = fsl_cx_sym_to_rid(f, sym, type, &rid);
  if(rvId) *rvId = rid;
  return rc
    ? rc
    : fsl_db_get_text( &f->dbMain, rv, NULL,
                       "SELECT uuid FROM blob WHERE rid=%"FSL_ID_T_PFMT,
                       rid );
}
