/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2022-2024. All rights resvered.
 */

/**
 * @file
 * The file declars the Okio class.
 */
package io4cj
internal import std.fs.*
internal import stdx.compress.zlib.*
internal import std.io.*
internal import std.sync.*
internal import std.math.*
internal import std.collection.*
internal import stdx.encoding.hex.*
internal import stdx.encoding.base64.*
internal import crypto_ffi.md5cj.*
internal import crypto_ffi.sha1cj.*
internal import crypto_ffi.sha256cj.*
internal import crypto_ffi.sha512cj.*
internal import crypto_ffi.hmaccj.*
internal import std.time.*
internal import std.net.*
internal import stdx.net.tls.*

/**
 * The class is Okio
 * @author liyanqing14
 * @since 0.32.5
 */
public class Okio {

    // Do not expose the constructor to call static functions.
    init (){}

    /**
     * The Function is buffer
     *
     * @param source of Source
     *
     * @return Type of BufferedSource
     * @since 0.32.5
     */
    @Frozen
    public static func buffer(source: Source): BufferedSource {
        return RealBufferedSource(source)
    }

    /**
     * The Function is buffer
     *
     * @param sink of Sink
     *
     * @return Type of BufferedSink
     * @since 0.32.5
     */
    @Frozen
    public static func buffer(sink: Sink): BufferedSink {
        return RealBufferedSink(sink)
    }

    /**
     * The Function is sink
     *
     * @param socket of Socket
     *
     * @return Type of Sink
     * @since 0.32.5
     */
    @Frozen
    public static func sink(socket: TcpSocket): Sink {
        let timeout = timeout(socket)
        let sink = sink(SocketBuffer(socket), timeout)
        return timeout.sink(sink)
    }

    /**
     * The Function is sink
     *
     * @param socket of TlsSocket
     *
     * @return Type of Sink
     * @since 0.32.5
     */
    @Frozen
    public static func sink(socket: TlsSocket): Sink {
        let timeout = timeout(socket)
        let sink = sink(SocketBuffer(socket), timeout)
        return timeout.sink(sink)
    }

    // TODO: Later, I need to customize a file object to call parameters. There are many file object parameters in cangjie, which is very troublesome
    /**
     * The Function is sink
     *
     * @param file of File
     *
     * @return Type of Sink
     * @since 0.32.5
     */
    @Frozen
    public static func sink(file: File): Sink {
        return sink(file, Timeout())
    }

    /**
     * The Function is source
     *
     * @param socket of Socket
     *
     * @return Type of Source
     * @since 0.32.5
     */
    @Frozen
    public static func source(socket: TcpSocket): Source {
        let timeout = timeout(socket)
        let source = source(SocketBuffer(socket), timeout)
        return timeout.source(source)
    }

    /**
     * The Function is source
     *
     * @param socket of TlsSocket
     *
     * @return Type of Source
     * @since 0.32.5
     */
    @Frozen
    public static func source(socket: TlsSocket): Source {
        let timeout = timeout(socket)
        let source = source(SocketBuffer(socket), timeout)
        return timeout.source(source)
    }

    /**
     * The Function is source
     *
     * @param file of File
     *
     * @return Type of Source
     * @since 0.32.5
     */
    @Frozen
    public static func source(file: File): Source {
        return source(file, Timeout())
    }

    private static func source (input: InputStream, timeout: Timeout) : Source { 
        func subRead (sink: Buffer, byteCount: Int64): Int64 {
            if (byteCount < 0) {
                throw IllegalArgumentException("byteCount < 0")
            }
            if (byteCount == 0) {
                return 0
            }
            try {
                timeout.throwIfReached()
                let tail = sink.writableSegment(1)
                let maxToCopy = min(byteCount, Segment.SIZE - tail.limit)
                let bytesRead = input.read(tail.data[tail.limit.. tail.limit + maxToCopy])
                if (bytesRead == 0) {
                    if (tail.pos == tail.limit) {
                        sink.head = tail.pop()
                        SegmentPool.recycle(tail)
                    }
                    return -1
                }
                tail.limit += bytesRead
                sink.mysize += bytesRead
                return bytesRead
            } catch (e: Exception) {
                throw e
            }
        }
        func subTimeout(): Timeout {
            return timeout
        }
        let source = AnonymousSource()
        source.funRead = subRead
        source.funTimeout = subTimeout
        return source
    }

