/* Introductory test/demo script for the libfossil th1ish bindings. */
ARGV.flags.c = '.'
[import {test-common.th1ish}]

scope { /* Some generic test code... */
    const FI = Fossil.file
    assert FI.isAccessible(__FILE)
    assert !FI.isAccessible(__FILE + '.nope')
    assert [FI.isFile __FILE]
    assert ![FI.isFile __FILE + '.baz']
    assert 0 < [FI.size __FILE]
    [print "This script's name: " __FILE ' ==canonical==> ' [FI.canonicalName __FILE]]

    //[print __SRC ': mtime of this script: ' [strftime '%c' [FI.mtime __FILE]]]
    var repoDb = ARGV.flags.r
    const f = Fossil.createContext()
    if(repoDb){
        [print __SRC ': Repo db: ' f.db.repo]
    }


    assert f inherits Fossil.Context
    [print __SRC ': f=' f ', f.db=' f.db ]
    //[api.ob.push]
    if(1 && f.db){
        var list = array['repo', 'checkout']
        const n = [list.length]
        for{var k, db, fname, i = 0}{i<n}{i+=1}{
            k = list.(i)
            db = f.db.(k)
            db || continue
            [print __SRC ": Db details:"]
            fname = db.filename
            [print "\tDb type: " k]
            [print "\tDB file: " fname]
            [print '\tSize:\t' [FI.size fname]]
            [print '\tmtime:\t' [strftime '%c' [FI.mtime fname]]]
        }

        if(f.db.checkout){
            var x = $f.selectVal {
                SELECT value FROM vvar WHERE name='repository'
            }
            $print 'selectVal = ' x
            assert 'string' === typename x
            x = $f.selectVal {SELECT value FROM vvar WHERE 0}
            //$print 'selectVal = ' x
            assert undefined === x
            x = $f.selectVal {SELECT NULL}
            //$print 'selectVal = ' x
            assert null === x
        }

    }
    //[api.ob.pop]

    assert f.db
    assert 'Db' === typename f.db
    if(f.db.repo){

        var uuid = [f.resolveUuid 'trunk']
        assert 'string'===typename uuid
        assert 40 === [uuid.length]
        $print "Repo trunk: " uuid
        unset uuid

        $print "Stash entries:" f.selectVal
        [f.dumpQuery
            {SELECT stashid AS stashid,
                vid AS vid,
                datetime(ctime) AS ctime,
                comment AS comment
             FROM stash
             ORDER BY stashid DESC
            }
            undefined // bind parameter(s, array)
            '\t'  // record separator
        ]
    }


    /**
        (Former) bug reminder: if we take a reference to
        one of the native db handles and then use
        it after closing the native db handle
        via [f.closeRepo] or [f.closeCheckout]
        then the handle holds a stale native pointer.
        We can fix this using cwal_weak_ref, but
        so far it's not worth the effort. As long as
        we just let the handle lie, and don't call any
        bound native methods, this is okay, but calling
        methods will have undefined results.

        hackhackhack... the binding now simply invalidates
        those handles then closeRepo/Checkout() are called.
    */
    var unlinked = f.db.repo
    if(0) {    
        [f.closeRepo]
        assert !f.db.repo
        assert 'exception' === typename catch{[f.closeRepo]}
    }else if(1){
        $f.closeCheckout
        assert !f.db.repo
        assert !f.db.checkout
        assert 'exception' === typename catch{[f.closeRepo]}
        assert 'exception' === typename catch{[f.closeCheckout]}
    }
    if(!f.db.repo){
        // see comments above
        assert Fossil.rc.TYPE === catch{[unlinked.exec "SELECT 1"]}.code
    }

    /**
        [f.openDb ...] works slightly differently than
        [Fossil.openDb]. If a FossilContext is the 'this' of
        an openDb() call then the db connection gets the array
        of fossil-specific DB functions installed and it may
        (depending on how it's called) require that the db
        be a valid repo. The "extra features" include that the
        db will use the Fossil Context's traceSql setting.
    */

    /**
        openMode is a string which describes how to open/interpret a db file.
        The string can contain any of the characters 'r', 'w', 'c', 'v'.
        The default access mode is 'rwc' (largely for historical
        reasons). If the string contains 'w', write mode is added, and
        'c' means "create if not exists"
        and implies 'w'. The 'v' flag means to verify that the db is
        a repository. This validation currently only works when the db
        is opened via Context.openDb(), not Fossil.openDb() (they are
        the same function, they just interpret the 'this' argument
        differently).
    */
    var openMode = 'c'
    var db = [f.openDb
        //":memory:"
        __FILE+'.db'
        openMode
    ]
    [print __SRC ': db=' db ' ==> ' db.filename]
    var ex = catch {
        [db.execMulti {
            BEGIN;
            DROP TABLE IF EXISTS t;
            CREATE TABLE t(a,b);
            COMMIT;
        }]

        var st = [db.prepare "INSERT INTO t (a,b) VALUES (?,?)"]
        [print __SRC ": Prepared statement: " st]
        assert 2 === st.parameterCount
        assert 0 === st.columnCount

        var buf = [api.Buffer 5]
        [buf.append 'bbb']
        [st.bind 1 'aaa']
        [st.bind 2 buf]
        [st.step]

        [st.reset]
        if(0){
            const FI = Fossil.file
            [buf.capacity [FI.size __FILE] + 2]
            [buf.length 0]
            [buf.readFile __FILE]
            var sz = [buf.length]
            assert sz > 0
            [buf.compress]
            [st.bind array['ccc',buf]]
        }else{
            [st.bind array['ccc','ddd']]
        }
        unset buf
        [st.step]
        [st.finalize]
        assert Fossil.rc.TYPE === catch{[st.finalize]}.code

        st = [db.prepare "SELECT a a,b b FROM t WHERE ? ORDER BY ROWID"]
        assert 2 === st.columnCount
        assert 1 === st.parameterCount
        assert 2 === [st.columnNames.length]
        [st.bind 1 1]
        assert [st.step]
        assert 'aaa' === [st.get 0]
        var b = [st.get 1]
        ('buffer'===typename b) && (b = [b.toString])
        assert 'bbb' === b

        [st.reset]
        var a = [st.stepArray]
        assert 'array' === typename a
        assert 'aaa' === a.0
        b = a.1
        ('buffer'===typename b) && (b = [b.toString])
        assert 'bbb' === b

        [st.reset]
        var o = [st.stepObject]
        assert 'object' === typename o
        assert 'aaa' === o.a
        b = o.b
        ('buffer'===typename b) && (b = [b.toString])
        assert 'bbb' === b

        [st.finalize]        
    }
    ex && throw ex//[print __SRC ": EXCEPTION:" ex]

    const stringify = proc(v){
        var b = [api.Buffer]
        [b.appendf '%1$J' v]
        return [b.toString]
    }

    var list = array[]
    db.each(object{
        sql: {SELECT a a, b b FROM t WHERE ?},
        bind: true,
        mode: 1, //1==array (default), 0==object
        callback:proc(){
            assert 2 === [columnNames.length]
            assert 'a' === columnNames.0
            assert 'b' === columnNames.1
            assert rowNumber > 0
            /** BUG: when this callback is a string,
                the script name is unknown in assertion trace
                and exceptions here, and line/col are relative to
                this callback string. This is because that level
                of script bindings doesn't have the call location
                info and the script engine cannot figure it out
                because of how src locations/script names are
                figured out on demand. Not sure how to fix that
                (needs to be done (or enabled) at the th1ish
                level).
            */
            /* __SRC is also (of course) wrong here if callback
               is-a String/Buffer.
            */
            list.push(clone(this))
            $print __SRC ': row #' rowNumber '=' this
        }
    })
    $print 'Collected results:' list

    [db.close]
    assert Fossil.rc.TYPE === catch{[db.close]}.code

    //[f.finalize] // optional (or let GC take care of it at end of scope)

}

