package org.codetome.snap.service.parser

import org.codetome.snap.model.ConfigSegmentType.REST_ENDPOINT
import org.codetome.snap.model.ConfigSegmentType.REST_METHOD
import org.codetome.snap.model.ParseContext
import org.codetome.snap.model.rest.MethodType
import org.codetome.snap.model.rest.Path
import org.codetome.snap.model.rest.RestEndpoint
import org.codetome.snap.model.rest.RestMethod
import org.codetome.snap.util.SubstringRanges

abstract class EndpointSegmentParser<N>(
        private val segmentTypeAnalyzer: SegmentTypeAnalyzer<N>,
        private val headerSegmentParser: HeaderSegmentParser<N>,
        private val descriptionSegmentParser: DescriptionSegmentParser<N>,
        private val schemaSegmentParser: SchemaSegmentParser<N>,
        private val restMethodSegmentParser: RestMethodSegmentParser<N>)
    : SegmentParser<N, RestEndpoint> {

    override fun parseSegment(nodes: List<N>, context: ParseContext): Pair<RestEndpoint, List<N>> {
        if (nodes.size < 3) {
            throw IllegalArgumentException("An Endpoint declaration must contain at least a description, a schema declaration and a method!")
        }
        if (REST_ENDPOINT != segmentTypeAnalyzer.determineSegmentType(nodes.first())) {
            throw IllegalArgumentException("An Endpoint declaration must start with an Endpoint header!")
        }
        val (header, rest0) = headerSegmentParser.parseSegment(nodes, context)
        val (description, rest1) = descriptionSegmentParser.parseSegment(rest0, context)
        val (schema, rest2) = schemaSegmentParser.parseSegment(rest1, context)
        context.declaredSchemas.put(schema.name, schema)

        if (rest2.isEmpty()) {
            throw IllegalArgumentException("An Endpoint declaration must contain at least a single Rest Method!")
        }
        var remaining = rest2
        val restMethods = mutableMapOf<MethodType, RestMethod>()
        while (hasRemainingRestMethods(remaining)) {
            val (restMethod, tempRemaining) = restMethodSegmentParser.parseSegment(remaining, context)
            remaining = tempRemaining
            restMethods.put(restMethod.method, restMethod)
        }
        return Pair(RestEndpoint(
                name = header.name,
                description = description,
                path = Path(
                        pathStr = header.linkContent,
                        paramToSchemaPropertyMapping = parsePathParams(header.linkContent) // TODO: fill this properly in next version!
                ),
                schema = schema,
                restMethods = restMethods
        ), remaining)
    }

    private fun parsePathParams(pathStr: String): Map<String, String> {
        return SubstringRanges.rangesDelimitedByPairInString(Pair('{', '}'), pathStr)
                .subStrings
                .map { Pair(it, it) }
                .toMap()
    }

    private fun hasRemainingRestMethods(remaining: List<N>) = remaining.isNotEmpty()
            && REST_METHOD == segmentTypeAnalyzer.determineSegmentType(remaining.first())
}