#include "Enemy.h"

void drawEnemies()
{
    u16 i = 0;
    u8 cell = 0;
    Enemy* enemy = mapEnemies;

    // Draw each of the enemies where I determine if the enemies are actually on screen.
    for (i = 0; i < mapEnemyCount; ++i)
    {
        // Enemy redraw should only be set for enemies that are in the current room so we won't check the current room.
        // Enemy should only be redrawn if they are not dead.
        if (enemy->redraw)
        {
            drawEnemy(enemy);

            enemy->redraw = FALSE;
        }

        ++enemy;
    }
}

void drawEnemy(Enemy* enemy)
{
    drawActor(&enemy->object, enemyGraphics[enemy->type - 1]);
}

void initEnemies(u8* mapEnemyArray, u8 arraySize)
{
    u8 i = 0;
    Enemy* enemy = mapEnemies;
    u8 roomX = 0;
    u8 roomY = 0;

    for (i = 0; i < arraySize; i += 3)
    {
        u8 x = mapEnemyArray[i];
        u8 y = mapEnemyArray[i + 1];
        u8 roomX = x / SCREEN_TILES_X;
        u8 roomY = y / SCREEN_TILES_Y;

        enemy->object.position.room = roomX + roomY * 3;
        enemy->object.position.x = (x % SCREEN_TILES_X) * TILE_WIDTH_BYTES;
        enemy->object.position.y = (y % SCREEN_TILES_Y) * TILE_HEIGHT;
        enemy->object.previousPosition.x = enemy->object.position.x;
        enemy->object.previousPosition.y = enemy->object.position.y;
        enemy->type = mapEnemyArray[i + 2];

        // Different enemies have different health values requiring more or less shots to destroy.
        if (enemy->type == ENEMY_TYPE_OCTOPUS)
        {
            enemy->health = 4;
        }
        else if (enemy->type == ENEMY_TYPE_SNAIL)
        {
            enemy->health = 2;
        }
        else
        {
            enemy->health = 3;
        }
        
        enemy->object.width = TILE_WIDTH_BYTES;
        enemy->object.height = TILE_HEIGHT;
        enemy->stunned = FALSE;
        enemy->collision = FALSE;
        enemy->logicFlag = 15;

        // ENEMY_TYPE_LAND_BUG Vertical up down air enemies.
        // ENEMY_TYPE_BUG Vertical up down water enemies.
        if (enemy->type == ENEMY_TYPE_LAND_BUG || enemy->type == ENEMY_TYPE_BUG)
        {
            enemy->object.velocity.x = 0;
            enemy->object.velocity.y = 2;
        }
        else if (enemy->type == ENEMY_TYPE_FISH ||
                 enemy->type == ENEMY_TYPE_CRAB ||
                 enemy->type == ENEMY_TYPE_MOTH ||
                 enemy->type == ENEMY_TYPE_SNAIL ||
                 enemy->type == ENEMY_TYPE_SNAKE ||
                 enemy->type == ENEMY_TYPE_SCORPION)
        {
            enemy->object.velocity.y = 0;
            enemy->object.velocity.x = 1;
        }

        ++enemy;
    }
}

