/*
  Unit tests and demo of the [versionedsettings] and [settingsmetadata]
  vtables.
*/
if(1){
    s2out << __FLC << ": versionedsettings and settingsmetadata tests disabled because "
        << "they are inexplicably flaky.\n";
    return;
}
const f = Fossil.Context.new({
    //traceSql: true // enable this for a more educational experience
}).openCheckout();
const vSettingsDir = f.db.selectValue("select fsl_ckout_dir()||'.fossil-settings/'");
affirm vSettingsDir.indexOf('.fossil-settings') > 0;

scope {
    const globVal = f.db.selectValue("select value from versionedsettings where name='binary-glob'");
    affirm globVal.indexOf('*.png')>=0;
    var count = 0;
    f.db.each({
        sql: 'select /*name as name, value as value*/ * from versionedsettings',
        callback: proc(){
            ++count;
            if(1===rowNumber){
                //print(__FLC,columnNames);
                affirm 2 === columnNames.length();
                /* TODO: check column names, once they're final */
            }
        }
    });
    affirm count > 2 /* libfossil has 3 as of this writing */;
}

/**
   A helper to get a versioned setting. If no such setting is found,
   then: if useMetaDefault is truthy and its fossil(1)-defined default
   value is returned, otherwise undefined is returned.
*/
f.vsGet = proc(key,useMetaDefault=true){
    affirm 'string' === typename key;
    return this.db.selectValue((useMetaDefault ? fmtMeta : fmtSimple), key);
}.importSymbols({
    fmtSimple: "select value from versionedsettings where name=?1",
    fmtMeta: <<<SQL
    select coalesce(v.value,m.default_value) from settingsmetadata m left join versionedsettings v
    on v.name=m.name where m.name=?1 SQL
});

if(0){
    print(__FLC,"Transaction tests disabled by stephan on 20141121 because of inexplicable weirdness at a time where other priorities take precedence.")
} || scope {
    const FS = Fossil.file; // convenience shortcut
    const db = f.db; // convenience shortcut
    const vsetting = 'allow-symlinks' /* the name of a setting libf's core repo does not currently use */;
    const vfile = vSettingsDir + vsetting /* path to vsettings' file */;
    const defaultVal = db.selectValue(
        // fossil-level default value for vsetting
        'select default_value from settingsmetadata where name=%1$Q'.applyFormat(vsetting)
    );
    affirm undefined !== defaultVal /* Expecting 'on' or 'off' or similar. */;
    // Make sure we'v got a clean working slate...
    catch FS.unlink(vfile);
    affirm !FS.isAccessible(vfile) /* Expecting to have no file for vsetting */;
    affirm 0 === db.transactionState() /* This test requires that no higher-level transaction be active. */;
    //print(__FLC,'stat(',vfile,') =',FS.stat(vfile));
    //print(__FLC, 'select default_value from settingsmetadata where name=%1$Q'.applyFormat(vsetting));
    //print(__FLC,defaultVal, f.vsGet(vsetting));
    //s2out << __FLC<< ': defaultVal = '<<defaultVal<< ', f.vsGet(vsetting) = '<<f.vsGet(vsetting)<<'\n';
    affirm defaultVal === f.vsGet(vsetting) /* sanity check */;
    affirm undefined === f.vsGet(vsetting,false) /* sanity check */;

    var ex = catch db.selectValue(
        "insert into versionedsettings(name,value) values('unknown-key',null)"
    );
    affirm ex /* Expecting exception for unknown vsettings key. */;
    //print(ex.codeString(),ex);
    affirm ex.message.indexOf('must be')>0 /* expecting error message to contain this substring */;
    affirm defaultVal === f.vsGet(vsetting) /* Expecting the insert to have failed. */;
    unset ex;

    // Make sure that transactions behave as we expect them to...
    db.transaction(proc(){
        this.selectValue(
            "insert into versionedsettings(name,value) values('%1$q','yes')".applyFormat(vsetting)
        );
        affirm 'yes' === f.vsGet(vsetting) /* Expecting INSERT to have written this. */;
        //print(__FLC,FS.stat(vfile));
        affirm !FS.isAccessible(vfile) /* The new file is not written until commit. */;
    });
    affirm FS.isAccessible(vfile);
    affirm 0 === db.transactionState() /* Expecting prev. transaction() to have been the top-most transaction. */;
    affirm 'yes' === f.vsGet(vsetting);
    /**
       2021-02-08: this select fails with an sqlite error if run from inside transaction():
       "cannot delete with open cursors". Whether that's due to a bug in the vtable or
       a change in sqlite's behavior since it was implemented is unknown
    */
    db.selectValue(
        "delete from versionedsettings where name=?1",
        vsetting
    );
    affirm !FS.isAccessible(vfile) /* file deleted, right? */;
    affirm defaultVal === f.vsGet(vsetting) /* Expecting DELETE to revert this. */;

    db.transaction(proc(){
        this.selectValue(
            "insert or replace into versionedsettings(name,value) values($name,$no)",
            {$name:vsetting, $no: 'no'}
        );
        affirm !FS.isAccessible(vfile) /* New files not written until commit. */;
        affirm 'no' === f.vsGet(vsetting) /* Expecting to be able to read back
                                             the new value before COMMIT */;
    });
    affirm 0 === db.transactionState() /* Expecting prev. transaction() to have been the top-most transaction. */;
    //print(__FLC,'stat(',vfile,') =',FS.stat(vfile));
    affirm FS.isAccessible(vfile) /* Expecting transaction to write it. */;
    affirm 'no' === f.vsGet(vsetting) /* Expecting transaction to write this. */;

    db.transaction(proc(){
        this.selectValue(
            "delete from versionedsettings where name=?1",
            [vsetting]
        );
        affirm FS.isAccessible(vfile) /* Not removed until commit. */;
        affirm defaultVal === f.vsGet(vsetting) /* Expecting DELETE to revert this. */;
    });
    affirm 0 === db.transactionState() /* Expecting prev. transaction() to have been the top-most transaction. */;
    affirm !FS.isAccessible(vfile) /* Expecting transaction to delete it. */;
    affirm defaultVal === f.vsGet(vsetting) /* Expecting to be back to our starting point. */;
}
