package org.allaymc.server.block.type;

import org.allaymc.api.block.property.enums.WallConnectionType;
import org.allaymc.api.block.property.type.BlockPropertyType;
import org.allaymc.api.block.property.type.BlockPropertyTypes;
import org.allaymc.api.block.type.BlockType;
import org.allaymc.server.item.type.AllayItemType;
import org.allaymc.testutils.AllayTestExtension;
import org.allaymc.testutils.TestRegistryFixtures;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;

import static org.allaymc.api.block.type.BlockTypes.BLUE_CANDLE;
import static org.allaymc.api.block.type.BlockTypes.COBBLED_DEEPSLATE_WALL;
import static org.allaymc.server.block.type.AllayBlockType.computeSpecialValue;
import static org.junit.jupiter.api.Assertions.*;

/**
 * @author daoge_cmd
 */
@ExtendWith(AllayTestExtension.class)
class AllayBlockTypeTest {
    static BlockType<TestBlock> testBlockType1;
    static BlockType<TestBlock> testBlockType2;
    static BlockPropertyType<Boolean> TEST_BOOLEAN_PROPERTY_TYPE;
    static BlockPropertyType<Integer> TEST_INT_PROPERTY_TYPE;
    static BlockPropertyType<TestEnum> TEST_ENUM_PROPERTY_TYPE;

    @BeforeAll
    static void init() {
        TEST_BOOLEAN_PROPERTY_TYPE = TestRegistryFixtures.blockBooleanProperty();
        TEST_INT_PROPERTY_TYPE = TestRegistryFixtures.blockIntProperty();
        TEST_ENUM_PROPERTY_TYPE = TestRegistryFixtures.blockEnumProperty();
        testBlockType1 = TestRegistryFixtures.blockType1();
        testBlockType2 = TestRegistryFixtures.blockType2();
    }

    @Test
    void testDefaultItemType() {
        assertNotNull(testBlockType1.getItemType());
        assertNotNull(testBlockType2.getItemType());
    }

    @Test
    void testCustomizeAutoGeneratedItemType() {
        var customTag = TestRegistryFixtures.customizedItemTag();
        var blockType = TestRegistryFixtures.customizedItemBlockType();

        assertNotNull(blockType.getItemType());
        assertTrue(blockType.getItemType().hasItemTag(customTag));
    }

    @Test
    void testConstantAndMissingCustomDefinitions() {
        var constantBlock = (AllayBlockType<?>) TestRegistryFixtures.customizedItemBlockType();
        var definition = constantBlock.getCustomBlockDefinition();

        assertNotNull(definition);
        assertEquals(constantBlock.getAllStates().size(), definition.stateDefinitions().size());
        assertEquals(1, definition.stateDefinitions().values().stream().distinct().count());
        assertNull(((AllayItemType<?>) constantBlock.getItemType()).getCustomItemDefinition());
        assertNull(((AllayBlockType<?>) testBlockType1).getCustomBlockDefinition());
    }

    @Test
    void testBlockType() {
        assertNotNull(testBlockType1);
    }

    @Test
    void testCommon() {
        var block = testBlockType1.getBlockBehavior();
        assertEquals(testBlockType1, block.getBlockType());
        // Test block properties
        var state = block.getBlockType().getDefaultState();
        assertFalse(state.getPropertyValue(TEST_BOOLEAN_PROPERTY_TYPE));
        state = state.setPropertyValue(TEST_BOOLEAN_PROPERTY_TYPE, true);
        assertTrue(state.getPropertyValue(TEST_BOOLEAN_PROPERTY_TYPE));
        assertEquals(0, state.getPropertyValue(TEST_INT_PROPERTY_TYPE));
        state = state.setPropertyValue(TEST_INT_PROPERTY_TYPE, 5);
        assertEquals(5, state.getPropertyValue(TEST_INT_PROPERTY_TYPE));
        assertEquals(TestEnum.A, state.getPropertyValue(TEST_ENUM_PROPERTY_TYPE));
        state = state.setPropertyValue(TEST_ENUM_PROPERTY_TYPE, TestEnum.B);
        assertEquals(TestEnum.B, state.getPropertyValue(TEST_ENUM_PROPERTY_TYPE));
    }

    @Test
    void testBlockStateHash() {
        var b1 = COBBLED_DEEPSLATE_WALL.getDefaultState();
        b1 = b1.setPropertyValue(BlockPropertyTypes.WALL_CONNECTION_TYPE_EAST, WallConnectionType.NONE);
        b1 = b1.setPropertyValue(BlockPropertyTypes.WALL_CONNECTION_TYPE_NORTH, WallConnectionType.TALL);
        b1 = b1.setPropertyValue(BlockPropertyTypes.WALL_CONNECTION_TYPE_SOUTH, WallConnectionType.SHORT);
        b1 = b1.setPropertyValue(BlockPropertyTypes.WALL_CONNECTION_TYPE_WEST, WallConnectionType.NONE);
        b1 = b1.setPropertyValue(BlockPropertyTypes.WALL_POST_BIT, true);
        assertEquals(1789459903, b1.unsignedBlockStateHash());

        var b2 = BLUE_CANDLE.getDefaultState();
        b2 = b2.setPropertyValue(BlockPropertyTypes.CANDLES, 2);
        b2 = b2.setPropertyValue(BlockPropertyTypes.LIT, false);
        assertEquals(4220034033L, b2.unsignedBlockStateHash());
    }

    @Test
    void testBlockAttributes() {
        assertEquals(2, testBlockType1.getBlockBehavior().getBlockStateData(testBlockType1.getDefaultState()).burnOdds());
        assertEquals(3, testBlockType2.getBlockBehavior().getBlockStateData(testBlockType2.getDefaultState()).burnOdds());
    }

    @Test
    void testSpecialValue() {
        var values = new BlockPropertyType.BlockPropertyValue<?, ?, ?>[3];
        values[0] = (TEST_BOOLEAN_PROPERTY_TYPE.createValue(true));// 1 bit
        values[1] = (TEST_INT_PROPERTY_TYPE.createValue(5));// 4 bit
        values[2] = (TEST_ENUM_PROPERTY_TYPE.createValue(TestEnum.B));// 2 bit
        byte offset = 0;
        for (var value : values) {
            offset += value.getPropertyType().getBitSize();
        }
        // 1 0101 01
        assertEquals(0b1010101, computeSpecialValue(offset, values));
        // 1 0101 00
        values[2] = TEST_ENUM_PROPERTY_TYPE.createValue(TestEnum.A);
        assertEquals(0b1010100, computeSpecialValue(offset, values));
        // 1 0000 00
        values[1] = TEST_INT_PROPERTY_TYPE.createValue(0);
        assertEquals(0b1000000, computeSpecialValue(offset, values));
    }

//    @Test
//    void testNullDefaultStateNotAllowed() {
//        assertThrows(BlockTypeBuildException.class, () -> {
//            AllayBlockType
//                    .builder(TestBlockImpl.class)
//                    .identifier("minecraft:test_block3")
//                    .setProperties(
//                            TEST_BOOLEAN_PROPERTY_TYPE,
//                            TEST_INT_PROPERTY_TYPE,
//                            TEST_ENUM_PROPERTY_TYPE)
//                    .addComponent(new TestComponentImpl())
//                    .setDefaultStateSupplier($ -> null)
//                    .build();
//        });
//    }
}
