/*
Copyright (c) 2025 WuJingrun(吴京润)

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package f_http

import std.collection.ArrayList
import std.io.{InputStream, OutputStream, ByteBuffer, copy}
import std.fs.File
import std.time.DateTime
import f_base.*
import f_random.RandomString

public class MultipartFormData {
    public let boundary = 'FountainBoundary${RandomString().randomLettersNumbers(32)}'
    let parts = ArrayList<MultipartFile>()
    public func newPart(): MultipartFileBuilder {
        MultipartFileBuilder(this)
    }
    public func encode(output: OutputStream){
        for(part in parts){
            output.write('--${boundary}\r\n'.unsafeBytes())
            let cd = part.contentDisposition.toString().unsafeBytes()
            let input = part.content
            output.write(cd)
            copy(input, to: output)
            output.write('\r\n'.unsafeBytes())
        }
        output.write('--${boundary}--\r\n'.unsafeBytes())
    }
    public func input(): InputStream {
        MultipartFileInputStream(this)
    }
}
public class MultipartFileBuilder {
    private let builder = ContentDispositionBuilder(ContentDisposition()).formData
    private var content = unsafe{zeroValue<InputStream>()}
    MultipartFileBuilder(private let data: MultipartFormData){}
    public func name(name: String): This {
        builder.name(name)
        this
    }
    public func value(content: Array<Byte>): This {
        let buf = ByteBuffer()
        this.content = buf
        buf.write(content)
        this
    }
    public func value<T>(content: T): This where T <: ToString {
        value(content.toString().unsafeBytes())
    }
    public func file(file: File): This {
        content = file
        builder.filename(file.info.name)
        builder.size(file.info.size)
        builder.creationDate(file.info.creationTime)
        builder.modificationDate(file.info.lastModificationTime)
        this
    }
    public func file(fileName: String, content: InputStream, size!: Int64 = -1, 
                     creationDate!: ?DateTime = None<DateTime>, modificationDate!: ?DateTime = None<DateTime>): This{
        if(size >= 0){
            builder.size(size)
        }
        if(let Some(x) <- creationDate){
            builder.creationDate(x)
        }
        if(let Some(x) <- modificationDate){
            builder.modificationDate(x)
        }
        builder.filename(fileName)
        this.content = content
        this
    }
    public func build(): MultipartFormData {
        data.parts.add(MultipartFile(builder.build(), content))
        data
    }
}
public class MultipartFileInputStream <: InputStream {
    private var cur = CurrentMultipartFile.Start
    MultipartFileInputStream(private let data: MultipartFormData){}
    public func read(buf: Array<Byte>): Int64 {
        match(cur){
            case Start => 
                cur = Current(0, data.parts[0], StartBoundary)
                read(buf)
            case Current(idx, tmp, offset, part) => 
                if(let s <- tmp.size - offset && s > 0 && s <= buf.size){
                    tmp.copyTo(buf, offset, 0, s)
                    cur = Current(idx, data.parts[idx], part.next)
                    s
                }else if(let s <- tmp.size - offset && s > 0 && s > buf.size){
                    tmp.copyTo(buf, offset, 0, buf.size)
                    cur = Current(idx, tmp, offset + buf.size, part)
                    buf.size
                }else {
                    cur = Current(idx, data.parts[idx], part.next)
                    read(buf)
                }
            case Current(idx, input) =>
                let l = input.read(buf)
                if(l == 0){
                    cur = Current(idx, data.parts[idx], CRLF_Boundary)
                }
                l
            case Current(idx, file, part) =>
                cur = match(part){
                    case StartBoundary => Current(idx, '--${data.boundary}\r\n'.unsafeBytes(), 0, StartBoundary)
                    case ContentDisposition =>
                        let tmp = file.contentDisposition.toString().unsafeBytes()
                        Current(idx, tmp, 0, MultipartPart.ContentDisposition)
                    case Content => Current(idx, file.content)
                    case CRLF_Boundary => Current(idx, '\r\n--${data.boundary}--\r\n'.unsafeBytes(), 0, CRLF_Boundary)
                    case Next => Current(idx + 1, data.parts[idx + 1], StartBoundary)
                }
                read(buf)
        }
    }
}
enum CurrentMultipartFile {
    | Start
    | Current(Int64, MultipartFile, MultipartPart)
    | Current(Int64, Array<Byte>, Int64, MultipartPart)
    | Current(Int64, InputStream)
}
enum MultipartPart{
    | StartBoundary
    | ContentDisposition
    | Content
    | CRLF_Boundary
    | Next
    prop next: MultipartPart {
        get(){
            match(this){
                case StartBoundary => ContentDisposition
                case ContentDisposition => Content
                case Content => CRLF_Boundary
                case CRLF_Boundary => Next
                case Next => StartBoundary
            }
        }
    }
}