#include "ChunkGroundModel.h"

#include <Trie.h>

#include "Area.h"
#include "Block.h"
#include "Constants.h"
#include "FactoryCraftModel.h"
#include "Globals.h"

using namespace Framework;

ChunkGroundModel::ChunkGroundModel(FactoryCraftModel* target, Chunk* zChunk)
    : ChunkModelBuilder(target, zChunk, Chunk::CombinedModels::GROUND)
{}

__int64 ChunkGroundModel::calculateLight(Framework::Vec3<float> vertexPos,
    Framework::Vec3<int> blockPos,
    Direction direction)
{
    __int64 result = 0;
    int sumCount = 1;
    short lightSum[6];
    Block* current = blocks()[Chunk::index(blockPos)];
    const unsigned char* light = current->getLightData(direction);
    for (int i = 0; i < 6; i++)
    {
        lightSum[i] = (short)light[i];
    }
    Vec3<int> vertexDirs(vertexPos.x < 0 ? -1 : 1,
        vertexPos.y < 0 ? -1 : 1,
        vertexPos.z < 0 ? -1 : 1);
    Directions dirs = getDirectionsFromVector(vertexDirs) & ~direction;
    Vec3<int> neighborDirs[3];
    int neighborIndex = 0;
    for (int i = 0; i < 6; i++)
    {
        Direction dir = getDirectionFromIndex(i);
        if ((dirs | dir) == dirs)
        {
            neighborDirs[neighborIndex++] = getDirection(dir);
            if (neighborIndex == 2) break;
        }
    }
    neighborDirs[2] = neighborDirs[0] + neighborDirs[1];
    for (int i = 0; i < 3; i++)
    {
        neighborDirs[i] += blockPos;
        if (neighborDirs[i].x >= 0 && neighborDirs[i].y >= 0
            && neighborDirs[i].z >= 0 && neighborDirs[i].x < CHUNK_SIZE
            && neighborDirs[i].y < CHUNK_SIZE
            && neighborDirs[i].z < WORLD_HEIGHT)
        {
            int neighborIndex = Chunk::index(neighborDirs[i]);
            Block* neighbor = blocks()[neighborIndex];
            if (neighbor)
            {
                const unsigned char* neighborLight
                    = neighbor->getLightData(direction);
                if ((neighborLight[0] | neighborLight[1] | neighborLight[2]
                        | neighborLight[3] | neighborLight[4]
                        | neighborLight[5])
                    != 0)
                {
                    sumCount++;
                    for (int j = 0; j < 6; j++)
                    {
                        lightSum[j] += (short)neighborLight[j];
                    }
                }
            }
        }
        else
        { // TODO: get light from neighbor chunk
        }
    }
    for (int i = 0; i < 6; i++)
    {
        lightSum[i] = (lightSum[i] / sumCount) & 0xFF;
    }
    result = ((__int64)lightSum[0] << 24) | ((__int64)lightSum[1] << 16)
           | ((__int64)lightSum[2] << 8) | ((__int64)lightSum[3] << 56)
           | ((__int64)lightSum[4] << 48) | ((__int64)lightSum[5] << 40);
    return result;
}

bool ChunkGroundModel::isPartOfGroundModel(
    Framework::Vec3<int> location, int directionIndex)
{
    Framework::Vec3<int> neighborLocation
        = location + getDirection(getDirectionFromIndex(directionIndex));
    bool needed = 0;
    if (neighborLocation.x < 0 || neighborLocation.y < 0
        || neighborLocation.z < 0 || neighborLocation.x >= CHUNK_SIZE
        || neighborLocation.y >= CHUNK_SIZE
        || neighborLocation.z >= WORLD_HEIGHT)
    {
        needed = 1;
    }
    else
    {
        int naighborIndex = Chunk::index(neighborLocation);
        if (!blocks()[naighborIndex]
            || !blocks()[naighborIndex]
                    ->zBlockType()
                    ->getModelInfo()
                    .getModelName()
                    .istGleich("cube"))
        {
            needed = 1;
        }
    }
    return needed;
}

