#include <Decoration/Tree/Tree.hpp>
#include <Utilities/Utilities.hpp>

void Tree::Sculpture::buildLeaves(ISculptor &sculptor, glm::ivec3 centerPos, int radius)
{
    if (leafBlock == "main:air")
    {
        return;
    }

    glm::ivec3 pos;
    for (pos.z=-radius; pos.z<=radius; pos.z++)
    {
        for (pos.y=-radius; pos.y<=radius; pos.y++)
        {
            for (pos.x=-radius; pos.x<=radius; pos.x++)
            {
                if (pos.x*pos.x + pos.y*pos.y + pos.z*pos.z <= radius*radius)
                {
                    sculptor.place(centerPos + pos, leafBlock);
                }
            }
        }
    }
}

void Tree::Sculpture::buildTrunk(ISculptor &sculptor, glm::ivec3 pos, int bottomZ, int topZ)
{
    for (pos.z=bottomZ; pos.z<=topZ; pos.z++)
    {
        sculptor.place(pos, logBlock, metaNaturalLogMask);
    }
}

void Tree::Sculpture::build(ISculptor &sculptor, float radiusRel, float heightRel, glm::ivec3 pos)
{
    int radius = minRadius + radiusRel * (maxRadius - minRadius);
    int height = minHeight + heightRel * (maxHeight - minHeight);

    height += radius;

    int top = pos.z + height - 1;
    buildLeaves(sculptor, glm::ivec3(pos.x, pos.y, top), radius);
    buildTrunk(sculptor, pos, pos.z, top);
}

Tree::Sculpture::Sculpture(
    int minHeight,
    int maxHeight,
    const char *logBlock,
    int minRadius,
    int maxRadius,
    const char *leafBlock
) :
    minHeight(minHeight),
    maxHeight(maxHeight),
    logBlock(logBlock),
    minRadius(minRadius),
    maxRadius(maxRadius),
    leafBlock(leafBlock)
{}

Tree::Sculpture Tree::oak = Tree::Sculpture(
    3,                              // minHeight
    13,                             // maxHeight
    "main:oakLog",                  // logBlock
    2,                              // minRadius
    4                               // maxRadius
);

Tree::Sculpture Tree::birch = Tree::Sculpture(
    10,                             // minHeight
    25,                             // maxHeight
    "main:birchLog",                // logBlock
    2,                              // minRadius
    4                               // maxRadius
);

Tree::Sculpture Tree::rubber = Tree::Sculpture(
    7,                              // minHeight
    17,                             // maxHeight
    "main:rubberLog",               // logBlock
    2,                              // minRadius
    4                               // maxRadius
);

Tree::Sculpture Tree::cactus = Tree::Sculpture(
    1,                              // minHeight
    5,                              // maxHeight
    "main:cactusLog",               // logBlock
    0,                              // minRadius
    1,                              // maxRadius
    "main:air"                      // leafBlock
);

Tree::Sculpture Tree::maple = Tree::Sculpture(
    6,                              // minHeight
    12,                             // maxHeight
    "main:mapleLog",                // logBlock
    4,                              // minRadius
    6,                              // maxRadius
    "main:mapleLeaves"              // leafBlock
);

Tree::Tree(
    Weighter getCount,
    Sculpture &sculpture
) :
    getCount(getCount),
    sculpture(sculpture)
{}

void Tree::build(ISculptor &sculptor, GeneratorNode node, glm::ivec3 pos)
{
    sculpture.build(
        sculptor,
        unormFromBits(node.seed >> 32),
        unormFromBits(node[0]),
        pos
    );
}

double Tree::getAverageDensity(const TerrainGenerator::Cell &cell, GeneratorNode node)
{
    return getCount(cell, node);
}