sourceafReflux::RefluxBuilder.fan

using afIoc
using fwt
using afPlastic

** Use to build and launch a Reflux application. Example:
**
**   syntax: fantom
** 
**   RefluxBuilder(AppModule#).start() |Reflux reflux, Window window| {
**     reflux.showPanel(MyPanel#)
**     ...
**   }
@Js
class RefluxBuilder {
    private const static Log log := RefluxBuilder#.pod.log

    ** The underlying IoC 'RegistryBuilder'. 
    RegistryBuilder registryBuilder := RegistryBuilder() { private set }
    
    ** The application name. Taken from the app pod's 'proj.name' meta, or the pod name if the meta doesn't exist.
    ** Read only.
    Str appName {
        get { options[RefluxConstants.meta_appName] }
        private set { throw Err("Read only") }
    }

    ** Returns options from the IoC 'RegistryBuilder'.
    Str:Obj? options {
        get { registryBuilder.options }
        private set { throw Err("Read only") }
    }
    
    ** Creates a 'RefluxBuilder'. 
    ** 'modOrPodName' may be a pod name or a qualified 'AppModule' type name. 
    ** 'addPodDependencies' is only used if a pod name is passed in.
    new makeFromName(Str modOrPodName, Bool addPodDependencies := true) {
        _initModules(registryBuilder, modOrPodName, addPodDependencies)
        _initBanner()
    }
    
    ** Creates a 'RefluxBuilder' from the given 'AppModule'.
    new makeFromModule(Type appModule) {
        _initModules(registryBuilder, appModule.qname, true)
        _initBanner()
    }
    
    ** Creates a 'RefluxBuilder' from the given 'AppModule'.
    new makeFromModules(Type[] modules) {
        _initModules(registryBuilder, modules[0].qname, true)
        _initBanner()
        
        if (modules.size > 1)
            modules.eachRange(1..-1) {
                addModule(it)
            }
    }
    
    ** Adds an IoC module to the registry. 
    This addModule(Obj module) {
        registryBuilder.addModule(module)
        return this
    }
    
    ** Adds many IoC modules to the registry. 
    This addModules(Obj[] modules) {
        registryBuilder.addModules(modules)
        return this
    }
    
    ** Inspects the [pod's meta-data]`docLang::Pods#meta` for the key 'afIoc.module'. This is then 
    ** treated as a CSV list of (qualified) module type names to load.
    ** 
    ** If 'addDependencies' is 'true' then the pod's dependencies are also inspected for IoC 
    ** modules.
    **  
    ** Convenience for 'registryBuilder.addModulesFromPod()'
    This addModulesFromPod(Str podName, Bool addDependencies := true) {
        registryBuilder.addModulesFromPod(podName, addDependencies)
        return this     
    }
    
    Void start(|Reflux, Window|? onOpen := null) {
        registry := registryBuilder.build
        
        uiScope := (Scope?) null
        registry.rootScope.createChild("uiThread") {
            uiScope = it.jailBreak
        }
        
        reflux   := (Reflux) uiScope.serviceById(Reflux#.qname)
        frame    := (Frame)  reflux.window

        // onActive -> onFocus -> onOpen
        frame.onOpen.add {
            // Give the widgets a chance to display themselves and set defaults
            Desktop.callLater(50ms) |->| {
                // load the session before we start loading URIs and opening tabs
                session := (Session) uiScope.serviceById(Session#.qname)
                session.load

                // once we've all started up and settled down, load URIs from the command line
                onOpen?.call(reflux, frame)
                
                // a crappy hack for Chrome - all buttons appear squished until we resize / relayout
                if (Env.cur.runtime == "js")
                    Desktop.callLater(50ms) |->| { frame.relayout }
            }
        }
        frame.open
        
        // JS is non-blocking - so don't shutdown the registry!
        if (Env.cur.runtime != "js")
            registry.shutdown
    }
    
    private Void _initBanner() {
        pod := (Pod?) registryBuilder.options[RefluxConstants.meta_appPod]
        ver  := pod?.version ?: "???"
        registryBuilder.options["afIoc.bannerText"] = "$appName v$ver"
    }

    private static Void _initModules(RegistryBuilder bob, Str moduleName, Bool transDeps) {
        Pod?  pod
        Type? mod
        
        // Pod name given...
        // lots of start up checks looking for pods and modules... 
        // see https://bitbucket.org/SlimerDude/afbedsheet/issue/1/add-a-warning-when-no-appmodule-is-passed
        if (!moduleName.contains("::")) {
            pod = Pod.find(moduleName, true)
            log.info(LogMsgs.refluxBuilder_foundPod(pod))
            mods := _findModFromPod(pod)
            mod = mods.first
            
            if (!transDeps)
                log.info("Suppressing transitive dependencies...")
            bob.addModulesFromPod(pod.name, transDeps)
            mods.each { bob.addModule(it) }
        }

        // AppModule name given...
        if (moduleName.contains("::")) {
            mod = Type.find(moduleName, true)
            log.info(LogMsgs.refluxBuilder_foundType(mod))
            pod = mod.pod
            
            bob.addModule(mod)
        }

        // we're screwed! No module = no web app!
        if (mod == null)
            log.warn(LogMsgs.refluxBuilder_noModuleFound)
        
        // A simple thing - ensure the Reflux module is added! 
        // (transitive dependencies are added explicitly via @SubModule)
        bob.addModule(RefluxModule#)
        if (Env.cur.runtime != "js")
            bob.addModule(PlasticModule#)

        projName := (Str?) null
        try pod?.meta?.get("proj.name")
        catch { /* JS F4 Errs */ }

        regOpts  := bob.options
        regOpts[RefluxConstants.meta_appName]   = (projName ?: pod?.name) ?: "Unknown"
        regOpts[RefluxConstants.meta_appPod]    = pod
        regOpts[RefluxConstants.meta_appModule] = mod
    }

    ** Looks for an 'AppModule' in the given pod. 
    private static Type[] _findModFromPod(Pod pod) {
        mods := Type#.emptyList
        modNames := pod.meta["afIoc.module"]
        if (modNames != null) {
            mods = modNames.split.map { Type.find(it, true) }
            log.info(LogMsgs.refluxBuilder_foundType(mods.first))
        } else {
            // we have a pod with no module meta... so lets guess the name 'AppModule'
            mod := pod.type("AppModule", false)
            if (mod != null) {
                mods = [mod]
                log.info(LogMsgs.refluxBuilder_foundType(mod))
                log.warn(LogMsgs.refluxBuilder_addModuleToPodMeta(pod, mod))
            }
        }
        return mods
    }
}