#include <Block/Block.hpp>
#include <Item/StatelessItem/BasicItem/Dye/Dye.hpp>
#include <ItemRegistry/ItemRegistry.hpp>
#include <Item/StatefulItem/BlockItem/BlockItem.hpp>
#include <Entity/PhysicsEntity/CameraEntity/Player/Player.hpp>
#include <Machine/Machine.hpp>
#include <Block/IMachineBlock.hpp>
#include <World/World.hpp>
#include <World/LocalWorld/LocalWorld.hpp>
#include <MovementPhysics/AirMovementPhysics/AirMovementPhysics.hpp>
#include <random>

Block::Block(std::string name, std::string displayName, float hardness) :
    grindingOutputName(""),
    breakingSound(&Sound::NONE),
    placingSound(&Sound::NONE),
    fallingSound(&Sound::NONE),
    name(name),
    displayName(displayName),
    hardness(hardness)
{
    if (isPrefixed("stone") || isSuffixed("Ore") || isSuffixed("OreX") || isSuffixed("OreMantle"))
    {
        withStoneSounds();
    }
}

bool Block::isIntersectedByRay(World &world, glm::ivec3 pos, wfvec3 start, wfvec3 dir, Side &side, RGBABytes &collisionKey)
{
    std::vector<BoundingBox<wfixed>> boxes;
    getCollisionBoxes(world, pos, boxes);

    for (BoundingBox<wfixed> &box : boxes)
    {
        std::optional<Side> maybeSide = box.getSideIntersectedByRay(start, dir);
        if (maybeSide.has_value())
        {
            side = *maybeSide;
            return true;
        }
    }

    return false;
}

bool Block::isIntersectedBy(World &world, glm::ivec3 pos, const BoundingBox<wfixed> &otherBox)
{
    std::vector<BoundingBox<wfixed>> boxes;
    getCollisionBoxes(world, pos, boxes);

    for (BoundingBox<wfixed> &box : boxes)
    {
        if (box.overlaps(otherBox))
        {
            return true;
        }
    }

    return false;
}

ItemStack Block::toItemStack(int stackSize)
{
    ItemStack stack = ItemStack(*itemRegistry.getItemByName("main:block"), stackSize);
    BlockItemState *state = new BlockItemState();
    state->block = this;
    stack.setStatePtr(reinterpret_cast<void*>(state));
    return stack;
}

void Block::onInteract(LocalWorld &world, Player &player, glm::ivec3 pos, Side side, const RGBABytes &collisionKey)
{
    ItemStack &stack = player.holdingStack();
    if (!stack.isEmpty())
    {
        stack.getItem()->onUse(world, player, stack, pos, side);
    }
}

void Block::getHarvestedStacks(LocalWorld &world, glm::ivec3 pos, std::vector<ItemStack> &stacks)
{
    stacks.push_back(toItemStack(1));
}

bool Block::canManuallyDig()
{
    return false;
}

std::string Block::getPureName()
{
    return name.substr(name.find(':')+1);
}

bool Block::isPrefixed(std::string prefix)
{
    if (getPureName().size() < prefix.size()) return false;

    return getPureName().substr(0, prefix.size()) == prefix;
}

bool Block::isDyedBlock()
{
    for (const auto &colorName : Dye::colorNames)
    {
        if (isPrefixed(colorName))
        {
            return true;
        }
    }

    return false;
}

bool Block::isSuffixed(std::string suffix)
{
    std::string pureName = getPureName();
    if (pureName.size() < suffix.size()) return false;
    return pureName.substr(pureName.size()-suffix.size(), suffix.size()) == suffix;
}

glm::vec3 Block::getLightColor(uint8_t meta)
{
    return glm::vec3(0.0f, 0.0f, 0.0f);
}

float Block::getLightVariance(uint8_t meta)
{
    return 0.0f;
}

glm::ivec3 Block::getLightOffset(uint8_t meta)
{
    return glm::ivec3(4);
}

bool Block::concealsFluidBelow()
{
    return isFullyOpaque();
}

bool Block::isFluidInteractable()
{
    return false;
}

bool Block::isFluid()
{
    return false;
}

void Block::onNeighborUpdated(LocalWorld &world, glm::ivec3 pos, Side side)
{
    // NOP
}

ItemStack Block::getGrindingOutput()
{
    if (grindingOutputName.size() == 0)
    {
        return ItemStack();
    }

    return ItemStack(grindingOutputName);
}

Block& Block::grindsTo(std::string name)
{
    grindingOutputName = name;
    return *this;
}

Block& Block::withStoneSounds()
{
    placingSound = &Sound::placingStone;
    breakingSound = &Sound::rockBreak;
    fallingSound = &Sound::stoneFootsteps2;

    withFootstepSound(Sound::stoneFootsteps1);
    withFootstepSound(Sound::stoneFootsteps2);

    return *this;
}

Block& Block::withDustSounds()
{
    withPlacingSound(Sound::placingDust);
    withBreakingSound(Sound::breakingDust);
    withFallingSound(Sound::fallOnDust);

    withFootstepSound(Sound::dustFootsteps1);
    withFootstepSound(Sound::dustFootsteps2);
    withFootstepSound(Sound::dustFootsteps3);

    return *this;
}

