#pragma once

#include <robin_hood.h>
#include "../../core/world/materials.hpp"
#include <cassert>

namespace world {
    /// Universe unit
    struct Voxel {
        /// Packed value
        /// swap(1) + material(12) + density(3)
        uint16_t value;
        using material_t = uint_fast16_t;
        using density_t = uint_fast8_t;
        static constexpr density_t DENSITY_MAX = (1 << 3)-1;
        static constexpr material_t MATERIAL_MAX = (1 << 12)-1;

        static constexpr uint16_t DENSITY_MASK = 0b0111;
        static constexpr uint16_t MATERIAL_MASK = 0b0111'1111'1111'1000;
        static constexpr uint16_t SWAP_MASK = 0b1000'0000'0000'0000;

        Voxel(uint16_t value = 0): value(value) { }
        Voxel(material_t material, density_t density, bool swap = false) {
            assert(density <= DENSITY_MAX);
            assert(material <= MATERIAL_MAX);
            value = (swap * SWAP_MASK) |
                    ((material << 3) & MATERIAL_MASK) |
                    (density & DENSITY_MASK);
        }

        /// Material type
        constexpr inline material_t material() const {
            return (value & MATERIAL_MASK) >> 3;
        }
        /// Texture idx
        constexpr inline uint16_t texture() const {
            return materials::textures_map[material()];
        }

        /// Quantity of element
        constexpr inline density_t density() const {
            return value & DENSITY_MASK;
        }
        /// Quantity of element on [0, 1]
        constexpr inline float density_ratio() const {
            return density() * (1.f / world::Voxel::DENSITY_MAX);
        }

        /// Swap value
        /// Use external metadata table
        constexpr inline bool swap() const {
            return (value & SWAP_MASK) != 0;
        }

        /// Is solid
        constexpr inline bool is_solid() const {
            return density() > 0 && materials::solidity[material()];
        }
        /// Is visible matter
        constexpr inline bool is_visible() const {
            return !materials::invisibility[material()];
        }
        /// Contains matter
        constexpr inline bool is_material() const {
            return density() > 0 && is_visible();
        }
        /// Is full
        constexpr inline bool is_full() const {
            return density() == DENSITY_MAX && !materials::transparency[material()];
        }

        /// Value after fill operation
        inline Voxel const filled(Voxel in, float ratio) {
            if (ratio >= 1)
                return in;

            const world::Voxel::density_t dst = in.density() * ratio;
            if (!in.is_material())
                return world::Voxel(material(), world::Voxel::DENSITY_MAX-dst, swap());

            if (is_material() && density() > dst)
                return Voxel(value);

            return world::Voxel(in.material(), dst, in.swap());
        }
    };
    /// Stock of material
    struct Item {
        /// Quantity of material
        unsigned long long Count;
        /// Material type
        /// @see world::materials
        Voxel::material_t Material;
    };
    /// List of materials
    struct ItemList: robin_hood::unordered_map<Voxel::material_t, unsigned long long> {
        void add(const std::optional<Item>& item) {
            if(item) {
                (*this)[item.value().Material] += item.value().Count;
            }
        }
    };
}
