package io.github.daviddenton.k2.contract

import io.github.daviddenton.k2.http.Method
import io.github.daviddenton.k2.http.Request
import io.github.daviddenton.k2.util.then
import io.netty.handler.codec.http.QueryStringEncoder

sealed class Binding(val parameter: NamedParameter<*, *>) : (RequestBuilder) -> RequestBuilder

class QueryBinding(parameter: NamedParameter<*, *>, private val value: String) : Binding(parameter) {
    override fun invoke(requestBuild: RequestBuilder): RequestBuilder =
        requestBuild.copy(queries = requestBuild.queries.plus(
            parameter.name to listOf(value).plus(requestBuild.queries[parameter.name] ?: emptyList())))
}

class PathBinding(parameter: NamedParameter<*, *>, private val value: String) : Binding(parameter) {
    override fun invoke(requestBuild: RequestBuilder) = requestBuild.copy(uriParts = requestBuild.uriParts.plus(value))
}

open class RequestBinding(parameter: NamedParameter<*, *>, private val into: (Request) -> Request) : Binding(parameter) {
    override fun invoke(requestBuild: RequestBuilder) = requestBuild.copy(fn = requestBuild.fn.then(into))
}

class FormFieldBinding(parameter: NamedParameter<*, *>, private val value: String) : RequestBinding(parameter, { it }) {
    operator fun invoke(form: Form): Form = form + (parameter.name to value)
}

class HeaderBinding(parameter: NamedParameter<*, *>, private val value: String) : RequestBinding(parameter,
    { it.header(parameter.name to value) })

data class RequestBuilder(val method: Method,
                          val uriParts: Iterable<String> = listOf(),
                          val queries: Map<String, List<String>> = emptyMap(),
                          val headers: Map<String, Set<String>> = emptyMap(),
                          val fn: (Request) -> Request) {
    fun build(): Request {
        val baseUri = uriParts.joinToString { "/" }

        val uri = queries.toList().fold(QueryStringEncoder(if (baseUri.isEmpty()) "/" else baseUri), {
            memo, q ->
            q.second.forEach { v -> memo.addParam(q.first, v) }
            memo
        }).toString()

        return fn(Request(method, uri, "", headers))
    }
}
