#include "Entity.h"

#include <Globals.h>

#include "Game.h"
#include "Globals.h"

Entity::Entity(const EntityType* zType,
    Framework::Model3DData* model,
    Framework::Model3DTextur* texture,
    int id,
    Framework::Vec3<float> position,
    float maxMovementSpeed)
    : FactoryCraftModel(),
      id(id),
      zType(zType),
      playerControlled(0),
      maxMovementSpeed(maxMovementSpeed),
      lastFlags(0),
      timeSinceSync(0),
      speed(0, 0, 0)
{
    pos = position;
    setModelDaten(model);
    setModelTextur(texture);
    lastDirection = World::INSTANCE->zKamera()->getDirection();
    currentFrame.duration = 0;
    rend = 1;
}

Entity::~Entity() {}

void Entity::api(char* message)
{
    switch (message[0])
    {
    case 0:
        { // add movement frame
            if (!playerControlled)
            {
                MovementFrame frame;
                frame.direction.x = *(float*)(message += 1);
                frame.direction.y = *(float*)(message += 4);
                frame.direction.z = *(float*)(message += 4);
                frame.targetPosition.x = *(float*)(message += 4);
                frame.targetPosition.y = *(float*)(message += 4);
                frame.targetPosition.z = *(float*)(message += 4);
                frame.movementFlags = *(int*)(message += 4);
                frame.duration = *(double*)(message += 4);
                cs.lock();
                movements.add(frame);
                cs.unlock();
            }
            break;
        }
    case 1:
        { // position correction
            if (playerControlled)
            {
                timeSinceSync = 0;
                pos.x = *(float*)(message += 1);
                pos.y = *(float*)(message += 4);
                pos.z = *(float*)(message += 4);
                lastDirection = World::INSTANCE->zKamera()->getDirection();
                lastFlags = 0;
            }
            break;
        }
    }
}

