/*
    SPDX-FileCopyrightText: 2009 Ian Wadham <iandw.au@gmail.com>

    SPDX-License-Identifier: GPL-2.0-or-later
*/

#include "kgrlevelgrid.h"

KGrLevelGrid::KGrLevelGrid (QObject * parent, const KGrRecording * theLevelData)
    :
    QObject     (parent)
{
    // Put a concrete wall all round the layout: left, right, top and bottom.
    // This saves ever having to test for being at the edge of the layout.
    int inWidth      = theLevelData->width;
    width            = inWidth + ConcreteWall * 2;

    int inHeight     = theLevelData->height;
    height           = inHeight + ConcreteWall * 2;

    int size         = width * height;

    layout.fill      (CONCRETE, size);

    // Initialise the flags for each cell.
    heroAccess.fill  (0, size);
    enemyAccess.fill (0, size);
    enemyHere.fill   (-1, size);

    // Copy the cells of the layout, but enclosed within the concrete wall.
    int inRow  = 0;
    int outRow = width + ConcreteWall;

    for (int j = 0; j < inHeight; j++) {
        for (int i = 0; i < inWidth; i++) {
            char type = theLevelData->layout [inRow + i];
            switch (type) {
            case HLADDER:
                // Change hidden ladders to FREE, but keep a list of them.
                hiddenLadders.append (outRow + i);
                type = FREE;
                break;
            case HENEMY:
                // Change hidden enemies to BRICK, but keep a list of them.
                hiddenEnemies.append (outRow + i);
                type = BRICK;
                break;
            case FLASHING:
                // Change flashing nuggets to NUGGET, but keep a list of them.
                flashingGold.append (outRow + i);
                type = NUGGET;
                break;
            }
            layout [outRow + i] = type;
        }
        inRow  = inRow  + inWidth;
        outRow = outRow + width;
    }
}

KGrLevelGrid::~KGrLevelGrid()
{
}

// Inline functions (see kgrlevelgrid.h).
//     char cellType   (int i, int j)
//     char heroMoves  (int i, int j)
//     char enemyMoves (int i, int j)
//     void gotGold    (const int i, const int j, const bool runnerHasGold)
//
//     void setEnemyOccupied (int i, int j, const int spriteId)
//     int enemyOccupied (int i, int j)
//
//     int  index      (int i, int j)

void KGrLevelGrid::calculateAccess (bool pRunThruHole)
{
    runThruHole = pRunThruHole;		// Save a copy of the runThruHole rule.

    char here;
    bool canEnter;

    // Calculate which cells can be entered (N.B. USEDHOLE is a trapped enemy).
    for (int j = 1; j < height - 1; j++) {
        for (int i = 1; i < width - 1; i++) {
            here      = cellType (i, j);
            canEnter  = (here != BRICK)  && (here != CONCRETE) &&
                        (here != FBRICK) && (here != USEDHOLE);
            heroAccess  [index (i, j)] = canEnter ? ENTERABLE : 0;
            enemyAccess [index (i, j)] = canEnter ? ENTERABLE : 0;
        }
    }

    // Calculate the access *from* each cell to its neighbours.
    for (int j = 1; j < height - 1; j++) {
        for (int i = 1; i < width - 1; i++) {
            calculateCellAccess (i, j);
        }
    }
}

void KGrLevelGrid::changeCellAt (const int i, const int j, const char type)
{
    int  position          = index (i, j);
    bool canEnter          = (type != BRICK) && (type != CONCRETE) &&
                             (type != FBRICK) && (type != USEDHOLE);
    layout      [position] = type;
    heroAccess  [position] = canEnter ? ENTERABLE : 0;
    enemyAccess [position] = canEnter ? ENTERABLE : 0;

    calculateCellAccess (i, j);		// Recalculate access *from* this cell
					// and access *to* it
    calculateCellAccess (i, j - 1);	// from above,
    calculateCellAccess (i - 1, j);	// from left,
    calculateCellAccess (i + 1, j);	// from right,
    calculateCellAccess (i, j + 1);	// and from below.
}

void KGrLevelGrid::calculateCellAccess (const int i, const int j)
{
    Flags access = 0;
    char  here   = cellType (i, j);
    if (here == CONCRETE) {
        // If edge-cell or other CONCRETE, no calculation (avoid index errors).
        return;
    }
    char  below  = cellType (i, j + 1);

    access = heroMoves (i, j) & ENTERABLE;

    // Cannot enter brick, concrete or used hole: can drop into a false brick.
    if (! (access & ENTERABLE) && (here != FBRICK)) {
	access = 0;
    }
    // If can stand or hang on anything, allow down, left and right.
    else if ((below == BRICK) || (below == CONCRETE) || (below == USEDHOLE) ||
	(below == LADDER) || (here == LADDER) || (here == BAR)) {
	access |= (dFlag [STAND] | dFlag [DOWN] |
		   dFlag [LEFT]  | dFlag [RIGHT]);
    }
    // If cannot stand or hang, can go down (space or false brick) or
    // maybe left or right (when standing on an enemy).
    else {
	access |= (dFlag [DOWN] | dFlag [LEFT]  | dFlag [RIGHT]);
    }
    // Can only go up if there is a ladder here.
    if (here == LADDER) {
	access |= dFlag [UP];
    }

    // Mask out directions that are blocked above, below, L or R, but not for
    // concrete/brick at edge of grid (or elsewhere) to avoid indexing errors.
    if (access != 0) {
        if (! (heroMoves (i, j - 1) & ENTERABLE)) {
            access = ~dFlag [UP] & access;		// Cannot go up.
        }
        if (! (heroMoves (i - 1, j) & ENTERABLE)) {
            access = ~dFlag [LEFT] & access;		// Cannot go left.
        }
        if (! (heroMoves (i + 1, j) & ENTERABLE)) {
            access = ~dFlag [RIGHT] & access;		// Cannot go right.
        }
        if (! (heroMoves (i, j + 1) & ENTERABLE)) {
            if (below != FBRICK) {
                access = ~dFlag [DOWN] & access;	// Cannot go down.
            }
        }
    }

    heroAccess [index (i, j)]  = access;
    
    // Enemy access is the same as the hero's when no holes are open.
    enemyAccess [index (i, j)] = heroAccess [index (i, j)];

    if (here == USEDHOLE) {
        enemyAccess [index (i, j)] = UP;	// Can only climb out of hole.
    }
    else if (! runThruHole) {			// Check the rule.
        char mask;
        mask = (cellType (i - 1, j) == HOLE) ? dFlag [LEFT] : 0;
        mask = (cellType (i + 1, j) == HOLE) ? (dFlag [RIGHT] | mask) : mask;
        enemyAccess [index (i, j)] &= ~mask;	// Block access to holes at L/R.
    }
}

void KGrLevelGrid::placeHiddenLadders()
{
    for (const int &offset : std::as_const(hiddenLadders)) {
        int i = offset % width;
        int j = offset / width;
        changeCellAt (i, j, LADDER);
    }
    Q_EMIT showHiddenLadders (hiddenLadders, width);
    hiddenLadders.clear();
}

#include "moc_kgrlevelgrid.cpp"
