package org.allaymc.api.block.type;

import org.allaymc.api.block.BlockBehavior;
import org.allaymc.api.block.data.BlockStateData;
import org.allaymc.api.block.property.type.BlockPropertyType;
import org.allaymc.api.item.ItemStack;
import org.allaymc.api.registry.Registries;
import org.cloudburstmc.nbt.NbtMap;
import org.jetbrains.annotations.UnmodifiableView;

import java.util.List;
import java.util.Map;

/**
 * BlockState represents a state of a {@link BlockType}. Each {@link BlockState} is a singleton which means you can directly
 * use `==` to compare whether two block state are equal. All the known block state can be found in {@link Registries#BLOCK_STATE_PALETTE}.
 *
 * @author daoge_cmd
 */
public interface BlockState {

    /**
     * Gets the block type of this block state.
     *
     * @return the associated {@link BlockType}
     */
    BlockType<?> getBlockType();

    /**
     * Gets the hash of this block state. The block state hash is a 32-bit integer with completely
     * different hash values for different block states, and this value does not change as long as
     * the held {@link BlockPropertyType} do not change (e.g. rename, delete, add).
     *
     * @return the hash value
     */
    int blockStateHash();

    /**
     * Gets the special value identifying this state.
     * <p>
     * For all states of a block type, the states have unique special values from one state to another,
     * and the special value can be computed through the state's property values
     *
     * @return the special value as a long
     */
    long specialValue();

    /**
     * Gets the property values of this state.
     *
     * @return unmodifiable map of {@link BlockPropertyType} to {@link BlockPropertyType.BlockPropertyValue}
     */
    @UnmodifiableView
    Map<BlockPropertyType<?>, BlockPropertyType.BlockPropertyValue<?, ?, ?>> getPropertyValues();

    /**
     * Creates a new state with updated property values.
     *
     * @param propertyValues list of {@link BlockPropertyType.BlockPropertyValue} to set
     * @return new {@link BlockState} with applied values
     * @throws IllegalArgumentException if any value is unsupported by this block type
     */
    BlockState setPropertyValues(List<BlockPropertyType.BlockPropertyValue<?, ?, ?>> propertyValues);

    /**
     * Gets the value of a specific property.
     *
     * @param property   the {@link BlockPropertyType} to query
     * @param <DATATYPE> property value type
     * @param <PROPERTY> property type subtype
     * @return the property value
     * @throws IllegalArgumentException if property is unsupported by this block type
     */
    <DATATYPE, PROPERTY extends BlockPropertyType<DATATYPE>> DATATYPE getPropertyValue(PROPERTY property);

    /**
     * Creates a new state with an updated property value.
     *
     * @param propertyValue the {@link BlockPropertyType.BlockPropertyValue} to apply
     * @return new {@link BlockState} with the updated value
     * @throws IllegalArgumentException if value is unsupported by this block type
     */
    BlockState setPropertyValue(BlockPropertyType.BlockPropertyValue<?, ?, ?> propertyValue);

    /**
     * Creates a new state with a specific property value set.
     *
     * @param property   the {@link BlockPropertyType} to modify
     * @param value      the value to set
     * @param <DATATYPE> property value type
     * @param <PROPERTY> property type subtype
     * @return new {@link BlockState} with the updated value
     * @throws IllegalArgumentException if property or value is unsupported by this block type
     */
    <DATATYPE, PROPERTY extends BlockPropertyType<DATATYPE>> BlockState setPropertyValue(PROPERTY property, DATATYPE value);

    /**
     * Gets the unsigned version of the block state hash.
     *
     * @return the unsigned version of the block state hash
     */
    default long unsignedBlockStateHash() {
        return Integer.toUnsignedLong(blockStateHash());
    }

    /**
     * Gets the NBT representation of this state.
     *
     * @return {@link NbtMap} of this block state
     */
    NbtMap getBlockStateNBT();

    /**
     * Converts this state to its item form.
     *
     * @return {@link ItemStack} representing this state
     */
    ItemStack toItemStack();

    /**
     * Gets the behavior of this block state.
     *
     * @return the {@link BlockBehavior} instance
     */
    default BlockBehavior getBehavior() {
        return getBlockType().getBlockBehavior();
    }

    /**
     * Gets the data for this block state.
     *
     * @return the {@link BlockStateData} instance
     */
    default BlockStateData getBlockStateData() {
        return getBehavior().getBlockStateData(this);
    }
}
