package org.allaymc.api.block.type;

import org.allaymc.api.block.BlockBehavior;
import org.allaymc.api.block.data.BlockTag;
import org.allaymc.api.block.property.type.BlockPropertyType;
import org.allaymc.api.item.type.ItemType;
import org.allaymc.api.utils.identifier.Identified;
import org.allaymc.api.utils.identifier.Identifier;
import org.jetbrains.annotations.Unmodifiable;
import org.jetbrains.annotations.UnmodifiableView;

import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Represents a type of block.
 *
 * @author daoge_cmd
 */
public interface BlockType<T extends BlockBehavior> extends Identified {

    /**
     * Get the block behavior associated with this block type.
     *
     * @return the block behavior
     */
    T getBlockBehavior();

    /**
     * Get the properties of this block type.
     *
     * @return an unmodifiable view of the map of block property types
     */
    @UnmodifiableView
    Map<String, BlockPropertyType<?>> getProperties();

    /**
     * Check if the block type has the specified property.
     *
     * @param name the property name
     * @return {@code true} if the block type has the property, {@code false} otherwise.
     */
    default boolean hasProperty(String name) {
        return getProperties().containsKey(name);
    }

    /**
     * Check if the block type has the specified property.
     *
     * @param propertyType the property type
     * @return {@code true} if the block type has the property, {@code false} otherwise.
     */
    default boolean hasProperty(BlockPropertyType<?> propertyType) {
        return getProperties().containsKey(propertyType.getName());
    }

    /**
     * Get all possible states of this block type.
     *
     * @return an unmodifiable view of the collection of all block states
     */
    @UnmodifiableView
    Collection<BlockState> getAllStates();

    /**
     * Get the hash map which contains all states of this block type.
     * The key is block state's hash.
     *
     * @return an unmodifiable view of the map of block states by their hash
     */
    @UnmodifiableView
    Map<Integer, BlockState> getBlockStateHashMap();

    /**
     * Get the special value map of block states.
     * The key is block state's special value.
     *
     * @return an unmodifiable view of the map of block states by their special values
     */
    @UnmodifiableView
    Map<Long, BlockState> getSpecialValueMap();

    /**
     * Get the number of bits used for special values.
     *
     * @return the number of special value bits
     */
    byte getSpecialValueBits();

    /**
     * Get the default state of this block type.
     *
     * @return the default block state
     */
    BlockState getDefaultState();

    /**
     * Get the item identifier for this block type.
     *
     * @return the item identifier
     */
    default Identifier getItemIdentifier() {
        return getItemType().getIdentifier();
    }

    /**
     * Get the item type associated with this block type.
     *
     * @return the item type
     */
    ItemType<?> getItemType();

    /**
     * Creates a block state from the given list of property values.
     *
     * @param propertyValues the list of property values
     * @return the block state, or null if the given property values is invalid
     */
    BlockState ofState(List<BlockPropertyType.BlockPropertyValue<?, ?, ?>> propertyValues);

    /**
     * Creates a block state, the property values are copied from the given block state. <br>
     * The given block state's type must have same property types as this block type.
     *
     * @param other the block state to copy property values from
     * @return the block state, or null if the given block state is invalid
     */
    default BlockState copyPropertyValuesFrom(BlockState other) {
        return ofState(other.getPropertyValues().values().toArray(BlockPropertyType.BlockPropertyValue<?, ?, ?>[]::new));
    }

    /**
     * Creates a block state from the given block state hash.
     *
     * @param blockStateHash the block state hash
     * @return the block state, or null if the given block state hash is invalid
     */
    default BlockState ofState(int blockStateHash) {
        return getBlockStateHashMap().get(blockStateHash);
    }

    /**
     * Creates a block state from the given array of property values.
     *
     * @param propertyValues the array of property values
     * @return the block state, or null if the given property values is invalid
     */
    default BlockState ofState(BlockPropertyType.BlockPropertyValue<?, ?, ?>... propertyValues) {
        return ofState(List.of(propertyValues));
    }

    /**
     * Get the set of tags associated with this block type.
     *
     * @return an unmodifiable set of block tags
     */
    @Unmodifiable
    Set<BlockTag> getBlockTags();

    /**
     * Check if this block type has the specified tag.
     *
     * @param blockTag the block tag
     * @return {@code true} if the block type has the tag, {@code false} otherwise.
     */
    default boolean hasBlockTag(BlockTag blockTag) {
        return getBlockTags().contains(blockTag);
    }
}