1 && scope {
    // Holy cow, zlib is a memory hog: it mallocs 64k
    // for each de/compression op.
    const FI = Fossil.file
    var b = api.Buffer(FI.size(__FILE) + 2)
    $b.readFile __FILE
    assert b inherits api.prototypes.Buffer
    var sz = [b.length]
    assert sz > 0
    [b.compress]
    var szC = [b.length]
    assert szC < sz
    print("Compressed buffer: ",sz," ==> ",szC)
    var zFile = __FILE+'.z';
    [b.writeToFile zFile]
    print("Compressed copy written to ",zFile);
    assert szC === [FI.size zFile]
    [b.uncompress]
    assert sz === [b.length]
    print("Decompressed buffer: ",szC," ==> ",sz)
    //print(b.toString())
}

1 && scope {
    var b = api.Buffer(30)
    $b.append {abcdefghijklm}
    var bb = clone(b)
    assert bb.length() === b.length()
    assert bb === b
    assert 0 === b.compare(bb)
    for {var i = 0, sz=bb.length()}{i<sz}{i+=1}{
        assert b.byteAt(i) === bb.byteAt(i)
    }
    bb.append("hi")
    assert b.compare(bb) < 0
    assert bb.compare(b) > 0
    assert bb !== b
    assert bb != b
}
