#include "TransparentChunkGroundModel.h"

#include <Trie.h>

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

using namespace Framework;

TransparentChunkGroundModel::TransparentChunkGroundModel(
    FactoryCraftModel* target, Chunk* zChunk)
    : ChunkModelBuilder(
          target, zChunk, Chunk::CombinedModels::TRANSPARENT_GROUND)
{}

__int64 TransparentChunkGroundModel::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;
}

void TransparentChunkGroundModel::buildModel()
{
    Model3DData* chunkModel = target->zModelData();
    // remove old model
    while (chunkModel->getPolygonCount() > 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);
                __int64 light = blocks()[i]->getMaxLight();
                int index = 0;
                for (Text* textureName :
                    *blocks()[i]->getCurrentModelInfo().getTexturNames())
                {
                    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] = light;
                        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);
            }
        }
    }
    Model3DTexture* texture = new Model3DTexture();
    int pi = 0;
    for (GroundModelPart* part : groundPartArray)
    {
        Polygon3D* polygon = new Polygon3D();
        polygon->indexAnz = part->indexCount;
        polygon->indexList = part->indexList;
        target->zModelData()->addPolygon(polygon);
        texture->setPolygonTexture(pi,
            uiFactory.initParam.bildschirm->zGraphicsApi()->createOrGetTexture(
                part->name));
        pi++;
        delete part;
    }
    target->zModelData()->setVertecies(groundVerticies, groundVertexCount);
    target->setModelTextur(texture);
    target->setVertexLightBuffer(lightBuffer, groundVertexCount);
}

bool TransparentChunkGroundModel::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]))
            {
                __int64 light = blocks()[i]->getMaxLight();
                int index = 0;
                for (Text* textureName :
                    *blocks()[i]->getCurrentModelInfo().getTexturNames())
                {
                    const Vertex3D* vBuffer
                        = blocks()[i]->zModelData()->zVertexBuffer();
                    Polygon3D* polygon
                        = blocks()[i]->zModelData()->getPolygon(index);
                    for (int vi = 0; vi < polygon->indexAnz; vi++)
                    {
                        lightBuffer[groundVertexCount++] = light;
                    }
                }
            }
        }
    }
    target->copyLightToGPU();
    return 1;
}

bool TransparentChunkGroundModel::isTransparent() const
{
    return true;
}

bool TransparentChunkGroundModel::isPartOfModel(Block* zBlock) const
{
    return zBlock->getCurrentModelInfo().getModelName().isEqual("grass");
}
