sourceafPillow::PillowModule.fan

using afIoc
using afIocConfig
using afBedSheet
using afEfanXtra
using afPlastic

** The [afIoc]`http://repo.status302.com/doc/afIoc/#overview` module class.
** 
** This class is public so it may be referenced explicitly in tests.
class PillowModule {

    internal static Void bind(ServiceBinder binder) {
        binder.bind(Pages#)
        binder.bind(PillowPrinter#)
        binder.bind(ContentTypeResolver#)
        binder.bind(ClientUriResolver#)
        binder.bind(PageMeta#, PageMetaProxy#).withoutProxy // we supply our own proxy!

//      binder.bindImpl(Routes#).withId("PillowRoutes")
    }
    
    // TODO: afIoc-1.5
//  @Contribute { serviceType=HttpPipeline# }
//  internal static Void contributeHttpPipeline(OrderedConfig config, Registry reg) {
//      pillowRoutes := reg.serviceById("PillowRoutes")
//      config.addOrdered("PillowRoutes", pillowRoutes, ["after: Routes"])
//  }

    @Contribute { serviceType=EfanLibraries# }
    internal static Void contributeEfanLibraries(MappedConfig config, BedSheetMetaData meta) {
        if (meta.appPod != null)
            config["app"] = meta.appPod
    }

    @Contribute { serviceType=ComponentCompiler# }
    internal static Void contributeComponentCompilerCallbacks(OrderedConfig config) {
        pageCompiler := (PageCompiler) config.autobuild(PageCompiler#)
        config.add(pageCompiler.callback)
    }

//  @Contribute { serviceId="PillowRoutes" }
    @Contribute { serviceId="Routes" }
    internal static Void contributeRoutes(OrderedConfig config, Pages pages, Registry registry) {

        config.addPlaceholder("PillowStart", ["after: FileHandlerEnd"])
        config.addPlaceholder("PillowEnd",   ["after: PillowStart"])
        
        pages.pageTypes.each |pageType| {
            pageMeta    := pages.pageMeta(pageType, null)
            serverUri   := pageMeta.serverGlob
            initTypes   := pageMeta.contextTypes
            
            // allow the file system to trump pillow pages
            config.addOrdered(pageType.qname, Route(serverUri, PageRenderFactory(pageType, initTypes), pageMeta.httpMethod), ["after: PillowStart", "before: PillowEnd"])
            
            pageType.methods.findAll { it.hasFacet(PageEvent#) }.each |eventMethod| {
                pageEvent   := (PageEvent) Method#.method("facet").callOn(eventMethod, [PageEvent#])    // Stoopid F4   
                eventUri    := serverUri.plusSlash + pageMeta.eventGlob(eventMethod)
                qname       := "${pageType.qname}/${eventMethod.name}"
                config.addOrdered(qname, Route(eventUri, EventCallerFactory(pageType, initTypes, eventMethod), pageEvent.httpMethod), ["after: PillowStart", "before: PillowEnd"])
            }
        }

        // TODO: should we? redirect welcome pages to directory
//      if (!url.isDir && pages.isWelcomePage(pageType)) {
//          redirect := Redirect.movedTemporarily(url.parent)
//          responseProcessors.processResponse(redirect)
//          return true
//      }
    }

    @Contribute { serviceType=ResponseProcessors# }
    static Void contributeResponseProcessors(MappedConfig config) {
        config[PageMeta#] = config.autobuild(PageMetaResponseProcessor#)
    }
    
    @Contribute { serviceType=EfanTemplateFinders# }
    internal static Void contributeEfanTemplateFinders(OrderedConfig config) {
        config.addOrdered("FindByPageFacetValue", FindEfanByPageFacetValue())
    }

    @Contribute { serviceType=ApplicationDefaults# }
    internal static Void contributeApplicationDefaults(MappedConfig config) {
        // we'll do our own logging thanks!
        config[EfanXtraConfigIds.supressStartupLogging] = true
    }

    @Contribute { serviceType=FactoryDefaults# }
    internal static Void contributeFactoryDefaults(MappedConfig config) {
        config[PillowConfigIds.welcomePage]         = "index"
        config[PillowConfigIds.defaultContentType]  = MimeType("text/plain")
    }

    @Contribute { serviceType=RegistryStartup# }
    internal static Void contributeRegistryStartup(OrderedConfig conf, PillowPrinter efanPrinter) {
        conf.add |->| {
            efanPrinter.logLibraries
        }
    }   
}

internal const class PageRenderFactory : RouteResponseFactory {
    const Type      pageType
    const Type[]    initParams
    
    new make(Type pageType, Type[] initParams) {
        this.pageType   = pageType
        this.initParams = initParams
    }
    
    override Bool matchSegments(Str?[] segments) {
        matchesParams(initParams, segments)
    }
    
    override Obj? createResponse(Str?[] segments) {
        // segments is RO and (internally) needs to be a Str, so we can't just append pageType to the start of segments.
        MethodCall(Pages#renderPage, [pageType, segments])
    }
}

internal const class EventCallerFactory : RouteResponseFactory {
    const Type      pageType
    const Type[]    initParams
    const Method    eventMethod
    
    new make(Type pageType, Type[] initParams, Method eventMethod) {
        this.pageType       = pageType
        this.initParams     = initParams
        this.eventMethod    = eventMethod
    }
    
    override Bool matchSegments(Str?[] segments) {
        if (segments.size < initParams.size)
            return false
        initSegs := segments[0..<initParams.size]
        if (!matchesParams(initParams, initSegs))
            return false
        eventSegs := segments[initParams.size..-1]
        return matchesMethod(eventMethod, eventSegs)
    }

    override Obj? createResponse(Str?[] segments) {
        initSegs  := segments[0..<initParams.size]
        eventSegs := segments[initParams.size..-1]
        return MethodCall(Pages#callPageEvent, [pageType, initSegs, eventMethod, eventSegs])
    }
}