Block& Block::withWoodSounds()
{
    withPlacingSound(Sound::woodKnock);
    withBreakingSound(Sound::woodBreak);
    withFallingSound(Sound::woodKnock);

    withFootstepSound(Sound::woodFootsteps1);
    withFootstepSound(Sound::woodFootsteps2);
    withFootstepSound(Sound::woodFootsteps3);

    return *this;
}

Block& Block::withMetalSounds()
{
    withPlacingSound(Sound::placingMetal);
    withBreakingSound(Sound::breakingMetal);
    withFallingSound(Sound::fallingOnMetal);

    withFootstepSound(Sound::metalFootsteps1);

    return *this;
}

Block& Block::withGlassSounds()
{
    withPlacingSound(Sound::placingGlass);
    withBreakingSound(Sound::breakingGlass);
    withFallingSound(Sound::fallingOnGlass);

    withFootstepSound(Sound::glassFootsteps1);

    return *this;
}

Block& Block::withBreakingSound(Sound &sound)
{
    breakingSound = &sound;
    return *this;
}

Block& Block::withPlacingSound(Sound &sound)
{
    placingSound = &sound;
    return *this;
}

Block& Block::withFootstepSound(Sound &sound)
{
    footstepSounds.push_back(&sound);
    return *this;
}

Block& Block::withFallingSound(Sound &sound)
{
    fallingSound = &sound;
    return *this;
}

Block& Block::withSoundsFrom(const Block &otherBlock)
{
    breakingSound = otherBlock.breakingSound;
    placingSound = otherBlock.placingSound;
    footstepSounds = otherBlock.footstepSounds;
    fallingSound = otherBlock.fallingSound;

    return *this;
}

void Block::onPlaced(LocalWorld &world, Chunk &chunk, glm::ivec3 pos)
{
    // NOP
}

void Block::onSignal(LocalWorld &world, glm::ivec3 pos, uint32_t code)
{
    if ((code & ~sigNeighborUpdateSideMask) == sigNeighborUpdateBase)
    {
        onNeighborUpdated(world, pos, Side(code & sigNeighborUpdateSideMask));
    }
}

void Block::getCollisionBoxes(World &world, glm::ivec3 pos, std::vector<BoundingBox<wfixed>> &boxes)
{
    BoundingBox<wfixed> box;
    box.x0 = (wfixed) pos.x;
    box.x1 = (wfixed) (pos.x+1);
    box.y0 = (wfixed) pos.y;
    box.y1 = (wfixed) (pos.y+1);
    box.z0 = (wfixed) pos.z;
    box.z1 = (wfixed) (pos.z+1);

    boxes.push_back(box);
}

bool Block::canReplaceWithPlacedBlock()
{
    return false;
}

bool Block::canPlace(LocalWorld &world, IWorldActor &actor, glm::ivec3 pos, Side side, uint8_t &meta)
{
    return true;
}

bool Block::isProtectorOf(World &world, glm::ivec3 myPos, glm::ivec3 otherPos)
{
    return false;
}

MovementPhysics& Block::getMovementPhysicsInside()
{
    return AirMovementPhysics::instance;
}

Sound& Block::getBackgroundSound(uint8_t meta)
{
    return Sound::NONE;
}

Sound& Block::getManualDiggingSound()
{
    return Sound::NONE;
}

std::vector<Block::Detail> Block::getDetails()
{
    std::vector<Detail> detailVec;

    detailVec.push_back(Detail{"grindingOutputName", grindingOutputName});
    detailVec.push_back(Detail{"breakingSound", getBreakingSound().name});
    detailVec.push_back(Detail{"placingSound", getPlacingSound().name});
    detailVec.push_back(Detail{"name", name});
    detailVec.push_back(Detail{"hardness", std::to_string(hardness)});
    detailVec.push_back(Detail{"canManuallyDig", std::to_string(canManuallyDig())});
    detailVec.push_back(Detail{"concealsFluidBelow", std::to_string(concealsFluidBelow())});
    detailVec.push_back(Detail{"isFluidInteractable", std::to_string(isFluidInteractable())});
    detailVec.push_back(Detail{"canReplaceWithPlacedBlock", std::to_string(canReplaceWithPlacedBlock())});

    return detailVec;
}

Sound& Block::getBreakingSound()
{
    return *breakingSound;
}

Sound& Block::getPlacingSound()
{
    return *placingSound;
}

Sound& Block::getRandomFootstepSound()
{
    if (footstepSounds.empty())
    {
        return Sound::NONE;
    }

    std::random_device rd;
    std::mt19937 gen(rd());
    std::uniform_int_distribution<> distr(0, footstepSounds.size() - 1);
    int randomIndex = distr(gen);

    return *footstepSounds[randomIndex];
}

Sound& Block::getFallingSound()
{
    return *fallingSound;
}

void Block::onRemoved(LocalWorld &world, Chunk &chunk, glm::ivec3 pos, uint8_t meta)
{
    // NOP
}

void Block::onWalkingOver(World &world, glm::ivec3 pos, PhysicsEntity &entity)
{
    // NOP
}

void Block::onEntering(World &world, PhysicsEntity &entity)
{
    // NOP
}

void Block::onLeaving(World &world, PhysicsEntity &entity)
{
    // NOP
}

void Block::getEffects(uint8_t meta, glm::vec3 offset, std::vector<std::shared_ptr<BlockEffect>> &effects)
{
    // NOP
}

bool Block::canSweepAway()
{
    return false;
}