void ChunkGroundModel::buildModel()
{
    Model3DData* chunkModel = target->zModelData();
    // remove old model
    while (chunkModel->getPolygonAnzahl() > 0)
    {
        chunkModel->removePolygon(0);
    }
    // calculate verticies
    Trie<GroundModelPart*> groundModelBuidler;
    Array<GroundModelPart*> groundPartArray;
    Vertex3D* groundVerticies = new Vertex3D[10000];
    __int64* lightBuffer = new __int64[10000];
    int groundVertexCount = 0;
    int groundVertexArraySize = 10000;
    for (int i = 0; i < CHUNK_SIZE * CHUNK_SIZE * WORLD_HEIGHT; i++)
    {
        if (blocks()[i])
        {
            if (isPartOfModel(blocks()[i]))
            {
                setBlockPartOfModel(blocks()[i], 1);
                int index = 0;
                for (Text* textureName :
                    *blocks()[i]->zBlockType()->getModelInfo().getTexturNames())
                {
                    Framework::Vec3<int> location(
                        (i / WORLD_HEIGHT) / CHUNK_SIZE,
                        (i / WORLD_HEIGHT) % CHUNK_SIZE,
                        i % WORLD_HEIGHT);
                    if (isPartOfGroundModel(location, index))
                    {
                        if (!groundModelBuidler.get(
                                *textureName, textureName->getLength()))
                        {
                            GroundModelPart* part = new GroundModelPart();
                            part->indexList = new int[10000];
                            part->indexCount = 0;
                            part->indexArraySize = 10000;
                            part->name = *textureName;
                            groundModelBuidler.set(
                                *textureName, textureName->getLength(), part);
                            groundPartArray.add(part);
                        }
                        GroundModelPart* part = groundModelBuidler.get(
                            *textureName, textureName->getLength());
                        const Vertex3D* vBuffer
                            = blocks()[i]->zModelData()->zVertexBuffer();
                        Polygon3D* polygon
                            = blocks()[i]->zModelData()->getPolygon(index);
                        if (part->indexCount + polygon->indexAnz
                            > part->indexArraySize)
                        {
                            int* tmp = new int[part->indexArraySize + 10000];
                            memcpy(tmp, part->indexList, part->indexCount * 4);
                            delete[] part->indexList;
                            part->indexList = tmp;
                            part->indexArraySize += 10000;
                        }
                        if (groundVertexCount + polygon->indexAnz
                            > groundVertexArraySize)
                        {
                            Vertex3D* tmp
                                = new Vertex3D[groundVertexArraySize + 10000];
                            memcpy(tmp,
                                groundVerticies,
                                groundVertexCount * sizeof(Vertex3D));
                            delete[] groundVerticies;
                            groundVerticies = tmp;
                            groundVertexArraySize += 10000;
                            __int64* lTmp = new __int64[groundVertexArraySize];
                            memcpy(lTmp,
                                lightBuffer,
                                groundVertexCount * sizeof(__int64));
                            delete[] lightBuffer;
                            lightBuffer = lTmp;
                        }
                        for (int vi = 0; vi < polygon->indexAnz; vi++)
                        {
                            lightBuffer[groundVertexCount] = calculateLight(
                                vBuffer[polygon->indexList[vi]].pos,
                                location,
                                getDirectionFromIndex(index));
                            part->indexList[part->indexCount++]
                                = groundVertexCount;
                            groundVerticies[groundVertexCount++]
                                = vBuffer[polygon->indexList[vi]];
                            groundVerticies[groundVertexCount - 1].pos
                                += blocks()[i]->getPos()
                                 - Vec3<float>((float)chunkCenter().x,
                                     (float)chunkCenter().y,
                                     (float)WORLD_HEIGHT / 2.f);
                            groundVerticies[groundVertexCount - 1].id
                                = groundVertexCount - 1;
                        }
                    }
                    index++;
                }
            }
            else
            {
                setBlockPartOfModel(blocks()[i], 0);
            }
        }
    }
    Model3DTextur* textur = new Model3DTextur();
    int pi = 0;
    for (GroundModelPart* part : groundPartArray)
    {
        Polygon3D* polygon = new Polygon3D();
        polygon->indexAnz = part->indexCount;
        polygon->indexList = part->indexList;
        target->zModelData()->addPolygon(polygon);
        textur->setPolygonTextur(pi,
            uiFactory.initParam.bildschirm->zGraphicsApi()->createOrGetTextur(
                part->name));
        pi++;
        delete part;
    }
    target->zModelData()->setVertecies(groundVerticies, groundVertexCount);
    target->setModelTextur(textur);
    target->setVertexLightBuffer(lightBuffer, groundVertexCount);
}

bool ChunkGroundModel::updateLightning()
{
    __int64* lightBuffer = target->zLightBuffer();
    int groundVertexCount = 0;
    for (int i = 0; i < CHUNK_SIZE * CHUNK_SIZE * WORLD_HEIGHT; i++)
    {
        if (blocks()[i])
        {
            if (isPartOfModel(blocks()[i]))
            {
                int index = 0;
                for (Text* textureName :
                    *blocks()[i]->zBlockType()->getModelInfo().getTexturNames())
                {
                    Framework::Vec3<int> location(
                        (i / WORLD_HEIGHT) / CHUNK_SIZE,
                        (i / WORLD_HEIGHT) % CHUNK_SIZE,
                        i % WORLD_HEIGHT);
                    if (isPartOfGroundModel(location, index))
                    {
                        const Vertex3D* vBuffer
                            = blocks()[i]->zModelData()->zVertexBuffer();
                        Polygon3D* polygon
                            = blocks()[i]->zModelData()->getPolygon(index);
                        for (int vi = 0; vi < polygon->indexAnz; vi++)
                        {
                            lightBuffer[groundVertexCount++] = calculateLight(
                                vBuffer[polygon->indexList[vi]].pos,
                                location,
                                getDirectionFromIndex(index));
                        }
                    }
                    index++;
                }
            }
        }
    }
    target->copyLightToGPU();
    return 1;
}

bool ChunkGroundModel::isTransparent() const
{
    return false;
}

bool ChunkGroundModel::isPartOfModel(Block* zBlock) const
{
    return zBlock->zBlockType()->getModelInfo().getModelName().istGleich(
        "cube");
}