// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you 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.

func leadingPrimitiveFastPathFields(_ fields: [ParsedField]) -> [ParsedField] {
    var result: [ParsedField] = []
    result.reserveCapacity(fields.count)
    for field in fields {
        if isPrimitiveFastPathField(field) {
            result.append(field)
        } else {
            break
        }
    }
    return result
}

private func leadingFixedPrimitiveFields(_ fields: [ParsedField]) -> [ParsedField] {
    var result: [ParsedField] = []
    result.reserveCapacity(fields.count)
    for field in fields {
        if primitiveFixedByteWidth(for: field) != nil {
            result.append(field)
        } else {
            break
        }
    }
    return result
}

private func primitiveFixedPrefixBytes(_ fields: [ParsedField]) -> Int {
    fields.reduce(0) { partial, field in
        partial + (primitiveFixedByteWidth(for: field) ?? 0)
    }
}

private func primitiveFixedByteWidth(for field: ParsedField) -> Int? {
    switch trimType(field.typeText) {
    case "Bool", "Int8", "UInt8":
        return 1
    case "Int16", "UInt16":
        return 2
    case "Float":
        return 4
    case "Double":
        return 8
    default:
        return nil
    }
}

private func isPrimitiveFastPathField(_ field: ParsedField) -> Bool {
    guard !field.isOptional else {
        return false
    }
    guard field.dynamicAnyCodec == nil, field.customCodecType == nil else {
        return false
    }
    guard field.typeID != 27, !compatibleFieldNeedsTypeInfo(field) else {
        return false
    }
    return primitiveUnsafeWriteMethod(for: field) != nil && primitiveUnsafeReadMethod(for: field) != nil
}

func buildPrimitiveFastWriteBlock(_ fields: [ParsedField]) -> String? {
    guard !fields.isEmpty else {
        return nil
    }
    let fixedFields = leadingFixedPrimitiveFields(fields)
    let remainingFields = Array(fields.dropFirst(fixedFields.count))
    let fixedPrefixBytes = primitiveFixedPrefixBytes(fixedFields)
    let maxNumericBytes = fields.reduce(0) { partial, field in
        partial + (primitiveMaxEncodedByteWidth(for: field) ?? 0)
    }
    guard maxNumericBytes > 0 else {
        return nil
    }
    let locals = fields.map { field in
        "let __\(field.name) = self.\(field.name)"
    }.joined(separator: "\n        ")
    var fixedOffset = 0
    let fixedWrites = fixedFields.compactMap { field -> String? in
        guard let line = primitiveUnsafeWriteFixedLine(for: field, offset: fixedOffset) else {
            return nil
        }
        fixedOffset += primitiveFixedByteWidth(for: field) ?? 0
        return line
    }.joined(separator: "\n            ")
    let remainingWrites = remainingFields.compactMap { field in
        primitiveUnsafeWriteAdvanceLine(for: field, indexExpr: "__writerIndex")
    }.joined(separator: "\n            ")
    var bodySections: [String] = []
    if !fixedWrites.isEmpty {
        bodySections.append(fixedWrites)
    }
    if !remainingWrites.isEmpty {
        bodySections.append(
            """
            var __writerIndex = \(fixedPrefixBytes)
            \(remainingWrites)
            assert(__writerIndex <= \(maxNumericBytes))
            return __writerIndex
            """
        )
    } else {
        bodySections.append("return \(fixedPrefixBytes)")
    }
    let writeBody = bodySections.joined(separator: "\n            ")
    return """
    \(locals)
    UnsafeUtil.writeRegion(buffer: __buffer, maxCount: \(maxNumericBytes)) { __base in
        \(writeBody)
    }
    """
}

private func primitiveMaxEncodedByteWidth(for field: ParsedField) -> Int? {
    switch trimType(field.typeText) {
    case "Bool", "Int8", "UInt8":
        return 1
    case "Int16", "UInt16":
        return 2
    case "Float":
        return 4
    case "Double":
        return 8
    case "Int32":
        return 5
    case "UInt32":
        return 5
    case "Int64":
        return 9
    case "UInt64":
        return 9
    case "Int":
        return 9
    case "UInt":
        return 9
    default:
        return nil
    }
}

private func primitiveUnsafeWriteFixedLine(for field: ParsedField, offset: Int) -> String? {
    guard let method = primitiveUnsafeWriteMethod(for: field) else {
        return nil
    }
    return "_ = UnsafeUtil.\(method)(__\(field.name), to: __base, index: \(offset))"
}

private func primitiveUnsafeWriteAdvanceLine(for field: ParsedField, indexExpr: String) -> String? {
    guard let method = primitiveUnsafeWriteMethod(for: field) else {
        return nil
    }
    return "__writerIndex = UnsafeUtil.\(method)(__\(field.name), to: __base, index: \(indexExpr))"
}

private func primitiveUnsafeWriteMethod(for field: ParsedField) -> String? {
    switch trimType(field.typeText) {
    case "Bool":
        return "writeBool"
    case "Int8":
        return "writeInt8"
    case "Int16":
        return "writeInt16"
    case "Int32":
        return "writeInt32"
    case "Int64":
        return "writeInt64"
    case "Int":
        return "writeInt"
    case "UInt8":
        return "writeUInt8"
    case "UInt16":
        return "writeUInt16"
    case "UInt32":
        return "writeUInt32"
    case "UInt64":
        return "writeUInt64"
    case "UInt":
        return "writeUInt"
    case "Float":
        return "writeFloat32"
    case "Double":
        return "writeFloat64"
    default:
        return nil
    }
}

