/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
 * This source file is part of the Cangjie project, licensed under Apache-2.0
 * with Runtime Library Exception.
 *
 * See https://cangjie-lang.cn/pages/LICENSE for license information.
 */

package std.net

/**
 * This struct represents a complex socket state including termination state,
 * presence of readers/writers, number of users, connected and bound flags.
 *
 * The idea is to encode all the state fields into a single number that can be
 * stored to an atomic variable and can be checked and updated atomically.
 */
struct SocketState {
    // the bit layout is as follows
    // 31 has writer
    // 30 has reader
    // 29 bound
    // 28 connected
    // 27 failed
    // 25-26 state code
    // 0-24  use counter
    private static const CONNECTED_MASK: UInt32 = 1u32 << 28
    private static const BOUND_MASK: UInt32 = 0b10u32 << 28
    private static const HAS_READER_MASK: UInt32 = 0b100u32 << 28
    private static const HAS_WRITER_MASK: UInt32 = 0b1000u32 << 28
    private static const FAILED_MASK: UInt32 = 1u32 << 27

    private static const COUNTER_MASK: UInt32 = 0x1FF_FFFFu32

    // 2 bits for state code
    // states are number in the proper order: it makes difference
    private static const NATIVE_STATE_MASK: UInt32 = 0x600_0000u32
    private static const NATIVE_STATE_SHIFT: UInt8 = 25
    private static const STATE_NOT_CREATED_CODE: UInt32 = 0
    private static const STATE_PREPARED_CODE: UInt32 = 1
    private static const STATE_CLOSING_CODE: UInt32 = 2
    private static const STATE_DISPOSED_CODE: UInt32 = 3

    static let Initial = SocketState()
    static let Terminal: SocketState
    static let JustAccepted: SocketState

    SocketState(var value: UInt32) {}

    private init() {
        this(0)
    }

    static init() {
        var s = SocketState()
        s.nativeState = SocketDisposed
        Terminal = s

        s = SocketState()
        s.nativeState = HolderState.SocketPrepared
        s.bound = true
        s.connected = true
        JustAccepted = s
    }

    /**
     * Number of currently running operations
     */
    mut prop useCounter: UInt32 {
        get() {
            value & COUNTER_MASK
        }
        set(newValue) {
            if ((newValue & !COUNTER_MASK) != 0) {
                incrementFailed()
            }
            value = (value & !COUNTER_MASK) | newValue
        }
    }

    private static func incrementFailed(): Nothing {
        throw OverflowException("Socket usage couter overflow")
    }

    /**
     * Numeric value of the current state, see STATE_XXX_CODE constants
     */
    private mut prop nativeStateNum: UInt32 {
        get() {
            (value & NATIVE_STATE_MASK) >> NATIVE_STATE_SHIFT
        }
        set(newValue) {
            value = (value & !NATIVE_STATE_MASK) | ((newValue << NATIVE_STATE_SHIFT) & NATIVE_STATE_MASK)
        }
    }

    prop disposedOrShuttingDown: Bool {
        get() {
            return nativeStateNum >= STATE_CLOSING_CODE
        }
    }

    mut prop nativeState: HolderState {
        get() {
            let nativeStateNum = nativeStateNum
            match {
                case nativeStateNum == STATE_NOT_CREATED_CODE => SocketNotCreated
                case nativeStateNum == STATE_PREPARED_CODE => SocketPrepared
                case nativeStateNum == STATE_CLOSING_CODE => SocketClosing
                case _ => SocketDisposed
            }
        }
        set(newState) {
            let newValue: UInt32 = match (newState) {
                case SocketNotCreated => STATE_NOT_CREATED_CODE
                case SocketPrepared => STATE_PREPARED_CODE
                case SocketClosing => STATE_CLOSING_CODE
                case SocketDisposed => STATE_DISPOSED_CODE
                // introducing more socket states will require changing NATIVE_STATE_MASK
                // and reconsidering native state bit layout
            }
            if (nativeStateNum > newValue) {
                throw IllegalArgumentException("Backward state transition is not allowed")
            }
            nativeStateNum = newValue
        }
    }

    mut prop connected: Bool {
        get() {
            check(CONNECTED_MASK)
        }
        set(new) {
            setBit(CONNECTED_MASK, new)
        }
    }

    mut prop bound: Bool {
        get() {
            check(BOUND_MASK)
        }
        set(new) {
            setBit(BOUND_MASK, new)
        }
    }

    mut prop hasReaders: Bool {
        get() {
            check(HAS_READER_MASK)
        }
        set(new) {
            setBit(HAS_READER_MASK, new)
        }
    }

    mut prop hasWriters: Bool {
        get() {
            check(HAS_WRITER_MASK)
        }
        set(new) {
            setBit(HAS_WRITER_MASK, new)
        }
    }

    mut prop failed: Bool {
        get() {
            check(FAILED_MASK)
        }
        set(new) {
            setBit(FAILED_MASK, new)
        }
    }

    func ensureNotClosed(): Unit {
        if (disposedOrShuttingDown) {
            SocketException.throwClosedException()
        }
    }

    func ensureConnected(): Unit {
        if (!connected) {
            SocketException.notYetConnected()
        }
    }

    func ensureNotConnected() {
        if (connected) {
            SocketException.alreadyConnected()
        }
    }

    func ensureBound(): Unit {
        if (!bound) {
            SocketException.notYetBound()
        }
    }

    func ensureBindInvoked(): Unit {
        if (!bound) {
            SocketException.notYetBoundNeedBind()
        }
    }

    func ensureNotBound(): Unit {
        if (bound) {
            SocketException.alreadyBound()
        }
    }

    private func check(mask: UInt32): Bool {
        (value & mask) != 0
    }

    private mut func setBit(mask: UInt32, newState: Bool) {
        if (newState) {
            value |= mask
        } else {
            value &= !mask
        }
    }
}