package com.ustadmobile.libcache.partial

/**
 * Data required to process a content range response. Supports only one range.
 *
 * @param fromByte the from byte (inclusive)
 * @param toByte the to byte (inclusive)
 * @param totalBytes the total size of the document
 * @param contentLength the actual size of this response.
 */
class ContentRange(
    val fromByte: Long,
    val toByte: Long,
    val totalBytes: Long,
    val contentLength: Long,
) {

    val contentRangeResponseHeader: String
        get() = "bytes $fromByte-$toByte/$totalBytes"

    companion object {

        /**
         *  As per https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Range
         *
         *  @param header the Content-Range header from the http request
         *  @param totalContentLength the total length of the content
         */
        fun parseRangeHeader(
            header: String,
            totalContentLength: Long,
        ) : ContentRange {
            val headerTrimmed = header.trim()

            if(headerTrimmed.indexOf(",") != -1) {
                throw RangeRequestNotSatisfiableException("Multiple content-ranges are not supported")
            }

            val equalsPos = headerTrimmed.indexOf("=")
            if(equalsPos < 0)
                throw IllegalArgumentException("Malformed Content-Range: must have unit followed = eg. bytes=")

            val unit = headerTrimmed.substring(0, equalsPos)
            if(unit != "bytes")
                throw RangeRequestNotSatisfiableException("Content-Range: only supported unit is: bytes, $unit is not supported")

            //Full spec
            //See https://httpwg.org/specs/rfc7233.html#rule.ranges-specifier

            val dashIndex = headerTrimmed.indexOf("-")

            val firstBytePos = headerTrimmed.substring(equalsPos + 1, dashIndex).let {
                if(it.isNotBlank()) it.toLong() else -1
            }

            val lastBytePos = headerTrimmed.substring(dashIndex + 1).let {
                if(it.isNotBlank()) it.toLong() else -1
            }

            /* If only the lastBytes are specified, then this means return the last n bytes as per
             * https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Range
             * "Alternatively, if it's unknown how large a resource is, the last n bytes can be requested using a suffix range of -n:
             * Range: bytes=-100"
             */
            return if(lastBytePos >= 0 && firstBytePos == -1L) {
                ContentRange(
                    fromByte = (totalContentLength - lastBytePos) + 1,
                    toByte = totalContentLength,
                    totalBytes = totalContentLength,
                    contentLength = lastBytePos,
                )
            }else {
                val lastByte = if(lastBytePos == -1L) totalContentLength - 1 else lastBytePos
                if(lastByte < firstBytePos) {
                    throw RangeRequestNotSatisfiableException("Cannot satisfy range: last byte " +
                            "$lastByte is before first byte $firstBytePos")
                }

                //Range is inclusive, starting from zero, so the last byte can be up to the total
                // content length minus 1
                if(lastBytePos >= totalContentLength) {
                    throw RangeRequestNotSatisfiableException("Cannot satisfy range: last byte is " +
                            "$lastByte, but totalSize is $totalContentLength")
                }

                ContentRange(
                    fromByte = firstBytePos,
                    toByte = lastByte,
                    totalBytes = totalContentLength,
                    contentLength = (lastByte - firstBytePos) + 1
                )
            }
        }
    }

}