/**
 * Copyright 2024 Beijing Baolande Software Corporation
 *
 * 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.
 *
 * Runtime Library Exception to the Apache 2.0 License:
 *
 * As an exception, if you use this Software to compile your source code and
 * portions of this Software are embedded into the binary product as a result,
 * you may redistribute such product without providing attribution as would
 * otherwise be required by Sections 4(a), 4(b) and 4(d) of the License.
 */

package hyperion.transport

/**
 * 封装为带长度的报文
 *
 * @author yangfuping
 */
public class LengthBasedFrameEncoder <: TypedMarshallerEncoder<ByteBuffer> {
    private let lengthFieldSizeVal: Int64

    private let includesLengthFieldSize: Bool

    /**
     * 检查记录报文长度的字节数,支持1字节、2字节、4字节四种长度
     */
    static func checkFrameLength(lengthFieldSize: Int64) {
        match (lengthFieldSize) {
            case 1 | 2 | 4 => return
            case _ => throw IllegalArgumentException(
                "unsupported lengthFieldSize: ${lengthFieldSize} (expected: 1, 2, or 4)");
        }
    }

    public init(lengthFieldSize: Int64) {
        this(lengthFieldSize, false)
    }

    public prop lengthFieldSize: Int64 {
        get() {
            return lengthFieldSizeVal
        }
    }

    public init(lengthFieldSize: Int64, includesLengthFieldSize: Bool) {
        checkFrameLength(lengthFieldSize)
        this.lengthFieldSizeVal = lengthFieldSize
        this.includesLengthFieldSize = includesLengthFieldSize
    }

    public func doEncode(context: IoFilterContext, session: Session, inMessage: ByteBuffer): Unit {
        if (logger.isTraceEnabled()) {
            logger.log(LogLevel.TRACE, "Encoding message: ${inMessage}")
        }

        var size = inMessage.limit()
        if (includesLengthFieldSize) {
            size += lengthFieldSizeVal
        }

        if (logger.isTraceEnabled()) {
            logger.log(LogLevel.TRACE, "Write FrameLength: ${size}")
        }

        let lengthFrameBuffer = ByteBuffer.allocate(lengthFieldSizeVal)
        match (lengthFieldSizeVal) {
            case 1 =>
                if (size > Int64(UInt8.Max)) {
                    throw ProtocolCodecException(
                        "Message size ${size} is overflow, max size should not large than ${UInt8.Max}");
                }
                lengthFrameBuffer.putByte(UInt8(size))
            case 2 =>
                if (size > Int64(UInt16.Max)) {
                    throw ProtocolCodecException(
                        "Message size ${size} is overflow, max size should not large than ${UInt16.Max}");
                }
                lengthFrameBuffer.putUInt16(UInt16(size))
            case 4 =>
                lengthFrameBuffer.putUInt32(UInt32(size))
                if (size > Int64(UInt32.Max)) {
                    throw ProtocolCodecException(
                        "Message size ${size} is overflow, max size should not large than ${UInt32.Max}");
                }
            case _ => throw ProtocolCodecException(
                "Unsupported lengthFieldSize: ${lengthFieldSizeVal} (expected: 1, 2, or 4)");
        }

        // 先添加记录长度的ByteBuffer
        lengthFrameBuffer.flip()
        context.offerMessage(lengthFrameBuffer)
        // 原有的ByteBuffer
        context.offerMessage(inMessage)

        if (logger.isTraceEnabled()) {
            logger.log(LogLevel.TRACE, "Encoding result: ${lengthFrameBuffer} and ${inMessage}")
        }
    }

    public override func toString() {
        return "LengthBasedFrameEncoder[lengthFiledSize=${lengthFieldSizeVal}, includesLengthFieldSize=${includesLengthFieldSize}]"
    }
}

/**
 * 拆解带长度的报文
 *
 * @author yangfuping
 */
public class LengthBasedFrameDecoder <: TypedUnmarsahllerDecoder<ByteBuffer> & MessageCompletedHandler {
    private let lengthFieldSizeVal: Int64

    private let includesLengthFieldSize: Bool

    public init(lengthFieldSize: Int64) {
        this(lengthFieldSize, false)
    }

    public init(lengthFieldSize: Int64, includesLengthFieldSize: Bool) {
        LengthBasedFrameEncoder.checkFrameLength(lengthFieldSize)
        this.lengthFieldSizeVal = lengthFieldSize
        this.includesLengthFieldSize = includesLengthFieldSize
    }

    public prop lengthFieldSize: Int64 {
        get() {
            return lengthFieldSizeVal
        }
    }

    public func messageCompleted(buffer: ByteBuffer, status: MessageCompletedStatus) {
        if (buffer.position() <= lengthFieldSizeVal) {
            status.completed = false
            return
        }

        var messageSize = status.messageSize
        var frameLength = 0
        if (let Some(size) <- messageSize) {
            frameLength = size
        } else {
            frameLength = Int64(getFrameLength(buffer))
            status.messageSize = Int64(frameLength)
        }

        if (buffer.position() >= frameLength) {
            status.completed = true
        }
    }

    private func getFrameLength(buffer: ByteBuffer): Int64 {
        var frameLength = 0
        match (lengthFieldSizeVal) {
            case 1 => frameLength = Int64(buffer.getByte(0))
            case 2 => frameLength = Int64(buffer.getUInt16(0))
            case 4 => frameLength = Int64(buffer.getUInt32(0))
            case _ => throw ProtocolCodecException(
                "Unsupported lengthFieldSize: ${lengthFieldSizeVal} (expected: 1, 2, or 4)");
        }

        if (logger.isTraceEnabled()) {
            logger.log(LogLevel.TRACE, "FrameLength: ${frameLength}")
        }

        if (!includesLengthFieldSize) {
            frameLength = frameLength + lengthFieldSizeVal
        }

        return frameLength
    }

    public func doDecode(context: IoFilterContext, session: Session, inMessage: ByteBuffer) {
        if (logger.isTraceEnabled()) {
            logger.log(LogLevel.TRACE, "Decoding message: ${inMessage}")
        }

        var frameLength = getFrameLength(inMessage)
        if (inMessage.limit() < frameLength) {
            throw ProtocolCodecException("Message's size ${inMessage.position()} is less than expected ${frameLength}");
        }

        // 丢弃超出部分
        inMessage.limit(inMessage.position() + frameLength)
        inMessage.position(inMessage.position() + lengthFieldSizeVal)

        context.offerMessage(inMessage)

        if (logger.isTraceEnabled()) {
            logger.log(LogLevel.TRACE, "Decoding result: ${inMessage}")
        }
    }

    public override func toString() {
        return "LengthBasedFrameDecoder[lengthFiledSize=${lengthFieldSizeVal}, includesLengthFieldSize=${includesLengthFieldSize}]"
    }
}

/**
 *
 * @author yangfuping
 */
public class LengthBasedFrameCodec <: ProtocolCodecFilter {
    public init(encoder: LengthBasedFrameEncoder, decoder: LengthBasedFrameDecoder) {
        super(encoder, decoder)

        if (encoder.lengthFieldSize != decoder.lengthFieldSize) {
            throw IllegalArgumentException(
                "The length field of LengthBasedFrameEncoder and LengthBasedFrameDecoder not the same,  encoder lengthFieldSize: ${encoder.lengthFieldSize}, decoder lengthFieldSize: ${decoder.lengthFieldSize}"
            )
        }
    }
}