private func primitiveUnsafeReadMethod(for field: ParsedField) -> String? {
    switch trimType(field.typeText) {
    case "Bool":
        return "readBool"
    case "Int8":
        return "readInt8"
    case "Int16":
        return "readInt16"
    case "Int32":
        return "readInt32"
    case "Int64":
        return "readInt64"
    case "Int":
        return "readInt"
    case "UInt8":
        return "readUInt8"
    case "UInt16":
        return "readUInt16"
    case "UInt32":
        return "readUInt32"
    case "UInt64":
        return "readUInt64"
    case "UInt":
        return "readUInt"
    case "Float":
        return "readFloat32"
    case "Double":
        return "readFloat64"
    default:
        return nil
    }
}

private func primitiveUnsafeFixedReadMethod(for field: ParsedField) -> String? {
    switch trimType(field.typeText) {
    case "Bool":
        return "readBoolUnchecked"
    case "Int8":
        return "readInt8Unchecked"
    case "UInt8":
        return "readUInt8Unchecked"
    case "Int16":
        return "readInt16Unchecked"
    case "UInt16":
        return "readUInt16Unchecked"
    case "Float":
        return "readFloat32Unchecked"
    case "Double":
        return "readFloat64Unchecked"
    default:
        return nil
    }
}

private func primitiveUnsafeFixedReadExpr(for field: ParsedField, baseExpr: String, offset: Int) -> String? {
    guard let method = primitiveUnsafeFixedReadMethod(for: field) else {
        return nil
    }
    return "UnsafeUtil.\(method)(from: \(baseExpr), index: \(offset))"
}

private func primitiveUnsafePointerReadAdvanceExpr(for field: ParsedField) -> String? {
    guard let method = primitiveUnsafeReadMethod(for: field) else {
        return nil
    }
    return "try UnsafeUtil.\(method)(from: __base, length: __length, index: &__readerIndex)"
}

private struct PrimitiveFastReadLayout {
    let statements: [String]
    let consumedExpr: String
    let fixedPrefixBytes: Int
}

private func buildPrimitiveFastReadStatements(
    _ fields: [ParsedField],
    assignLine: (ParsedField, String) -> String,
    remainingReadExpr: (ParsedField) -> String?
) -> PrimitiveFastReadLayout? {
    guard !fields.isEmpty else {
        return nil
    }
    let fixedFields = leadingFixedPrimitiveFields(fields)
    let remainingFields = Array(fields.dropFirst(fixedFields.count))
    let fixedPrefixBytes = primitiveFixedPrefixBytes(fixedFields)
    var fixedOffset = 0
    let fixedReads = fixedFields.compactMap { field -> String? in
        guard let readExpr = primitiveUnsafeFixedReadExpr(for: field, baseExpr: "__base", offset: fixedOffset) else {
            return nil
        }
        fixedOffset += primitiveFixedByteWidth(for: field) ?? 0
        return assignLine(field, readExpr)
    }.joined(separator: "\n            ")
    let remainingReads = remainingFields.compactMap { field -> String? in
        guard let readExpr = remainingReadExpr(field) else {
            return nil
        }
        return assignLine(field, readExpr)
    }.joined(separator: "\n            ")
    var readSections: [String] = []
    if !fixedReads.isEmpty {
        readSections.append(fixedReads)
    }
    if !remainingReads.isEmpty {
        readSections.append("var __readerIndex = \(fixedPrefixBytes)")
        readSections.append(remainingReads)
    }
    let consumedExpr = remainingReads.isEmpty ? "\(fixedPrefixBytes)" : "__readerIndex"
    return PrimitiveFastReadLayout(
        statements: readSections,
        consumedExpr: consumedExpr,
        fixedPrefixBytes: fixedPrefixBytes
    )
}

private func buildPrimitiveFastReadBlock(
    _ fields: [ParsedField],
    assignLine: (ParsedField, String) -> String
) -> String? {
    guard let readLayout = buildPrimitiveFastReadStatements(
        fields,
        assignLine: assignLine,
        remainingReadExpr: primitiveUnsafePointerReadAdvanceExpr
    ) else {
        return nil
    }
    var readSections: [String] = []
    if readLayout.fixedPrefixBytes > 0 {
        readSections.append("try UnsafeUtil.checkReadable(__bytes, index: 0, need: \(readLayout.fixedPrefixBytes))")
    }
    readSections.append(
        """
        guard let __base = __bytes.baseAddress else {
            throw ForyError.outOfBounds(cursor: 0, need: \(max(readLayout.fixedPrefixBytes, 1)), length: __bytes.count)
        }
        """
    )
    if readLayout.consumedExpr == "__readerIndex" {
        readSections.append("let __length = __bytes.count")
    }
    readSections.append(contentsOf: readLayout.statements)
    readSections.append("return \(readLayout.consumedExpr)")
    let readBody = readSections.joined(separator: "\n            ")
    return """
    try UnsafeUtil.readRegion(buffer: __buffer) { __bytes in
        \(readBody)
    }
    """
}

func buildPrimitiveFastClassReadBlock(_ fields: [ParsedField]) -> String? {
    buildPrimitiveFastReadBlock(fields) { field, readExpr in
        "value.\(field.name) = \(readExpr)"
    }
}

func buildPrimitiveFastStructReadDeclarations(_ fields: [ParsedField]) -> String? {
    guard !fields.isEmpty else {
        return nil
    }
    return fields.map { field in
        "var __\(field.name): \(field.typeText) = \(field.typeText).foryDefault()"
    }.joined(separator: "\n        ")
}

func buildPrimitiveFastStructReadBlock(_ fields: [ParsedField]) -> String? {
    buildPrimitiveFastReadBlock(fields) { field, readExpr in
        "__\(field.name) = \(readExpr)"
    }
}
