assert api.cgi
/**
    Dispatches to one of api.cgi.routes, depending on
    the PATH_INFO. It either throws or exit()s, but never
    returns successfully.
*/
api.cgi.dispatchRoute = proc(){
    const R = this.request

    this.util.assertHasRepo()

    // Look for a CGI.routes entry matching PATH_INFO...
    var ps = R.ENV.PATH_INFO_SPLIT
    var next = this.routes
    var rightMost = next
    var leftMost = array[]
    // th1ish bug: cloning of arrays returns a Function:
    // var remainingPath = ps ? clone ps : undefined
    var remainingPath = ps ? ps.slice()/*clone*/ : undefined
    //print(typename ps, ps)
    ps && for{var x, i = 0, key, psLen = [ps.length]}
        {next && (i<psLen)}{i+=1}{
        key = ps.(i)
        next.hasOwnProperty(key) || continue
        x = next.(key)
        next = x || undefined
        next && (rightMost = next)
        leftMost.push(remainingPath.shift())
    }
    var cb
    if( rightMost && ('function' === typename rightMost.render) ){
        cb = rightMost
    }else if ('function' === typename rightMost){
        cb = rightMost
    }else{
        if(ps && ps.length()){
            this.httpStatus(404)
            throw api.Exception(Fossil.rc.NOT_FOUND, "Unknown page: "+R.ENV.PATH_INFO)
            //throw "Unknown page: "+R.ENV.PATH_INFO
        }else{
            cb = this.routes._default
        }
    }
    //print('cb',cb)
    //R.ENV.remainingPath = remainingPath.join('/')
    //R.ENV.leftPath = leftMost.join('/')
    //R.remainingPath = remainingPath
    R.routeAfterApp = remainingPath
    
    this.runFuncAsPage(cb)
    throw __SRC+": cannot be reached"
}

api.cgi.createPageProxyForFile = proc(pageBaseName){
    const pf = api.import.path
    const pageName = {pages/}+pageBaseName
    const fn = pf.search(pageName)
    fn || throw api.Exception(Fossil.rc.NOT_FOUND, "Could not find page "+pageName)
    return proc(cgi){ import(fn) }.importSymbols(nameof fn)
}

/**
    A helper class for setting up routes to render
    via the 'skins' mechanism.

    Calling this function returns an Object which inherits
    from this function. Its (inherited) render() method
    it used by the framework to render a skinable page.
*/
api.cgi.SkinnedPageRoute = proc(pageFile,title){
    assert 'string' === typename pageFile
    const rc = object{
        pageFile: pageFile,
        pageTitle: (title || this.request.ENV.PATH_INFO)
    }
    rc.prototype = argv.callee
    return rc
}
api.cgi.SkinnedPageRoute.__typename = 'SkinnedPageRoute'

/**
  If a SkinnedPageRoute instance matches during dispatchRoute(),
  this function will be called to render the content using
  the configured skin (api.cgi.config.defaultSkinName).
*/
api.cgi.SkinnedPageRoute.render = proc(cgi){
    const pageBaseName = this.pageFile
    const pageTitle = this.pageTitle
    assert 'string' === typename pageBaseName
    assert 'string' === typename pageTitle
    const pageName = cgi.resolveResourceName({pages/}+pageBaseName)
    const out = api.io.output
    const ob = api.ob
    const skinName = (cgi.config.defaultSkinName || 'default');


    const importSkin = proc(name){
        const key = cgi.resolveResourcePath('skins/'+name);
        var skin = cgi.cache(key)
        if(!skin){
            skin = api.import(key)
            skin || throw api.Exception(Fossil.rc.NOT_FOUND, "Could not find skin "+skinName)
            cgi.cache(key, skin)
        }
        return skin
    }.importSymbols(nameof cgi)

    const skin = importSkin(skinName)
    //cgi.currentSkin(skin)
    skin.pageTitle = pageTitle
    //cgi.printJSON(skin)
    skin.outputOpener(cgi)
    ob.push()
    const obLevel = ob.level()
    out({<div class='page-content'>})
    const err = catch{
        const pf = api.import.path
        assert 'PathFinder' === typename pf
        const fn = pf.search(pageName)
        fn || throw api.Exception(Fossil.rc.NOT_FOUND, 'Could not find page ['+pageBaseName+']')
        skin.showingPageSource = cgi.handleShowSource(fn)
        if(!skin.showingPageSource){
            ob.push()
            import(fn)
            ob.flush()
            ob.pop()
        }
    }
    if(err){
        /* Discard output and generate exception message... */
        while(ob.level() > obLevel){
            ob.pop()
        }
        out({<span class='exception'>Exception while running page:</span>},
            {<div><textarea class='exception' rows='20' cols='100'>},
            cgi.toJSONString(cgi.scrubException(err)),
            {</textarea></div>});
    }
    out({</div><!-- .page-content -->})
    while(ob.level() >= obLevel){
        ob.flush()
        ob.pop()
    }
    skin.outputCloser(cgi)
}

api.cgi.DownloadRoute = proc(resourceName, contentType = undefined /*===guess!*/){
    assert 'string' === typename resourceName
    const rc = object{
        mimeType: (contentType || this.guessMimeType(resourceName)),
        resourceName: resourceName,
        maxAge: 3601
    }
    rc.prototype = argv.callee
    return rc
}