    private static func sink (out: OutputStream, timeout: Timeout) : Sink {
        func subWrite(source: Buffer, bytecount: Int64): Unit {
            Util.checkOffsetAndCount(source.size, 0, bytecount)
            var byteCount = bytecount
            while (byteCount > 0) {
                timeout.throwIfReached()
                var head = source.head.getOrThrow()
                var toCopy = min(byteCount, head.limit - head.pos)
                out.write(head.data[head.pos..head.pos+toCopy])
                head.pos += toCopy
                byteCount -= toCopy
                source.mysize -= toCopy
                if (head.pos == head.limit) {
                    source.head = head.pop()
                    SegmentPool.recycle(head)
                }
            }
        }
        func subFlush (): Unit {
            out.flush()
        }
        func subTimeout (): Timeout {
            return timeout
        }
        let sink = AnonymousSink()
        sink.funWrite = subWrite
        sink.funFlush = subFlush
        sink.funTimeout = subTimeout
        return sink
    }

    private static func timeout(socket: TcpSocket): AsyncTimeout {
        func subtimeout (): Unit {
            try {
                socket.close()
            } catch (e: Exception) {
                e.printStackTrace()
            } 
        }
        let ast = AsyncTimeout()
        ast.fun = subtimeout
        return ast
    }

    private static func timeout(socket: TlsSocket): AsyncTimeout {
        func subtimeout (): Unit {
            try {
                socket.close()
            } catch (e: Exception) {
                e.printStackTrace()
            } 
        }
        let ast = AsyncTimeout()
        ast.fun = subtimeout
        return ast
    }


}

class AnonymousSource <: Source {
    var funRead: ?(Buffer, Int64)-> Int64 = None
    var funTimeout: ?()-> Timeout = None

    /**
     * The Function is close
     *
     * @return Type of Unit
     * @since 0.32.5
     */
    @Frozen
    public override func close(): Unit { }

    /**
     * The Function is read
     *
     * @param sink of Buffer
     * @param byteCount of Int64
     *
     * @return Type of Int64
     * @since 0.32.5
     */
    @Frozen
    public override func read(sink: Buffer, byteCount: Int64): Int64 {
        match (this.funRead) {
            case None => -1
            case Some(v) => v(sink, byteCount)
        }
    }

    /**
     * The Function is timeout
     *
     * @return Type of Timeout
     * @since 0.32.5
     */
    @Frozen
    public override func timeout(): Timeout {
        return this.funTimeout.getOrThrow()()
    }

    /**
     * The Function is isClosed
     *
     * @return Type of Bool
     * @since 0.32.5
     */
    @Frozen
    public override func isClosed(): Bool {false}

    /**
     * The Function is toString
     *
     * @return Type of String
     * @since 0.32.5
     */
    @Frozen
    public override func toString(): String{
        return this.toString()
    }
}


class AnonymousSink <: Sink {
    var funWrite: ?(Buffer, Int64)-> Unit = None
    var funFlush: ?()-> Unit = None
    var funTimeout: ?()-> Timeout = None

    /**
     * The Function is close
     *
     * @return Type of Unit
     * @since 0.32.5
     */
    @Frozen
    public override func close(): Unit {}

    /**
     * The Function is flush
     *
     * @return Type of Unit
     * @since 0.32.5
     */
    @Frozen
    public override func flush(): Unit {
        match (this.funFlush) {
            case None => ()
            case Some(v) => v()
        }
    }

    /**
     * The Function is timeout
     *
     * @return Type of Timeout
     * @since 0.32.5
     */
    @Frozen
    public override func timeout(): Timeout {
        return this.funTimeout.getOrThrow()()
    }

    /**
     * The Function is write
     *
     * @param source of Buffer
     * @param byteCount of Int64
     *
     * @return Type of Unit
     * @since 0.32.5
     */
    @Frozen
    public override func write(source: Buffer, byteCount: Int64): Unit {
        match (this.funWrite) {
            case None => ()
            case Some(v) => v(source, byteCount)
        }
    }

    /**
     * The Function is isClosed
     *
     * @return Type of Bool
     * @since 0.32.5
     */
    @Frozen
    public override func isClosed(): Bool {false}

     /**
     * The Function is toString
     *
     * @return Type of String
     * @since 0.32.5
     */
    @Frozen
    public override func toString(): String{
        return this.toString()
    }
}