/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2024-2024. All rights reserved.
 */
package cbor4cj

import std.io.ByteBuffer
import std.io.InputStream
import std.io.OutputStream
import std.math.numeric.BigInt

public abstract class AbstractBuilder<T> {
    private let parent: ?T

    public init(parent: ?T) {
        this.parent = parent
    }

    protected func getParent(): ?T {
        return parent
    }

    protected open func addChunk(_: ?DataItem): Unit {
        throw Exception()
    }

    protected func convert(value: Int64): DataItem {
        if (value >= 0) {
            return UnsignedInteger(value)
        } else {
            return NegativeInteger(value)
        }
    }

    protected func convert(value: BigInt): DataItem {
        if (value.sign == -1) {
            return NegativeInteger(value)
        } else {
            return UnsignedInteger(value)
        }
    }

    protected func convert(value: Bool): DataItem {
        if (value) {
            return SimpleValue.TRUE
        } else {
            return SimpleValue.FALSE
        }
    }

    protected func convert(bytes: Array<UInt8>): DataItem {
        return ByteString(bytes)
    }

    protected func convert(string: String): DataItem {
        return UnicodeString(string)
    }

    protected func convert(value: Float32): DataItem {
        if (isHalfPrecisionEnough(value)) {
            return HalfPrecisionFloat(value)
        } else {
            return SinglePrecisionFloat(value)
        }
    }

    protected func convert(value: Float64): DataItem {
        return DoublePrecisionFloat(value)
    }

    protected func tag(value: Int64): Tag {
        return Tag(value)
    }

    private func isHalfPrecisionEnough(value: Float32): Bool {
        try {
            let outputStream = ByteBuffer()
            let encoder = getHalfPrecisionFloatEncoder(outputStream)
            encoder.encode(HalfPrecisionFloat(value))
            let bytes: Array<UInt8> = outputStream.bytes()
            let inputStream = ByteBuffer()
            inputStream.write(bytes)
            let decoder = getHalfPrecisionFloatDecoder(inputStream)
            let arr = Array<Byte>(1, repeat: 0)
            if (inputStream.read(arr) == -1) {
                throw CborException("unexpected end of stream")
            }
            let halfPrecisionFloat = decoder.decode(0)
            return value == halfPrecisionFloat.getValue()
        } catch (cborException: CborException) {
            return false
        }
    }

    protected open func getHalfPrecisionFloatEncoder(outputStream: OutputStream): HalfPrecisionFloatEncoder {
        return HalfPrecisionFloatEncoder(None, outputStream)
    }

    protected open func getHalfPrecisionFloatDecoder(inputStream: InputStream): HalfPrecisionFloatDecoder {
        return HalfPrecisionFloatDecoder(None, inputStream)
    }
}