api.cgi.DownloadRoute.render = proc(cgi){
    assert 'string' === typename this.resourceName
    const fn = cgi.resolveResourcePath(this.resourceName)
    const mimeType = (this.mimeType || cgi.guessMimeType(fn))
    cgi.setContentType( mimeType )
    cgi.handleContentDisposition(fn, mimeType)
    const mtime = Fossil.file.mtime(fn)
    if(this.maxAge){
        cgi.setHeader('Cache-Control', 'max-age='+this.maxAge)
    }else{
        cgi.setHeader('Cache-Control', 'public' /* most content is immutable!*/)
    }
    const timeFmt = "%a %d %b %Y %H:%M:%S GMT"
    cgi.setHeader( 'Last-Modified', strftime(timeFmt, mtime) )
    Fossil.file.passthrough(fn)
    //this.resolvedName = fn
    //return this
}

var CGI = api.cgi
api.cgi.routes = object {
    skinned: CGI.SkinnedPageRoute('timeline', 'api.cgi.SkinnedPageRoute test'),
    foo: object{
        bar:proc(cgi){
            cgi.setContentType('text/plain')
            print("should not be emitted")
            api.ob.clear()
            print(__SRC,"hi from", cgi.request.ENV.PATH_INFO)
            print('.remainingPath ==> ', cgi.request.remainingPath)
        }
    },
    err: CGI.SkinnedPageRoute('no-such-file', 'File Listing'),
    dir: CGI.SkinnedPageRoute('dir', 'File Listing'),
    download: object{
        foo: CGI.DownloadRoute('skins/default.fossi1ish.css'),
        css: object{
            'dir.css': CGI.DownloadRoute('pages/dir.css'),
            'default.css': CGI.DownloadRoute('skins/default.fossi1ish.css')
            /* FIXME: ^^^^ plug that in to cgi.config.defaultSkinName */
        },
        uuid: proc(cgi){
            // incomplete/experimental...
            const R = cgi.request
            const F = cgi.getFossilInstance()
            //Fossil.sorter(cgi.request.ENV)
            //print(cgi.toJSONString(cgi.request));
            const sym = R.ENV.PATH_INFO_SPLIT.pop()
            const blob = F.loadBlob(sym)
            const name = cgi.getVar('name')
            var mimeType = cgi.getVar('mimeType')
            if(name){
                mimeType || (mimeType = cgi.guessMimeType(name))
                
                //cgi.setHeader("Content-Disposition", 'attachment; filename="'.concat(name, '"'))
            }else if(!mimeType){
                mimeType = 'text/plain'
            }
            cgi.setHeader('Cache-Control', 'max-age=290304000, public' /*content is immutable!*/)
            cgi.setContentType(mimeType)
            cgi.out(blob)
        }
    },
    env: CGI.SkinnedPageRoute('env','CGI Environment'),
    exception: CGI.SkinnedPageRoute('exception', 'Exception Example'),
    manifest: CGI.SkinnedPageRoute('manifest', 'Manifest Browser'),
    reports: scope{
        const pmain = CGI.SkinnedPageRoute('reports', 'Activity Reports')
        pmain.'by-user' = CGI.SkinnedPageRoute('reports-by-user', 'By-user Activity Reports')
        pmain.'by-month' = CGI.SkinnedPageRoute('reports-by-month', 'By-month Activity Reports')
        pmain.'by-year' = CGI.SkinnedPageRoute('reports-by-year', 'By-year Activity Reports')
        pmain.'by-week' = CGI.SkinnedPageRoute('reports-by-week', 'By-week Activity Reports')
        pmain
    },
    snap: proc(cgi){
        //const x = api.prototypes.Exception(3,"!!!");
        //throw x ; //"Aw, snap! Google changed its APIs again!";
        throw api.Exception(Fossil.rc.ERROR, "Aw, snap! Google v8 changed its APIs again!")
    },
    timeline: scope{
        //var pmain = CGI.createPageProxyForFile('timeline')
        //pmain.html = CGI.createPageProxyForFile('timeline-html')
        //pmain
        CGI.SkinnedPageRoute('timeline', 'Timeline Overview')
    }/*object{
        text: CGI.createPageProxyForFile('timeline'),
        html: CGI.createPageProxyForFile('timeline-html')
    }*/,
    throw: proc(cgi){
        throw __SRC+" A test exception."
    },
    test: proc(cgi){
        const out = api.io.output
        cgi.setContentType('text/plain')
        const skin = api.import('cgi/skins/default')
        assert 'object' === typename skin
        //cgi.printJSON(skin)
        skin.pageTitle = cgi.request.ENV.PATH_INFO
        skin.outputOpener(cgi)
        $out "Hi, world!"
        skin.outputCloser(cgi)
    },
    test2: CGI.SkinnedPageRoute('test','Test Page & < Page #2'),
    _default: CGI.SkinnedPageRoute('default', 'Home'),
        // proc(){return api.cgi.getFossilInstance().getProjectName()}),
    _defaultOld: proc(cgi){
        const R = cgi.request
        const now = time()
        //cgi.setCookie( 'default-page', object{value: now, path:'/foo/', httpOnly:true})
        cgi.setHeader('X-my-header', 1);
        var rc = object {
            timestamp: [time],
            //cookies: R.COOKIES,
            get: R.GET,
            //ARGV: ARGV
            cgiRoot: cgi.cgiRoot,
            //session: cgi.getSession(),
            {Fossil.rc}: Fossil.rc
        }
        if(cgi.isLocalServer){
            rc.privileged = object{
                env: R.ENV
            }
        }

        const fsl = cgi.getFossilInstance()
        if(fsl && fsl.db && fsl.db.repo){
            rc.context = "%1$p".applyFormat(fsl)
            rc.repo = object{
                trunkVersion: fsl.symToUuid("trunk"),
                projectName: fsl.getProjectName()
            }
        }
        return rc
    }
}

unset CGI.createPageProxyForFile, CGI
