package aceboot::bodyparser

import std.collection.*
import aceboot::web.*

let FORM_KEY = "__ace_form"

/**
 * 请求解析中间件:按 Content-Type 解析请求体。
 * 当前支持 application/x-www-form-urlencoded,解析结果存入 ctx.state,
 * 通过 formValue(ctx, name) 读取。读取的是 ctx.rawBody()(由适配层/测试填充)。
 *
 * JSON / multipart 待后续补充(JSON 可在 M3/上层用 stdx.encoding.json 或独立解析器)。
 */
public func bodyParser(): Middleware {
    return {
        ctx: Context, next: Next =>
        let ct = ctx.header("content-type") ?? ""
        if (ctx.hasRequestBodyStream() && (ct.contains("application/x-www-form-urlencoded") || ct.contains("multipart/form-data"))) {
            throw Exception("bodyParser cannot be used with streaming request bodies")
        }
        if (ct.contains("application/x-www-form-urlencoded")) {
            match (ctx.rawBody()) {
                case Some(body) => parseForm(ctx, body)
                case None => ()
            }
        } else if (ct.contains("multipart/form-data")) {
            let boundary = extractBoundary(ct)
            if (boundary != "") {
                match (ctx.rawBodyBytes()) {
                    case Some(body) => parseMultipartBytes(ctx, body, boundary)
                    case None => ()
                }
            }
        }
        next()
    }
}

// 从 Content-Type 提取 boundary=... 的值(可能后接 ;)。
func extractBoundary(ct: String): String {
    let marker = "boundary="
    match (ct.indexOf(marker)) {
        case Some(i) =>
            let b = ct[i + marker.size..ct.size]
            match (b.indexOf(";")) {
                case Some(j) => b[0..j]
                case None => b
            }
        case None => ""
    }
}

// UploadedFile / FILE_KEY / fileValue / fileValues 在 aceboot::web(facade 可见,供 @File/@Files 宏)。

// 在 hay 中从 from 起找子序列 needle 的首位置。
func indexOfBytes(hay: Array<UInt8>, needle: Array<UInt8>, from: Int64): ?Int64 {
    if (needle.size == 0 || hay.size < needle.size) {
        return None
    }
    var i = from
    while (i <= hay.size - needle.size) {
        var matched = true
        for (j in 0..needle.size) {
            if (hay[i + j] != needle[j]) {
                matched = false
                break
            }
        }
        if (matched) {
            return i
        }
        i++
    }
    return None
}

/**
 * 字节级解析 multipart/form-data(二进制安全)。按 --boundary 字节序列切分,
 * 每段头部(ASCII)解析 name/filename/Content-Type,正文保留原始字节:
 * 带 filename → 文件(存 FILE_KEY,经 fileValue 读);无 filename → 文本字段(存 FORM_KEY)。
 */
func parseMultipartBytes(ctx: Context, body: Array<UInt8>, boundary: String): Unit {
    let form = HashMap<String, String>()
    let files = HashMap<String, ArrayList<UploadedFile>>()
    let delim = ("--" + boundary).toArray()
    let sep = [13u8, 10u8, 13u8, 10u8] // \r\n\r\n

    // 收集所有 delim 位置。
    let positions = ArrayList<Int64>()
    var idx = indexOfBytes(body, delim, 0)
    while (true) {
        match (idx) {
            case Some(pos) =>
                positions.add(pos)
                idx = indexOfBytes(body, delim, pos + delim.size)
            case None => break
        }
    }

    // 相邻 delim 之间为一段。
    var k = 0
    while (k + 1 < positions.size) {
        let segStart = positions[k] + delim.size
        let segEnd = positions[k + 1]
        k++
        if (segEnd <= segStart) {
            continue
        }
        var seg = body[segStart..segEnd]
        // 去前导 \r\n
        if (seg.size >= 2 && seg[0] == 13u8 && seg[1] == 10u8) {
            seg = seg[2..seg.size]
        }
        // 头/正文分隔
        match (indexOfBytes(seg, sep, 0)) {
            case Some(hsep) =>
                let head = String.fromUtf8(seg[0..hsep])
                var content = seg[hsep + 4..seg.size]
                // 去正文尾部 \r\n
                if (content.size >= 2 && content[content.size - 2] == 13u8 && content[content.size - 1] == 10u8) {
                    content = content[0..content.size - 2]
                }
                let name = extractDispositionName(head)
                if (name != "") {
                    let filename = extractAttr(head, "filename=\"")
                    if (filename != "") {
                        let uf = UploadedFile(name, filename, extractContentType(head), content)
                        match (files.get(name)) {
                            case Some(list) => list.add(uf) // 同名多文件
                            case None =>
                                let l = ArrayList<UploadedFile>()
                                l.add(uf)
                                files.add(name, l)
                        }
                    } else {
                        form.add(name, String.fromUtf8(content))
                    }
                }
            case None => ()
        }
    }
    ctx.state.add(FORM_KEY, form)
    ctx.state.add(FILE_KEY, files)
}