bool Entity::tick(double time)
{
    if (playerControlled && GetForegroundWindow() == window->getFensterHandle())
    {
        Vec3<float> direction = World::INSTANCE->zKamera()->getDirection();
        Vec3<float> lastPos = pos;
        int flags = 0;
        speed = {0, 0, speed.z};
        if (GetKeyState('w') & 0x8000 || GetKeyState('W') & 0x8000)
        {
            flags |= 1;
            speed += {direction.x, direction.y, 0};
        }
        if (GetKeyState('a') & 0x8000 || GetKeyState('A') & 0x8000)
        {
            flags |= 2;
            Vec2<float> norm = {direction.x, direction.y};
            norm.CCW90().normalize();
            speed += {norm.x, norm.y, 0};
        }
        if (GetKeyState('s') & 0x8000 || GetKeyState('S') & 0x8000)
        {
            flags |= 4;
            speed += {-direction.x, -direction.y, 0};
        }
        if (GetKeyState('d') & 0x8000 || GetKeyState('D') & 0x8000)
        {
            flags |= 8;
            Vec2<float> norm = {direction.x, direction.y};
            norm.CW90().normalize();
            speed += {norm.x, norm.y, 0};
        }
        if (GetKeyState(T_Shift) & 0x8000)
        {
            flags |= 16;
            speed.z = -maxMovementSpeed;
        }
        else if (GetKeyState(T_Space) & 0x8000)
        {
            flags |= 32;
            speed.z = maxMovementSpeed;
        }
        else
        {
            speed.z = 0.f;
        }
        Vec2<float> norm = {speed.x, speed.y};
        if (norm.getLengthSq() != 0)
        {
            norm.normalize();
            speed.x = norm.x * maxMovementSpeed;
            speed.y = norm.y * maxMovementSpeed;
        }
        // collision checking
        Vec3<float> minP = model->getMinPos();
        Vec3<float> maxP = model->getMaxPos();
        Vec3<float> worldBoundingBox[8];
        worldBoundingBox[0] = applyWorldTransformation(minP);
        worldBoundingBox[1]
            = applyWorldTransformation({minP.x, minP.y, maxP.z});
        worldBoundingBox[2]
            = applyWorldTransformation({minP.x, maxP.y, minP.z});
        worldBoundingBox[3]
            = applyWorldTransformation({maxP.x, minP.y, minP.z});
        worldBoundingBox[4]
            = applyWorldTransformation({maxP.x, minP.y, maxP.z});
        worldBoundingBox[5]
            = applyWorldTransformation({maxP.x, maxP.y, minP.z});
        worldBoundingBox[6]
            = applyWorldTransformation({minP.x, maxP.y, maxP.z});
        worldBoundingBox[7] = applyWorldTransformation(maxP);
        Vec3<float> worldBoundingBoxFloor[8];
        for (int i = 0; i < 8; i++)
        {
            worldBoundingBoxFloor[i] = Vec3<float>(floor(worldBoundingBox[i].x),
                floor(worldBoundingBox[i].y),
                floor(worldBoundingBox[i].z));
        }
        Vec3<float> frameSpeed = speed * (float)time;
        bool hasCollided = 0;
        for(int m = 0; m < 20; m++)
        {
            float tf = 1.f;
            int collType = 0;
            int updateType = 0;
            int updateI = 0;
            if (frameSpeed.x > 0)
            {
                for (int i = 0; i < 8; i++)
                {
                    if (abs(frameSpeed.x) >= abs(worldBoundingBoxFloor[i].x
                                                 + 1.f - worldBoundingBox[i].x))
                    {
                        float xt = (worldBoundingBoxFloor[i].x + 1.f
                                       - worldBoundingBox[i].x)
                                 / frameSpeed.x;
                        Vec3<float> tmp = worldBoundingBox[i] + frameSpeed * xt;
                        if (tmp.y >= worldBoundingBoxFloor[i].y
                            && tmp.y < worldBoundingBoxFloor[i].y + 1.f
                            && tmp.z >= worldBoundingBoxFloor[i].z
                            && tmp.z < worldBoundingBoxFloor[i].z + 1.f)
                        {
                            Block* b = World::INSTANCE->zBlockAt(
                                Vec3<int>{(int)worldBoundingBoxFloor[i].x + 1,
                                    (int)worldBoundingBoxFloor[i].y,
                                    (int)worldBoundingBoxFloor[i].z});
                            if (b) // TODO: ignore passable blocks
                            {
                                if (xt < tf)
                                {
                                    tf = xt;
                                    collType = 1;
                                    updateType = 0;
                                }
                                hasCollided = 1;
                            }
                            else
                            {
                                if (xt < tf)
                                {
                                    tf = xt;
                                    collType = 0;
                                    updateType = 1;
                                    updateI = i;
                                }
                            }
                        }
                    }
                }
            }
            if (frameSpeed.x < 0)
            {
                for (int i = 0; i < 8; i++)
                {
                    if (abs(frameSpeed.x) >= abs(
                            worldBoundingBoxFloor[i].x - worldBoundingBox[i].x))
                    {
                        float xt = (worldBoundingBoxFloor[i].x
                                       - worldBoundingBox[i].x)
                                 / frameSpeed.x;
                        Vec3<float> tmp = worldBoundingBox[i] + frameSpeed * xt;
                        if (tmp.y >= worldBoundingBoxFloor[i].y
                            && tmp.y < worldBoundingBoxFloor[i].y + 1.f
                            && tmp.z >= worldBoundingBoxFloor[i].z
                            && tmp.z < worldBoundingBoxFloor[i].z + 1.f)
                        {
                            Block* b = World::INSTANCE->zBlockAt(
                                Vec3<int>{(int)worldBoundingBoxFloor[i].x - 1,
                                    (int)worldBoundingBoxFloor[i].y,
                                    (int)worldBoundingBoxFloor[i].z});
                            if (b) // TODO: ignore passable blocks
                            {
                                if (xt < tf)
                                {
                                    tf = xt;
                                    collType = 1;
                                    updateType = 0;
                                }
                                hasCollided = 1;
                            }
                            else
                            {
                                if (xt < tf)
                                {
                                    tf = xt;
                                    collType = 0;
                                    updateType = 1;
                                    updateI = i;
                                }
                            }
                        }
                    }
                }
            }
            if (frameSpeed.y > 0)
            {
                for (int i = 0; i < 8; i++)
                {
                    if (abs(frameSpeed.y) >= abs(worldBoundingBoxFloor[i].y
                                                 + 1.f - worldBoundingBox[i].y))
                    {
                        float yt = (worldBoundingBoxFloor[i].y + 1.f
                                       - worldBoundingBox[i].y)
                                 / frameSpeed.y;
                        Vec3<float> tmp = worldBoundingBox[i] + frameSpeed * yt;
                        if (tmp.x >= worldBoundingBoxFloor[i].x
                            && tmp.x < worldBoundingBoxFloor[i].x + 1.f
                            && tmp.z >= worldBoundingBoxFloor[i].z
                            && tmp.z < worldBoundingBoxFloor[i].z + 1.f)
                        {
                            Block* b = World::INSTANCE->zBlockAt(
                                Vec3<int>{(int)worldBoundingBoxFloor[i].x,
                                    (int)worldBoundingBoxFloor[i].y + 1,
                                    (int)worldBoundingBoxFloor[i].z});
                            if (b) // TODO: ignore passable blocks
                            {
                                if (yt < tf)
                                {
                                    tf = yt;
                                    collType = 2;
                                    updateType = 0;
                                }
                                hasCollided = 1;
                            }
                            else
                            {
                                if (yt < tf)
                                {
                                    tf = yt;
                                    collType = 0;
                                    updateType = 2;
                                    updateI = i;
                                }
                            }
                        }
                    }
                }
            }
            if (frameSpeed.y < 0)
            {
                for (int i = 0; i < 8; i++)
                {
                    if (abs(frameSpeed.y) >= abs(
                            worldBoundingBoxFloor[i].y - worldBoundingBox[i].y))
                    {
                        float yt = (worldBoundingBoxFloor[i].y
                                       - worldBoundingBox[i].y)
                                 / frameSpeed.y;
                        Vec3<float> tmp = worldBoundingBox[i] + frameSpeed * yt;
                        if (tmp.x >= worldBoundingBoxFloor[i].x
                            && tmp.x < worldBoundingBoxFloor[i].x + 1.f
                            && tmp.z >= worldBoundingBoxFloor[i].z
                            && tmp.z < worldBoundingBoxFloor[i].z + 1.f)
                        {
                            Block* b = World::INSTANCE->zBlockAt(
                                Vec3<int>{(int)worldBoundingBoxFloor[i].x,
                                    (int)worldBoundingBoxFloor[i].y - 1,
                                    (int)worldBoundingBoxFloor[i].z});
                            if (b) // TODO: ignore passable blocks
                            {
                                if (yt < tf)
                                {
                                    tf = yt;
                                    collType = 2;
                                    updateType = 0;
                                }
                                hasCollided = 1;
                            }
                            else
                            {
                                if (yt < tf)
                                {
                                    tf = yt;
                                    collType = 0;
                                    updateType = 2;
                                    updateI = i;
                                }
                            }
                        }
                    }
                }
            }
            if (frameSpeed.z > 0)
            {
                for (int i = 0; i < 8; i++)
                {
                    if (abs(frameSpeed.z) >= abs(worldBoundingBoxFloor[i].z
                                                 + 1.f - worldBoundingBox[i].z))
                    {
                        float zt = (worldBoundingBoxFloor[i].z + 1.f
                                       - worldBoundingBox[i].z)
                                 / frameSpeed.z;
                        Vec3<float> tmp = worldBoundingBox[i] + frameSpeed * zt;
                        if (zt <= 1.f && tmp.x >= worldBoundingBoxFloor[i].x
                            && tmp.x < worldBoundingBoxFloor[i].x + 1.f
                            && tmp.y >= worldBoundingBoxFloor[i].y
                            && tmp.y < worldBoundingBoxFloor[i].y + 1.f)
                        {
                            Block* b = World::INSTANCE->zBlockAt(
                                Vec3<int>{(int)worldBoundingBoxFloor[i].x,
                                    (int)worldBoundingBoxFloor[i].y,
                                    (int)worldBoundingBoxFloor[i].z + 1});
                            if (b) // TODO: ignore passable blocks
                            {
                                if (zt < tf)
                                {
                                    tf = zt;
                                    collType = 3;
                                }
                                hasCollided = 1;
                            }
                            else
                            {
                                if (zt < tf)
                                {
                                    tf = zt;
                                    collType = 0;
                                    updateType = 3;
                                    updateI = i;
                                }
                            }
                        }
                    }
                }
            }
            if (frameSpeed.z < 0)
            {
                for (int i = 0; i < 8; i++)
                {
                    if (abs(frameSpeed.z) >= abs(
                            worldBoundingBoxFloor[i].z - worldBoundingBox[i].z))
                    {
                        float zt = (worldBoundingBoxFloor[i].z
                                       - worldBoundingBox[i].z)
                                 / frameSpeed.z;
                        Vec3<float> tmp = worldBoundingBox[i] + frameSpeed * zt;
                        if (tmp.x >= worldBoundingBoxFloor[i].x
                            && tmp.x < worldBoundingBoxFloor[i].x + 1.f
                            && tmp.y >= worldBoundingBoxFloor[i].y
                            && tmp.y < worldBoundingBoxFloor[i].y + 1)
                        {
                            Block* b = World::INSTANCE->zBlockAt(
                                Vec3<int>{(int)worldBoundingBoxFloor[i].x,
                                    (int)worldBoundingBoxFloor[i].y,
                                    (int)worldBoundingBoxFloor[i].z - 1});
                            if (b) // TODO: ignore passable blocks
                            {
                                if (zt < tf)
                                {
                                    tf = zt;
                                    collType = 3;
                                    updateType = 0;
                                }
                                hasCollided = 1;
                            }
                            else
                            {
                                if (zt < tf)
                                {
                                    tf = zt;
                                    collType = 0;
                                    updateType = 3;
                                    updateI = i;
                                }
                            }
                        }
                    }
                }
            }
            if (collType == 1)
                frameSpeed.x = tf > 0.1f ? frameSpeed.x * (tf - 0.1f) : 0.f;
            if (collType == 2)
                frameSpeed.y = tf > 0.1f ? frameSpeed.y * (tf - 0.1f) : 0.f;
            if (collType == 3)
                frameSpeed.z = tf > 0.1f ? frameSpeed.z * (tf - 0.1f) : 0.f;
            if (updateType == 1)
            {
                if ((int)worldBoundingBoxFloor[updateI].x
                    <= (int)floor(worldBoundingBox[updateI].x + frameSpeed.x))
                    worldBoundingBoxFloor[updateI].x++;
                if ((int)worldBoundingBoxFloor[updateI].x
                    > (int)floor(worldBoundingBox[updateI].x + frameSpeed.x))
                    worldBoundingBoxFloor[updateI].x--;
            }
            if (updateType == 2)
            {
                if ((int)worldBoundingBoxFloor[updateI].y
                    <= (int)floor(worldBoundingBox[updateI].y + frameSpeed.y))
                    worldBoundingBoxFloor[updateI].y++;
                if ((int)worldBoundingBoxFloor[updateI].y
                    > (int)floor(worldBoundingBox[updateI].y + frameSpeed.y))
                    worldBoundingBoxFloor[updateI].y--;
            }
            if (updateType == 3)
            {
                if ((int)worldBoundingBoxFloor[updateI].z
                    <= (int)floor(worldBoundingBox[updateI].z + frameSpeed.z))
                    worldBoundingBoxFloor[updateI].z++;
                if ((int)worldBoundingBoxFloor[updateI].z
                    > (int)floor(worldBoundingBox[updateI].z + frameSpeed.z))
                    worldBoundingBoxFloor[updateI].z--;
            }
            if (updateType || collType) continue;
            break;
        }
        pos += frameSpeed;
        World::INSTANCE->zKamera()->setPosition(
            pos + Vec3<float>(0.f, 0.f, 1.5f));
        Model3D* target = World::INSTANCE->getCurrentTarget();
        Block* b = target ? dynamic_cast<Block*>(target) : 0;
        ((Game*)(Menu*)menuRegister->get("game"))
            ->updatePosition(
                pos, b != 0, b ? b->getLocation() : Vec3<int>(0, 0, 0));
        if (target) target->release();
        if (flags != lastFlags || direction != lastDirection
            || timeSinceSync >= 1 || hasCollided)
        {
            if (timeSinceSync > 0)
            {
                MovementFrame frame;
                frame.direction = lastDirection;
                frame.targetPosition = lastPos;
                frame.movementFlags = lastFlags;
                frame.duration = timeSinceSync;
                World::INSTANCE->zClient()->sendPlayerMovement(frame);
            }
            lastFlags = flags;
            lastDirection = direction;
            timeSinceSync = 0;
        }
        timeSinceSync += time;
        rend = 1;
    }
    else
    {
        double totalTime = time;
        while (totalTime > 0)
        {
            if (currentFrame.duration <= 0)
            {
                if (movements.getEintragAnzahl() > 0)
                {
                    currentFrame = movements.get(0);
                    cs.lock();
                    movements.remove(0);
                    cs.unlock();
                }
                else
                {
                    break;
                }
            }
            double t = min(currentFrame.duration, totalTime);
            pos += (currentFrame.targetPosition - pos)
                 * (float)(t / currentFrame.duration);
            currentFrame.duration -= t;
            totalTime -= t;
            if (currentFrame.duration <= 0)
            {
                pos = currentFrame.targetPosition;
            }
            rend = 1;
        }
    }
    return Model3D::tick(time);
}

int Entity::getId() const
{
    return id;
}

const EntityType* Entity::zEntityType() const
{
    return zType;
}

void Entity::lock()
{
    cs.lock();
}

void Entity::unlock()
{
    cs.unlock();
}

void Entity::setPlayerControlled()
{
    playerControlled = 1;
    World::INSTANCE->zKamera()->setPosition(pos + Vec3<float>(0.f, 0.f, 1.5f));
}