void logicEnemies(u8 playerX, u8 playerY)
{
    u8 i = 0;
    Enemy* enemy = mapEnemies;

    u8 futureXTileLeft;
    u8 futureXTileRight;
    u8 futureYTileBottom;
    u8 futureYTileTop;

    for (i = 0; i < mapEnemyCount; ++i)
    {
        // Only run through enemy logic on enemies that are alive and on screen.
        if (enemy->health && enemy->object.position.room == currentRoom)
        {
            u8 cellsClear = 0;
            enemy->logicFlag++;

            // This controls the speed that enemies move at.
            if (enemy->type == ENEMY_TYPE_SNAIL)
            {
                if (enemy->logicFlag < 5)
                {
                    // Move to the next enemy before exiting the logic for this enemy.
                    goto nextEnemy;
                }
                else
                {
                    enemy->logicFlag = 0;
                }
            }
            else if (enemy->type == ENEMY_TYPE_OCTOPUS)
            {
                if (enemy->logicFlag < 3)
                {
                    // Move to the next enemy before exiting the logic for this enemy.
                    goto nextEnemy;
                }
                else
                {
                    enemy->logicFlag = 0;
                }
            }
            else if (enemy->type == ENEMY_TYPE_SNAKE || enemy->type == ENEMY_TYPE_FISH)
            {
                if (enemy->logicFlag < 2)
                {
                    // Move to the next enemy before exiting the logic for this enemy.
                    goto nextEnemy;
                }
                else
                {
                    enemy->logicFlag = 0;
                }
            }

            // Enemy type ENEMY_TYPE_OCTOPUS follows the player around.
            if (enemy->type == ENEMY_TYPE_OCTOPUS && !enemy->stunned)
            {
                if (playerX > enemy->object.position.x + 8)
                {
                    enemy->object.velocity.x = 1;
                }
                else if (playerY > enemy->object.position.y + 6)
                {
                    enemy->object.velocity.y = 1;
                }
                else if (playerX < enemy->object.position.x - 8)
                {
                    enemy->object.velocity.x = -1;
                }
                else if (playerY < enemy->object.position.y - 6)
                {
                    enemy->object.velocity.y = -1;
                }
            }

            // While an enemy is stunned and the collision timer is active the enemy is fixed.
            if (enemy->stunned)
            {
                enemy->redraw = TRUE;

                if (collisionActive == FALSE)
                {
                    enemy->stunned = FALSE;

                    if (enemy->type == ENEMY_TYPE_LAND_BUG || enemy->type == ENEMY_TYPE_BUG)
                    {
                        enemy->object.velocity.y = 2;
                    }
                    else if (enemy->type == ENEMY_TYPE_FISH ||
                             enemy->type == ENEMY_TYPE_CRAB ||
                             enemy->type == ENEMY_TYPE_MOTH ||
                             enemy->type == ENEMY_TYPE_SNAIL ||
                             enemy->type == ENEMY_TYPE_SNAKE ||
                             enemy->type == ENEMY_TYPE_SCORPION)
                    {
                        enemy->object.velocity.x = 1;
                    }
                }
                else
                {
                    // Move onto the next enemy.
                    goto nextEnemy;
                }
            }

            // if +1 is clear update the position.
            // The positions that the enemy could occupy.
            futureXTileLeft = (enemy->object.position.x + enemy->object.velocity.x) >> 2;
            futureXTileRight = (enemy->object.position.x + TILE_WIDTH_BYTES - 1 + enemy->object.velocity.x);
            futureYTileBottom = (enemy->object.position.y + TILE_HEIGHT - 1 + enemy->object.velocity.y);
            futureYTileTop = (enemy->object.position.y + enemy->object.velocity.y) >> 4;
            futureXTileRight >>= 2;
            futureYTileBottom >>= 4;

            // Check whether all the position the enemy will occupy is clear or not.
            cellsClear += processEnemyCell(futureXTileLeft, futureYTileTop, enemy->type);
            cellsClear += processEnemyCell(futureXTileLeft, futureYTileBottom, enemy->type);
            cellsClear += processEnemyCell(futureXTileRight, futureYTileTop, enemy->type);
            cellsClear += processEnemyCell(futureXTileRight, futureYTileBottom, enemy->type);

            // Check if the cell below the enemy (horizontal ground based enemy) is solid or not.
            // TODO consider adding crab here.
            if (enemy->type == ENEMY_TYPE_SNAIL || enemy->type == ENEMY_TYPE_SNAKE || enemy->type == ENEMY_TYPE_SCORPION)
            {
                u8 belowCellLeft = MAPA(futureXTileLeft, futureYTileBottom + 1);
                u8 belowCellRight = MAPA(futureXTileRight, futureYTileBottom + 1);

                // Make sure that the cells underneath the enemy are solid (not empty or water).
                // Making sure both left and right are solid means the enemy will not go partially over empty tiles.
                if (belowCellLeft == CELL_EMPTY || belowCellLeft & CELL_WATER_FLAG || belowCellRight == CELL_EMPTY || belowCellRight & CELL_WATER_FLAG)
                {
                    cellsClear = 1;
                }
            }

            // If all cells are clear update the position using the velocity and exit the loop.
            if (!cellsClear)
            {
                // Update the previous position before updating the current position with the velocity.
                enemy->object.previousPosition.y = enemy->object.position.y;
                enemy->object.previousPosition.x = enemy->object.position.x;
                enemy->object.position.y += enemy->object.velocity.y;
                enemy->object.position.x += enemy->object.velocity.x;
                enemy->redraw = TRUE;
            }
            else
            {
                // Enemy is blocked so change direction.
                enemy->object.velocity.x *= -1;
                enemy->object.velocity.y *= -1;
            }
        }

        nextEnemy:

        // Move onto the next enemy.
        ++enemy;
    }

    // Check enemy collisions after the enemies have moved.
    checkEnemyCollisions();
}