// 从头部取 marker(如 `filename="`)后到下一个引号的值;无则空串。
func extractAttr(head: String, marker: String): String {
    match (head.indexOf(marker)) {
        case Some(i) =>
            let rest = head[i + marker.size..head.size]
            match (rest.indexOf("\"")) {
                case Some(j) => rest[0..j]
                case None => ""
            }
        case None => ""
    }
}

// 取 part 头里的 Content-Type 值(到行尾)。
func extractContentType(head: String): String {
    let marker = "Content-Type:"
    match (head.indexOf(marker)) {
        case Some(i) =>
            var v = head[i + marker.size..head.size]
            match (v.indexOf("\r")) {
                case Some(j) => v = v[0..j]
                case None => ()
            }
            trimLead(v)
        case None => "application/octet-stream"
    }
}

func trimLead(s: String): String {
    let bs = s.toArray()
    var i = 0
    while (i < bs.size && bs[i] == 32u8) {
        i++
    }
    String.fromUtf8(bs[i..bs.size])
}

// 取 part 头里 Content-Disposition 的 name="..." 值。
func extractDispositionName(part: String): String {
    let marker = "name=\""
    match (part.indexOf(marker)) {
        case Some(i) =>
            let rest = part[i + marker.size..part.size]
            match (rest.indexOf("\"")) {
                case Some(j) => rest[0..j]
                case None => ""
            }
        case None => ""
    }
}

func parseForm(ctx: Context, body: String): Unit {
    let form = HashMap<String, String>()
    for (pair in body.split("&")) {
        if (pair == "") {
            continue
        }
        let kv = pair.split("=")
        if (kv.size >= 2) {
            form.add(urlDecode(kv[0]), urlDecode(kv[1]))
        } else {
            form.add(urlDecode(kv[0]), "")
        }
    }
    ctx.state.add(FORM_KEY, form)
}

/** 读取已解析的表单字段。 */
public func formValue(ctx: Context, name: String): ?String {
    match (ctx.state.get(FORM_KEY)) {
        case Some(v) => match (v as HashMap<String, String>) {
            case Some(m) => m.get(name)
            case None => None
        }
        case None => None
    }
}

func hexVal(b: UInt8): Int64 {
    if (b >= 48u8 && b <= 57u8) {
        return Int64(b) - 48
    }
    if (b >= 65u8 && b <= 70u8) {
        return Int64(b) - 65 + 10
    }
    if (b >= 97u8 && b <= 102u8) {
        return Int64(b) - 97 + 10
    }
    return -1
}

/** application/x-www-form-urlencoded 解码:'+' 视作空格,'%XX' 解码为字节。 */
func urlDecode(s: String): String {
    let bytes = s.toArray()
    let out = ArrayList<UInt8>()
    var i = 0
    while (i < bytes.size) {
        let b = bytes[i]
        if (b == 43u8) {
            out.add(32u8)
            i += 1
        } else if (b == 37u8 && i + 2 < bytes.size) {
            let h = hexVal(bytes[i + 1])
            let l = hexVal(bytes[i + 2])
            if (h >= 0 && l >= 0) {
                out.add(UInt8(h * 16 + l))
                i += 3
            } else {
                out.add(b)
                i += 1
            }
        } else {
            out.add(b)
            i += 1
        }
    }
    return String.fromUtf8(out.toArray())
}