#include "Block.h"

#include <Shader.h>
#include <Texture.h>

#include "CustomDX11API.h"
#include "Globals.h"

Block::Block(const BlockType* zType,
    Framework::Vec3<int> pos,
    Model3DData* model,
    Model3DTexture* texture,
    int maxHP,
    bool transparent,
    bool needRequestModelInfo,
    float size,
    bool passable,
    float speedModifier,
    Direction frontDirection)
    : FactoryCraftModel(),
      zType(zType),
      location(pos),
      maxHP((float)maxHP),
      needRequestModelInfo(needRequestModelInfo),
      partOfModel(0),
      flowOptions(0),
      distanceToSource(0),
      passable(passable),
      speedModifier(speedModifier),
      currentModelInfo(0),
      frontDirection(frontDirection)
{
    hp = (float)maxHP;
    memset(sideVisible, 0, 6);
    // Model3D::setAlpha(transparent);
    Model3D::setPosition(
        (Framework::Vec3<float>)pos + Framework::Vec3<float>{0.5f, 0.5f, 0.5f});
    setModelData(model);
    setModelTextur(texture);
    memset(lightData, 0, 6 * 6);
    setSize(size);
    Model3D::setRotation(::getRotation(NORTH, frontDirection));
}

Block::~Block()
{
    if (currentModelInfo)
    {
        delete currentModelInfo;
    }
}

void Block::beforeRender(
    GraphicsApi* api, Shader* zVertexShader, Shader* zPixelShader)
{
    if (needRequestModelInfo)
    {
        needRequestModelInfo = 0;
        World::INSTANCE->zClient()->blockAPIRequest(location, "\0", 1);
    }
    FactoryCraftModel::beforeRender(api, zVertexShader, zPixelShader);
}

void Block::setFlow(char flowOptions, char distanceToSource)
{
    this->flowOptions = flowOptions;
    this->distanceToSource = distanceToSource;
}

void Block::api(char* message)
{
    switch (message[0])
    {
    case 0: // hp change
        {
            hp = *(float*)(message + 1);
            setDestroyedState(1 - hp / maxHP);
            Model3D* mdl = World::INSTANCE->getCurrentTarget();
            if (mdl == this)
            {
                if (partOfModel)
                {
                    World::INSTANCE->zSelectedEffectModel()->setDestroyedState(
                        1 - hp / maxHP);
                }
            }
            if (mdl) mdl->release();
            break;
        }
    case 1: // model change
        {
            ModelInfo* old = currentModelInfo;
            ByteArrayReader reader(message + 1, 10000, 0);
            currentModelInfo = new ModelInfo(&reader);
            setModelData(currentModelInfo->getModel());
            setSize(currentModelInfo->getSize());
            setModelTextur(currentModelInfo->getTexture());
            Chunk* zChunk = World::INSTANCE->zChunk(
                World::INSTANCE->getChunkCenter(location.x, location.y));
            if (zChunk)
            {
                zChunk->setModelChanged(partOfModel);
            }
            if (old)
            {
                delete old;
            }
            break;
        }
    case 2: // update fluid state
        {
            flowOptions = message[1];
            distanceToSource = message[2];
            Chunk* zChunk = World::INSTANCE->zChunk(
                World::INSTANCE->getChunkCenter(location.x, location.y));
            if (zChunk)
            {
                zChunk->setModelChanged(partOfModel);
            }
        }
    case 3: // update passable and speed modifier
        {
            passable = message[1];
            speedModifier = *(float*)(message + 2);
            break;
        }
    }
}

void Block::copyLightTo(Block* zB)
{
    memcpy(zB->lightData, lightData, 6 * 6);
    memcpy(zB->sideVisible, sideVisible, 6);
}

void Block::setLightData(Direction dir, unsigned char* data, Chunk* zC)
{
    memcpy(lightData + getDirectionIndex(dir) * 6, data, 6);
    if (data[0] | data[1] | data[2] | data[3] | data[4] | data[5])
        sideVisible[getDirectionIndex(dir)] = 1;
    else
        sideVisible[getDirectionIndex(dir)] = 0;
    if (zC) zC->setLightChanged(partOfModel);
    int sum1 = 0;
    int sum2 = 0;
    int sum3 = 0;
    for (int i = 0; i < 6; i++)
    {
        sum1 += *(lightData + i * 6 + 3);
        sum2 += *(lightData + i * 6 + 4);
        sum3 += *(lightData + i * 6 + 5);
    }
    unsigned char average[3];
    average[0] = (unsigned char)(sum1 / 6);
    average[1] = (unsigned char)(sum2 / 6);
    average[2] = (unsigned char)(sum3 / 6);
    setAverageLight(average);
}