u8 processEnemyCell(u8 x, u8 y, u8 enemyType)
{
    u8 cell = MAPA(x, y);

    // Make sure the enemy does not go out of bounds on the x axis (right screen edge).
    if (x >= SCREEN_TILES_X)
    {
        return 1;
    }

    // Make sure the enemy does not go out of bound on the y axis (y = -1 would be 255).
    if (y >= SCREEN_TILES_Y)
    {
        return 1;
    }

    if (enemyType == ENEMY_TYPE_MOTH ||
        enemyType == ENEMY_TYPE_SNAIL ||
        enemyType == ENEMY_TYPE_SNAKE ||
        enemyType == ENEMY_TYPE_SCORPION ||
        enemyType == ENEMY_TYPE_LAND_BUG)
    {
        // Land enemy cannot move in water.
        if (cell & CELL_WATER_FLAG)
        {
            return 1;
        }

        return MAPA(x, y);
    }
    else
    {
        // Water enemy cannot move without water.
        if (cell & CELL_WATER_FLAG)
        {
            return 0;
        }
        else
        {
            return 1;
        }
    }
}

void damageEnemy(Enemy* enemy)
{
    --enemy->health;

    if (!enemy->health)
    {
        // Enemy no longer has health so destroy it.
        destroyEnemy(enemy);

        // Add 300 points for destroying an enemy.
        levelScore += 30;
    }
}

void destroyEnemy(Enemy* enemy)
{
    u8 cellX = enemy->object.position.x / TILE_WIDTH_BYTES;
    u8 cellY = enemy->object.position.y / TILE_HEIGHT;
    backbufferWidth = cellX == 19 ? TILE_WIDTH_BYTES : BACKBUFFER_WIDTH;

    // Draw over where the enemy was using the backbuffer.
    populateBackbufferFromMap(cellX, cellY);
    drawBackbuffer(cellX, cellY);

    // One enemy is dead it should never be redrawn.
    enemy->redraw = FALSE;

    // Move enemy off screen where it can never be drawn.
    // Then the player does not need to do a health check for enemies.
    enemy->object.position.room = 13;
}

void checkEnemyCollisions()
{
    u8 i = 0;
    u8 j = 0;
    Enemy* enemy = mapEnemies;

    for (i = 0; i < mapEnemyCount; ++i)
    {
        // Make sure the current enemy is alive and in the current room.
        if (enemy->health && enemy->object.position.room == currentRoom)
        {
            // Iterate through the remaining enemies and check collision.
            for (j = i + 1; j < mapEnemyCount; ++j)
            {
                Enemy* otherEnemy = mapEnemies + j;

                // Test if the other enemy is alive, is on screen and is near to colliding with the current enemy (based on current position).
                if (otherEnemy->health && otherEnemy->object.position.room == currentRoom)
                {
                    if (difference(enemy->object.position.x, otherEnemy->object.position.x) < 8 && 
                        difference(enemy->object.position.y, otherEnemy->object.position.y) < 32)
                    {
                        enemy->collision = TRUE;
                        otherEnemy->collision = TRUE;
                    }
                }
            }
        }

        ++enemy;
    }
}

void drawColidedEnemies()
{
    u8 i = 0;
    Enemy* enemy = mapEnemies;

    for (i = 0; i < mapEnemyCount; ++i)
    {
        if (enemy->object.position.room == currentRoom && enemy->collision)
        {
            // Draw an enemy from the graphicset.
            u8* memory = cpct_getScreenPtr(CPCT_VMEM_START, enemy->object.position.x, enemy->object.position.y);
            cpct_drawSprite(enemyGraphics[enemy->type - 1], memory, TILE_WIDTH_BYTES, TILE_HEIGHT);

            enemy->collision = FALSE;
        }

        ++enemy;
    }
}