from sonolus.backend.ir import IRGet
from sonolus.backend.place import BlockPlace
from sonolus.script.internal.context import ctx
from sonolus.script.internal.generic import validate_concrete_type
from sonolus.script.internal.impl import validate_value
from sonolus.script.internal.meta_fn import meta_fn
from sonolus.script.internal.value import BackingValue, ExprBackingValue, ReadOnlyBackingValueWrapper


@meta_fn
def alloc[T](type_: type[T]) -> T:  # type: ignore
    """Return an uninitialized instance of the given type.

    Use this carefully as reading from uninitialized memory can lead to unexpected behavior.
    """
    type_ = validate_concrete_type(type_)
    if ctx():
        return type_._alloc_()  # type: ignore
    else:
        return type_._alloc_()._as_py_()  # type: ignore


@meta_fn
def zeros[T](type_: type[T]) -> T:  # type: ignore
    """Make a new instance of the given type initialized with zeros.

    Generally works the same as the unary `+` operator on record and array types.
    """
    return validate_concrete_type(type_)._zero_()  # type: ignore


@meta_fn
def copy[T](value: T) -> T:
    """Make a deep copy of the given value.

    Generally works the same as the unary `+` operator on records and arrays.
    """
    value = validate_value(value)  # type: ignore
    if ctx():
        return value._copy_()  # type: ignore
    else:
        return value._copy_()._as_py_()  # type: ignore


def swap[T](a: T, b: T):
    """Swap the values of the two provided mutable values."""
    temp = copy(a)
    a @= b  # type: ignore
    b @= temp  # type: ignore


@meta_fn
def sizeof(type_: type, /) -> int:
    """Return the size of the given type."""
    return validate_concrete_type(type_)._size_()


@meta_fn
def freeze[T](value: T) -> T:
    """Return a read-only version of the given value.

    If the original value is modified, no guarantees are made about behavior and changes may or may not be reflected
    in the frozen version. Use freeze(copy(value)) to guarantee independence.
    """
    value = validate_value(value)
    result = type(value)._from_list_(_map_data_value_to_frozen(v) for v in value._to_list_())
    if ctx():
        return result
    else:
        return result._as_py_()


def _map_data_value_to_frozen(v):
    match v:
        case int() | float() | bool() | ExprBackingValue() | ReadOnlyBackingValueWrapper():
            return v
        case BackingValue():
            return ReadOnlyBackingValueWrapper(v)
        case BlockPlace():
            return ExprBackingValue(IRGet(v))
        case _:
            raise TypeError("Unsupported value for freezing")