void Block::setPartOfModel(int type, bool part)
{
    if (part)
        this->partOfModel |= type;
    else
        this->partOfModel &= ~type;
}

const unsigned char* Block::getLightData(Direction dir) const
{
    return lightData + getDirectionIndex(dir) * 6;
}

__int64 Block::getMaxLight() const
{
    unsigned char max[6];
    max[0] = max[1] = max[2] = max[3] = max[4] = max[5] = 0;
    for (int i = 0; i < 6; i++)
    {
        for (int j = 0; j < 6; j++)
        {
            if (lightData[i * 6 + j] > max[j]) max[j] = lightData[i * 6 + j];
        }
    }
    return ((__int64)max[0] << 24) | ((__int64)max[1] << 16)
         | ((__int64)max[2] << 8) | ((__int64)max[3] << 56)
         | ((__int64)max[4] << 48) | ((__int64)max[5] << 40);
}

bool Block::isVisible() const
{
    return true;
    // return sideVisible[0] || sideVisible[1] || sideVisible[2] ||
    // sideVisible[3]
    //     || sideVisible[4] || sideVisible[5];
}

Direction Block::getFrontDirection() const
{
    return frontDirection;
}

Vec3<int> Block::getLocation() const
{
    return location;
}

const BlockType* Block::zBlockType() const
{
    return zType;
}

Skeleton* Block::zSkeleton() const
{
    return skelett;
}

Text Block::printLightInfo()
{
    Text result = "NORTH[0;-1;0](";
    result.append() << (int)lightData[0] << "," << (int)lightData[1] << ","
                    << (int)lightData[2] << ";" << (int)lightData[3] << ","
                    << (int)lightData[4] << "," << (int)lightData[5] << ")\n"
                    << "EAST[1;0;0](" << (int)lightData[6] << ","
                    << (int)lightData[7] << "," << (int)lightData[8] << ";"
                    << (int)lightData[9] << "," << (int)lightData[10] << ","
                    << (int)lightData[11] << ")\n"
                    << "SOUTH[0;1;0](" << (int)lightData[12] << ","
                    << (int)lightData[13] << "," << (int)lightData[14] << ";"
                    << (int)lightData[15] << "," << (int)lightData[16] << ","
                    << (int)lightData[17] << ")\n"
                    << "WEST[-1;0;0](" << (int)lightData[18] << ","
                    << (int)lightData[19] << "," << (int)lightData[20] << ";"
                    << (int)lightData[21] << "," << (int)lightData[22] << ","
                    << (int)lightData[23] << ")\n"
                    << "TOP[0;0;1](" << (int)lightData[24] << ","
                    << (int)lightData[25] << "," << (int)lightData[26] << ";"
                    << (int)lightData[27] << "," << (int)lightData[28] << ","
                    << (int)lightData[29] << ")\n"
                    << "BOTTOM[0;0;-1](" << (int)lightData[30] << ","
                    << (int)lightData[31] << "," << (int)lightData[32] << ";"
                    << (int)lightData[33] << "," << (int)lightData[34] << ","
                    << (int)lightData[35] << ")\n";
    switch (frontDirection)
    {
    case NORTH:
        result += "Front: NORTH\n";
        break;
    case EAST:
        result += "Front: EAST\n";
        break;
    case SOUTH:
        result += "Front: SOUTH\n";
        break;
    case WEST:
        result += "Front: WEST\n";
        break;
    case TOP:
        result += "Front: TOP\n";
        break;
    case BOTTOM:
        result += "Front: BOTTOM\n";
        break;
    }
    return result;
}

int Block::getPartOfModels() const
{
    return partOfModel;
}

char Block::getFlowOptions() const
{
    return flowOptions;
}

char Block::getDistanceToSource() const
{
    return distanceToSource;
}

const ModelInfo& Block::getCurrentModelInfo() const
{
    return currentModelInfo ? *currentModelInfo : zType->getModelInfo();
}
