/****************************************************************************
 *    Copyright 2009  Ian Wadham <iandw.au@gmail.com>                         *
 *                                                                          *
 *    This program is free software; you can redistribute it and/or         *
 *    modify it under the terms of the GNU General Public License as        *
 *    published by the Free Software Foundation; either version 2 of        *
 *    the License, or (at your option) any later version.                   *
 *                                                                          *
 *    This program is distributed in the hope that it will be useful,       *
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *    GNU General Public License for more details.                          *
 *                                                                          *
 *    You should have received a copy of the GNU General Public License     *
 *    along with this program.  If not, see <http://www.gnu.org/licenses/>. *
 ****************************************************************************/

#include <cstdio>
#include <cstdlib>

// Include kgrgame.h only to access flags KGrGame::bugFix and KGrGame::logging.
#include "kgrgame.h"
#include "kgrscene.h"

#include "kgrtimer.h"
#include "kgrview.h"
#include "kgrlevelplayer.h"
#include "kgrrulebook.h"
#include "kgrlevelgrid.h"
#include "kgrrunner.h"
#include "kgrdebug.h"

#include "kgoldrunner_debug.h"
#include <KMessageBox>	// TODO - Remove.
#include <KRandomSequence>

KGrLevelPlayer::KGrLevelPlayer (KGrGame * parent, KRandomSequence * pRandomGen)
    :
    QObject          (parent),
    game             (parent),
    randomGen        (pRandomGen),
    hero             (0),
    controlMode      (MOUSE),
    holdKeyOption    (CLICK_KEY),
    nuggets          (0),
    playState        (NotReady),
    recording        (0),
    playback         (false),
    targetI          (1),
    targetJ          (1),
    direction        (NO_DIRECTION),
    newDirection     (NO_DIRECTION),
    timer            (0),
    digCycleTime     (200),	// Milliseconds per dig-timing cycle (default).
    digCycleCount    (40),	// Cycles while hole is fully open (default).
    digOpeningCycles (5),	// Cycles for brick-opening animation.
    digClosingCycles (4),	// Cycles for brick-closing animation.
    digKillingTime   (2),	// Cycle at which enemy/hero gets killed.
    dX               (0),	// X motion for KEYBOARD + HOLD_KEY option.
    dY               (0)	// Y motion for KEYBOARD + HOLD_KEY option.
{
    t.start(); // IDW

    dbgLevel = 0;
}

int KGrLevelPlayer::playerCount = 0;

KGrLevelPlayer::~KGrLevelPlayer()
{
    qDeleteAll(dugBricks);
    dugBricks.clear(); //TODO: necessary?
    //qCDebug(KGOLDRUNNER_LOG) << "LEVEL PLAYER BEING DELETED.";
    playerCount--;
}

void KGrLevelPlayer::init (KGrView * view,
                           KGrRecording * pRecording,
                           const bool pPlayback,
                           const bool gameFrozen)
{
    // TODO - Remove?
    playerCount++;
    if (playerCount > 1) {
        KMessageBox::information (view,
                QStringLiteral("ERROR: KGrLevelPlayer Count = %1").arg(playerCount),
                QStringLiteral("KGrLevelPlayer"));
    }

    recording = pRecording;
    playback  = pPlayback;

    // Create the internal model of the level-layout.
    grid            = new KGrLevelGrid (this, recording);

    // Set mouse/keyboard/laptop control and click/hold option for keyboard.
    controlMode     = recording->controlMode;
    holdKeyOption   = recording->keyOption;

    dX              = 0;		// X motion for KEYBOARD + HOLD_KEY.
    dY              = 0;		// Y motion for KEYBOARD + HOLD_KEY.
    levelWidth      = recording->width;
    levelHeight     = recording->height;

    reappearIndex   = levelWidth;	// Initialise the enemy-rebirth code.
    reappearPos.fill (1, levelWidth);

    // Set the rules of this game.
    switch (recording->rules) {
    case TraditionalRules:
        rules = new KGrTraditionalRules (this);
        break;
    case KGoldrunnerRules:
        rules = new KGrKGoldrunnerRules (this);
        break;
    case ScavengerRules:
        rules = new KGrScavengerRules (this);
        break;
    }

    recIndex  = 0;
    recCount  = 0;
    randIndex = 0;
    T         = 0;

    view->gameScene()->setGoldEnemiesRule (rules->enemiesShowGold());

    // Determine the access for hero and enemies to and from each grid-cell.
    grid->calculateAccess    (rules->runThruHole());

    // Connect to code that paints grid cells and start-positions of sprites.
    connect (this, &KGrLevelPlayer::paintCell, view->gameScene(), &KGrScene::paintCell);
    connect (this, &KGrLevelPlayer::makeSprite, view->gameScene(), &KGrScene::makeSprite);

    // Connect to the mouse-positioning code in the graphics.
    connect (this, &KGrLevelPlayer::getMousePos, view->gameScene(), &KGrScene::getMousePos);
    connect (this, &KGrLevelPlayer::setMousePos, view->gameScene(), &KGrScene::setMousePos);

    // Show the layout of this level in the view (KGrCanvas).
    int wall = ConcreteWall;
    int enemyCount = 0;
    for (int j = wall ; j < levelHeight + wall; j++) {
        for (int i = wall; i < levelWidth + wall; i++) {
            char type = grid->cellType (i, j);

            // Hide false bricks.
            if (type == FBRICK) {
                type = BRICK;
            }

            // Count the gold in this level.
            if (type == NUGGET) {
                nuggets++;
            }

            // If the hero is here, leave the tile empty.
            if (type == HERO) {
                emit paintCell (i, j, FREE);
            }

            // If an enemy is here, count him and leave the tile empty.
            else if (type == ENEMY) {
                enemyCount++;
                emit paintCell (i, j, FREE);
            }

            // Or, just paint this tile.
            else {
                emit paintCell (i, j, type);
            }
        }
    }

    // Set the timing rules, maybe based on the number of enemies.
    rules->setTiming (enemyCount);
    rules->getDigTimes (digCycleTime, digCycleCount);

    // Create the hero (always sprite 0), with the proper timing.
    for (int j = wall ; j < levelHeight + wall; j++) {
        for (int i = wall; i < levelWidth + wall; i++) {
            char type = grid->cellType (i, j);
            if (type == HERO) {
                if (hero == 0) {
                    targetI = i;
                    targetJ = j;
                    heroId  = emit makeSprite (HERO, i, j);
                    hero    = new KGrHero (this, grid, i, j, heroId, rules);
                    hero->setNuggets (nuggets);
                    hero->setDigWhileFalling (recording->digWhileFalling);
                    if ((controlMode == MOUSE) || (controlMode == LAPTOP)) {
                        emit setMousePos (targetI, targetJ);
                    }
                    grid->changeCellAt (i, j, FREE);	// Hero now a sprite.
                }
            }
        }
    }

    // Create the enemies (sprites 1-n), with the proper timing.
    for (int j = wall ; j < levelHeight + wall; j++) {
        for (int i = wall; i < levelWidth + wall; i++) {
            char type = grid->cellType (i, j);
            if (type == ENEMY) {
                KGrEnemy * enemy;
                int id = emit makeSprite (ENEMY, i, j);
                enemy = new KGrEnemy (this, grid, i, j, id, rules);
                enemies.append (enemy);
                grid->changeCellAt (i, j, FREE);	// Enemy now a sprite.
                grid->setEnemyOccupied (i, j, id);
            }
        }
    }

    // Connect the hero's and enemies' efforts to the graphics.
    connect (this, &KGrLevelPlayer::gotGold, view->gameScene(), &KGrScene::gotGold);

    // Connect mouse-clicks from KGrView to digging slot.
    connect (view, &KGrView::mouseClick, this, &KGrLevelPlayer::doDig);

    // Connect the hero and enemies (if any) to the animation code.
    connect (hero, &KGrHero::startAnimation, view->gameScene(), &KGrScene::startAnimation);

    for (KGrEnemy * enemy : qAsConst(enemies)) {
        connect (enemy, &KGrEnemy::startAnimation, view->gameScene(), &KGrScene::startAnimation);
    }

    // Connect the scoring.
    connect (hero, &KGrHero::incScore, game, &KGrGame::incScore);
    for (KGrEnemy * enemy : qAsConst(enemies)) {
        connect (enemy, &KGrEnemy::incScore, game, &KGrGame::incScore);
    }

    // Connect the sounds.
    connect (hero, &KGrHero::soundSignal, game, &KGrGame::playSound);

    // Connect the level player to the animation code (for use with dug bricks).
    connect (this, &KGrLevelPlayer::startAnimation, view->gameScene(), &KGrScene::startAnimation);

    connect (this, &KGrLevelPlayer::deleteSprite, view->gameScene(), &KGrScene::deleteSprite);

    // Connect the grid to the view, to show hidden ladders when the time comes.
    connect (grid, &KGrLevelGrid::showHiddenLadders, view->gameScene(), &KGrScene::showHiddenLadders);

    // Connect and start the timer.  The tick() slot emits signal animation(),
    // so there is just one time-source for the model and the view.

    timer = new KGrTimer (this, TickTime);	// TickTime def in kgrglobals.h.
    if (gameFrozen) {
        timer->pause();				// Pause is ON as level starts.
    }

    connect (timer, &KGrTimer::tick, this, &KGrLevelPlayer::tick);
    connect (this, &KGrLevelPlayer::animation, view->gameScene(), &KGrScene::animate);

    if (! playback) {
        // Allow some time to view the level before starting a replay.
        recordInitialWaitTime (1500);		// 1500 msec or 1.5 sec.
    }
}

void KGrLevelPlayer::startDigging (Direction diggingDirection)
{
    int digI = 1;
    int digJ = 1;

    // We need the hero to decide if he CAN dig and if so, where.
    if (hero->dig (diggingDirection, digI, digJ)) {
        // The hero can dig as requested: the chosen brick is at (digI, digJ).
        grid->changeCellAt (digI, digJ, HOLE);

        // Start the brick-opening animation (non-repeating).
        int id = emit makeSprite (BRICK, digI, digJ);
        emit startAnimation (id, false, digI, digJ,
                        (digOpeningCycles * digCycleTime), STAND, OPEN_BRICK);

        DugBrick * thisBrick = new DugBrick;
        DugBrick   brick     = {id, digCycleTime, digI, digJ,
                    (digCycleCount + digOpeningCycles + digClosingCycles - 1),
                    t.elapsed()}; // IDW test
        (* thisBrick)        = brick;
        dugBricks.append (thisBrick);
    }
}

void KGrLevelPlayer::processDugBricks (const int scaledTime)
{
    DugBrick * dugBrick;
    QMutableListIterator<DugBrick *> iterator (dugBricks);

    while (iterator.hasNext()) {
        dugBrick = iterator.next();
        dugBrick->cycleTimeLeft -= scaledTime;
        if (dugBrick->cycleTimeLeft < scaledTime) {
            dugBrick->cycleTimeLeft += digCycleTime;
            if (--dugBrick->countdown == digClosingCycles) {
                // Start the brick-closing animation (non-repeating).
                emit startAnimation (dugBrick->id, false,
                                     dugBrick->digI, dugBrick->digJ,
                                     (digClosingCycles * digCycleTime),
                                     STAND, CLOSE_BRICK);
            }
            if (dugBrick->countdown == digKillingTime) {
                // Close the hole and maybe capture the hero or an enemy.
                grid->changeCellAt (dugBrick->digI, dugBrick->digJ, BRICK);
            }
            if (dugBrick->countdown <= 0) {
                // Dispose of the dug brick and remove it from the list.
                emit deleteSprite (dugBrick->id);
                delete dugBrick;
                iterator.remove();
            }
        }
    }
}

void KGrLevelPlayer::prepareToPlay()
{
    if ((controlMode == MOUSE) || (controlMode == LAPTOP)) {
        emit setMousePos (targetI, targetJ);
    }
    playState = Ready;
}

void KGrLevelPlayer::pause (bool stop)
{
    if (stop) {
        timer->pause();
    }
    else {
        timer->resume();
    }
}

void KGrLevelPlayer::setTarget (int pointerI, int pointerJ)
{
    // Mouse or other pointer device (eg. laptop touchpad) controls the hero.
    switch (playState) {
    case NotReady:
        // Ignore the pointer until KGrLevelPlayer is ready to start.
        break;
    case Ready:
        // Wait until the human player is ready to start playing.
        if ((pointerI == targetI) && (pointerJ == targetJ)) {
            // The pointer is still over the hero: do not start playing yet.
            break;
        }
        // The pointer moved: fall into "case Playing:" and start playing.
        else if (! playback) { // TODO - Remove debugging code (3 lines).
            T = 0;
        }
        playState = Playing;
    case Playing:
        // The human player is playing now.
        if (! playback) {
            record (3, pointerI, pointerJ);
        }
        targetI = pointerI;
        targetJ = pointerJ;
        break;
    }
}

void KGrLevelPlayer::doDig (int button)
{
    // Click to end demo/playback mode.
    if (playback) {
        interruptPlayback();
        return;
    }

    // If not ready or game control is not by mouse, ignore mouse-clicks.
    if ((playState == NotReady) || (controlMode != MOUSE)) {
        return;
    }

    uchar recordByte = 0;
    playState = Playing;
    switch (button) {
    case Qt::LeftButton:
        recordByte = DIRECTION_CODE + DIG_LEFT;
        startDigging (DIG_LEFT);
        break;
    case Qt::RightButton:
        recordByte = DIRECTION_CODE + DIG_RIGHT;
        startDigging (DIG_RIGHT);
        break;
    default:
        break;
    }
    if (recordByte != 0) {
        // Record the digging action.
        record (1, recordByte);
    }
}

void KGrLevelPlayer::setDirectionByKey (const Direction dirn,
                                        const bool pressed)
                     
{
    // Keystrokes control the hero.  KGrGame should avoid calling this during
    // playback, but better to be safe ...
    if (playback || (playState == NotReady) || (controlMode == MOUSE)) {
        return;
    }

    if ((dirn == DIG_LEFT) || (dirn == DIG_RIGHT)) {
	// Control mode is KEYBOARD or LAPTOP (hybrid: pointer + dig-keys).
        if (playState == Ready) {
            playState = Playing;
            T = 0;
        }
        if (controlMode == KEYBOARD) {
// IDW What happens here if keyboard option is HOLD_KEY?  What *should* happen?
            newDirection = STAND;	// Stop a keyboard move when digging.
        }
        startDigging (dirn);
        record (1, (uchar) (DIRECTION_CODE + dirn));
    }
    else if (controlMode == KEYBOARD) {
        if (playState == Ready) {
            playState = Playing;
            T = 0;
        }
        // Start recording and acting on the new direction at the next tick.
        if ((holdKeyOption == CLICK_KEY) && pressed && (dirn != direction)) {
            newDirection = dirn;
        }
        else if (holdKeyOption == HOLD_KEY) {
            int sign = pressed ? +1 : -1;
            dX = dX + sign * movement [dirn][X];
            dY = dY + sign * movement [dirn][Y];
        }
    }
}

Direction KGrLevelPlayer::getDirection (int heroI, int heroJ)
{
    if ((controlMode == MOUSE) || (controlMode == LAPTOP)) {
        int index = (playback) ? recIndex : recIndex - 2;
        dbe2 "T %04d recIndex %03d hero at [%02d, %02d] aiming at [%02d, %02d]\n",
             T, index, heroI, heroJ, targetI, targetJ);

        // If using a pointer device, calculate the hero's next direction,
        // starting from last pointer position and hero's current position.

        direction = setDirectionByDelta (targetI - heroI, targetJ - heroJ,
                                         heroI, heroJ);
    }
    else if ((controlMode == KEYBOARD) && (holdKeyOption == HOLD_KEY)) {

        // If using the hold/release key option, resolve diagonal directions
        // (if there are multi-key holds) into either horizontal or vertical.

        direction = setDirectionByDelta (dX, dY, heroI, heroJ);
        dbe2 "T %04d recIndex %03d delta [%02d, %02d] "
             "hero at [%02d, %02d] direction %d\n",
             T, recIndex - 1, dX, dY, heroI, heroJ, direction);
    }

    return direction;
}

Direction KGrLevelPlayer::setDirectionByDelta (const int di, const int dj,
                                               const int heroI, const int heroJ)
{
    Direction dirn = STAND;

    if ((dj > 0) && (grid->heroMoves (heroI, heroJ) & dFlag [DOWN])) {
        dirn = DOWN;
    }
    else if ((dj < 0) && (grid->heroMoves (heroI, heroJ) & dFlag [UP])) {
        dirn = UP;
    }
    else if (di > 0) {
        dirn = RIGHT;
    }
    else if (di < 0) {
        dirn = LEFT;
    }
    else {		// Note: di is zero, but dj is not necessarily zero.
        dirn = STAND;
    }
    return dirn;
}

void KGrLevelPlayer::recordInitialWaitTime (const int ms)
{
    // Allow a pause for viewing when playback starts.
    recCount = ms / TickTime;			// Convert milliseconds-->ticks.
    if (controlMode == KEYBOARD) {
        recording->content [recIndex++]   = (uchar) (DIRECTION_CODE +
                                                     NO_DIRECTION);
        recording->content [recIndex]     = (uchar) recCount;
        recording->content [recIndex + 1] = (uchar) END_CODE;
    }
    else {
        recording->content [recIndex++]   = (uchar) targetI;
        recording->content [recIndex++]   = (uchar) targetJ;
        recording->content [recIndex]     = (uchar) recCount;
        recording->content [recIndex + 1] = (uchar) END_CODE;
    }
}

void KGrLevelPlayer::record (const int bytes, const int n1, const int n2)
{
    if (playback) {
        return;
    }

    // Check for repetition of a previous direction-key or pointer posn. (I, J).
    if (recIndex > 2)
    dbe3 "recCount %d bytes %d n1 %d n2 %d [recIndex-1] %d [recIndex-2] %d\n",
        recCount, bytes, n1, n2, (uchar) recording->content.at (recIndex - 1),
        (uchar) recording->content.at (recIndex - 2));
    if ((recCount > 0) && (bytes > 1) && (recCount < (END_CODE - 1)) &&
        (((bytes == 2) && (n1 == (uchar) recording->content [recIndex - 1])) ||
         ((bytes == 3) && (n1 == (uchar) recording->content [recIndex - 2]) &&
                          (n2 == (uchar) recording->content [recIndex - 1]))
        )) {
        // Count repetitions, up to a maximum of (END_CODE - 1) = 254.
        recording->content [recIndex]       = (uchar) (++recCount);
        if (bytes == 2) {
            dbe2 "T %04d recIndex %03d REC: codes --- %3d %3d - recCount++\n",
                 T, recIndex - 1, (uchar)(recording->content.at (recIndex-1)),
                                  (uchar)(recording->content.at (recIndex)));
        }
        else if (bytes == 3) {
            dbe2 "T %04d recIndex %03d REC: codes %3d %3d %3d - recCount++\n",
                 T, recIndex - 2, (uchar)(recording->content.at (recIndex-2)),
                                  (uchar)(recording->content.at (recIndex-1)),
                                  (uchar)(recording->content.at (recIndex)));
        }
        return;
    }

    // Record a single code or the first byte of a new doublet or triplet.
    recCount = 0;
    recording->content [++recIndex]         = (uchar) n1;

    if (bytes == 3) {
        // Record another byte for a triplet (i.e. the pointer's J position).
        recording->content [++recIndex] = (uchar) n2;
    }

    if (bytes > 1) {
        // Record a repetition-count of 1 for a new doublet or triplet.
        recCount = 1;
        recording->content [++recIndex]     = (uchar) recCount;
    }

    switch (bytes) {
    case 1:
        dbe2 "T %04d recIndex %03d REC: singleton %3d %x\n",
             T, recIndex, n1, n1);
        break;
    case 2:
        dbe2 "T %04d recIndex %03d REC: codes %3d %3d %3d - NEW DIRECTION\n",
             T, recIndex - 1, direction,
                              (uchar)(recording->content.at (recIndex-1)),
                              (uchar)(recording->content.at (recIndex)));
        break;
    case 3:
        dbe2 "T %04d recIndex %03d REC: codes %3d %3d %3d - NEW TARGET\n",
             T, recIndex - 2, (uchar)(recording->content.at (recIndex-2)),
                              (uchar)(recording->content.at (recIndex-1)),
                              (uchar)(recording->content.at (recIndex)));
        break;
    default:
        break;
    }

    // Add the end-of-recording code (= 255).
    recording->content [recIndex + 1] = (uchar) END_CODE;
    return;
}

Direction KGrLevelPlayer::getEnemyDirection (int  enemyI, int enemyJ,
                                             bool leftRightSearch)
{
    int heroX, heroY, pointsPerCell;

    pointsPerCell = hero->whereAreYou (heroX, heroY);
    return rules->findBestWay (enemyI, enemyJ,
                               heroX / pointsPerCell, heroY / pointsPerCell,
                               grid, leftRightSearch);
}

bool KGrLevelPlayer::heroCaught (const int heroX, const int heroY)
{
    if (enemies.count() == 0) {
        return false;
    }
    int enemyX, enemyY, pointsPerCell_1;
    for (KGrEnemy * enemy : qAsConst(enemies)) {
        pointsPerCell_1 = enemy->whereAreYou (enemyX, enemyY) - 1;
        if (((heroX < enemyX) ? ((heroX + pointsPerCell_1) >= enemyX) :
                                 (heroX <= (enemyX + pointsPerCell_1))) &&
            ((heroY < enemyY) ? ((heroY + pointsPerCell_1) > enemyY) :
                                 (heroY <= (enemyY + pointsPerCell_1)))) {
            // dbk << "Caught by";
            // enemy->showState();
            return true;
        }
    }
    return false;
}

KGrEnemy * KGrLevelPlayer::standOnEnemy (const int spriteId,
                                         const int x, const int y)
{
    int minEnemies = (spriteId == heroId) ? 1 : 2;
    if (enemies.count() < minEnemies) {
        return 0;
    }
    int enemyX, enemyY, pointsPerCell;
    for (KGrEnemy * enemy : qAsConst(enemies)) {
        pointsPerCell = enemy->whereAreYou (enemyX, enemyY);
        if (((enemyY == (y + pointsPerCell)) ||
             (enemyY == (y + pointsPerCell - 1))) &&
            (enemyX > (x - pointsPerCell)) &&
            (enemyX < (x + pointsPerCell))) {
            return enemy;
        }
    }
    return 0;
}

bool KGrLevelPlayer::bumpingFriend (const int spriteId, const Direction dirn,
                                    const int gridI,  const int gridJ)
{
    int dI = 0;
    int dJ = 0;
    switch (dirn) {
    case LEFT:
         dI = -1;
         break;
    case RIGHT:
         dI = +1;
         break;
    case UP:
         dJ = -1;
         break;
    case DOWN:
         dJ = +1;
         break;
    default:
         break;
    }

    int otherEnemy;
    if (dI != 0) {
        otherEnemy = grid->enemyOccupied (gridI + dI, gridJ);
        if (otherEnemy > 0) {
            dbk3 << otherEnemy << "at" << (gridI + dI) << gridJ
                     << "dirn" << ((otherEnemy > 0) ?
                               (enemies.at (otherEnemy - 1)->direction()) : 0)
                     << "me" << spriteId << "dirn" << dirn;
            if (enemies.at (otherEnemy - 1)->direction() != dirn) {
                dbk3 << spriteId << "wants" << dirn << ":" << otherEnemy
                         << "at" << (gridI + dI) << gridJ << "wants"
                         << (enemies.at (otherEnemy - 1)->direction());
                return true;
            }
        }
    }
    if (dJ != 0) {
        otherEnemy = grid->enemyOccupied (gridI, gridJ + dJ);
        if (otherEnemy > 0) {
            dbk3 << otherEnemy << "at" << gridI << (gridJ + dJ)
                     << "dirn" << ((otherEnemy > 0) ?
                               (enemies.at (otherEnemy - 1)->direction()) : 0)
                     << "me" << spriteId << "dirn" << dirn;
            if (enemies.at (otherEnemy - 1)->direction() != dirn) {
                dbk3 << spriteId << "wants" << dirn << ":" << otherEnemy
                         << "at" << gridI << (gridJ + dJ) << "wants"
                         << (enemies.at (otherEnemy - 1)->direction());
                return true;
            }
        }
    }
    return false;
}

void KGrLevelPlayer::unstackEnemy (const int spriteId,
                                   const int gridI, const int gridJ,
                                   const int prevEnemy)
{
    dbe2 "KGrLevelPlayer::unstackEnemy (%02d at [%02d,%02d] prevEnemy %02d)\n",
        spriteId, gridI, gridJ, prevEnemy);
    int nextId = grid->enemyOccupied (gridI, gridJ);
    int prevId;
    while (nextId > 0) {
        prevId = enemies.at (nextId - 1)->getPrevInCell();
        dbe2 "Next %02d prev %02d\n", nextId, prevId);
        if (prevId == spriteId) {
            dbe2 "    SET IDs - id %02d prev %02d\n", nextId, prevEnemy);
            enemies.at (nextId - 1)->setPrevInCell (prevEnemy);
            // break;
        }
        nextId = prevId;
    }
}

void KGrLevelPlayer::tick (bool missed, int scaledTime)
{
    int i, j;
    emit getMousePos (i, j);
    if (i == -2) {
        return;         // The KGoldRunner window is inactive.
    }
    if ((i == -1) && (playback || (controlMode != KEYBOARD))) {
        return;		// The pointer is outside the level layout.
    }

    if (playback) {			// Replay a recorded move.
        if (! doRecordedMove()) {
            playback = false;
            // TODO - Should we emit interruptDemo() in UNEXPECTED_END case?
            dbk << "Unexpected END_OF_RECORDING - or KILL_HERO ACTION.";
            return;			// End of recording.
        }
    }
    else if ((controlMode == MOUSE) || (controlMode == LAPTOP)) {
        setTarget (i, j);		// Make and record a live pointer-move.
    }
    else if (controlMode == KEYBOARD) {
        if (holdKeyOption == CLICK_KEY) {
            if (newDirection != direction) {
                direction = newDirection;
            }
        }
        // If keyboard with holdKey option, record one of nine directions.
        else if (holdKeyOption == HOLD_KEY) {
            const Direction d [9] = {UP_LEFT,   UP,    UP_RIGHT,
                                     LEFT,      STAND, RIGHT,
                                     DOWN_LEFT, DOWN,  DOWN_RIGHT};
            direction = d [(3 * (dY + 1)) + (dX + 1)];
        }
        // Record the direction, but do not extend the initial wait-time.
        if ((direction != NO_DIRECTION) && (playState == Playing)) { // IDW
// IDW Need a better condition here. (playState == Playing) was to stop
// IDW the HOLD_KEY option recording a whole lot of STAND directions before
// IDW the first key is pressed. (direction != NO_DIRECTION) is previous code.
            record (2, DIRECTION_CODE + direction);
        }
    }

    if (playState != Playing) {
        return;
    }
    T++;

    if (dugBricks.count() > 0) {
        processDugBricks (scaledTime);
    }

    HeroStatus status = hero->run (scaledTime);
    if ((status == WON_LEVEL) || (status == DEAD)) {
        // Unsolicited timer-pause halts animation immediately, regardless of
        // user-selected state. It's OK: KGrGame deletes KGrLevelPlayer v. soon.
        timer->pause();

        // Queued connection ensures KGrGame slot runs AFTER return from here.
        emit endLevel (status);
        //qCDebug(KGOLDRUNNER_LOG) << "END OF LEVEL";
        return;
    }

    for (KGrEnemy * enemy : qAsConst(enemies)) {
        enemy->run (scaledTime);
    }

    emit animation (missed);
}

int KGrLevelPlayer::runnerGotGold (const int  spriteId,
                                   const int  i, const int j,
                                   const bool hasGold, const bool lost)
{
    if (hasGold) {
        dbk2 << "GOLD COLLECTED BY" << spriteId << "AT" << i << j;
    }
    else if (lost) {
        dbk2 << "GOLD LOST BY" << spriteId << "AT" << i << j;
    }
    else {
        dbk2 << "GOLD DROPPED BY" << spriteId << "AT" << i << j;
    }
    if (! lost) {
        grid->gotGold (i, j, hasGold);		// Record pickup/drop on grid.
    }
    emit gotGold (spriteId, i, j, hasGold, lost); // Erase/show gold on screen.

    // If hero got gold, score, maybe show hidden ladders, maybe end the level.
    if ((spriteId == heroId) || lost) {
        if (--nuggets <= 0) {
            grid->placeHiddenLadders();		// All gold picked up or lost.
        }
    }
    if (lost) {
        hero->setNuggets (nuggets);		// Update hero re lost gold.
    }
    return nuggets;
}

void KGrLevelPlayer::makeReappearanceSequence()
{
    // The idea is to make each possible x co-ord come up once per levelWidth
    // reappearances of enemies.  This is not truly random, but it reduces the
    // tedium in levels where you must keep killing enemies until a particular
    // x or range of x comes up (e.g. if they have to collect gold for you).

    // First put the positions in ascending sequence.
    for (int k = 0; k < levelWidth; k++) {
        reappearPos [k] = k + 1;
    }

    int z;
    int left = levelWidth;
    int temp;

    // Shuffle the co-ordinates of reappearance positions (1 to levelWidth).
    for (int k = 0; k < levelWidth; k++) {
        // Pick a random element from those that are left.
        z = (int) (randomByte ((uchar) left));
        // Exchange its value with the last of the ones left.
        temp = reappearPos [z];
        reappearPos [z] = reappearPos [left - 1];
        reappearPos [left - 1] = temp;
        left--;
    }
    dbk2 << "Randoms" << reappearPos;
    reappearIndex = 0;
}

void KGrLevelPlayer::enemyReappear (int & gridI, int & gridJ)
{
    bool looking = true;
    int  i, j, k;

    // Follow Traditional or Scavenger rules: enemies reappear at top.
    j = rules->reappearRow();

    // Randomly look for a free spot in the row.  Limit the number of tries.
    for (k = 1; ((k <= 3) && looking); k++) {
        if (reappearIndex >= levelWidth) {
            makeReappearanceSequence();	// Get next array of random i.
        }
        i = reappearPos [reappearIndex++];
        switch (grid->cellType (i, j)) {
        case FREE:
        case HLADDER:
            looking = false;
            break;
        default:
            break;
        }
    }

    // If unsuccessful, choose the first free spot in the rows below.
    while ((j < levelHeight) && looking) {
        j++;
        i = 0;
        while ((i < levelWidth) && looking) {
            i++;
            switch (grid->cellType (i, j)) {
            case FREE:
            case HLADDER:
                looking = false;
                break;
            default:
                break;
            }
        }
    }
    dbk2 << "Reappear at" << i << j;
    gridI = i;
    gridJ = j;
}

uchar KGrLevelPlayer::randomByte (const uchar limit)
{
    if (! playback) {
        uchar value = randomGen->getLong ((unsigned long) limit);
        // A zero-byte terminates recording->draws, so add 1 when recording ...
        dbe2 "Draw %03d, index %04d, limit %02d\n", value, randIndex, limit);
        recording->draws [randIndex++] = value + 1;
        return value;
    }
    else {
        dbe2 "Draw %03d, index %04d, limit %02d\n",
             (recording->draws.at (randIndex) - 1), randIndex, limit);
        // and subtract 1 when replaying.
        return ((uchar) recording->draws.at (randIndex++) - 1);
    }
}

bool KGrLevelPlayer::doRecordedMove()
{
    int i, j;
    uchar code = recording->content [recIndex];
    while (true) {
        // Check for end of recording.
        if ((code == END_CODE) || (code == 0)) {
            dbe2 "T %04d recIndex %03d PLAY - END of recording\n",
                 T, recIndex);
            emit endLevel (UNEXPECTED_END);
            return false;
        }

        // Simulate recorded mouse movement.
        if (code < DIRECTION_CODE) {
            // playState = Playing;
            if (recCount <= 0) {
                i = code;
                j = (uchar)(recording->content [recIndex + 1]);
                // targetI = code;
                // targetJ = (uchar)(recording->content [recIndex + 1]);
                recCount = (uchar)(recording->content [recIndex + 2]);
                dbe2 "T %04d recIndex %03d PLAY codes %d %d %d - NEW TARGET\n",
                     T, recIndex, i, j, recCount);
                     // T, recIndex, targetI, targetJ, recCount);

                setTarget (i, j);
            }
            else {
                dbe2 "T %04d recIndex %03d PLAY codes %d %d %d\n",
                     T, recIndex, targetI, targetJ, recCount);
            }
            if (--recCount <= 0) {
                recIndex = recIndex + 3;
                dbe2 "T %04d recIndex %03d PLAY - next index\n",
                     T, recIndex);
            }
            break;
        }

        // Simulate a key press or mouse button click.
        else if (code < MODE_CODE) {
            code = code - DIRECTION_CODE;
            if (code != direction) {
                playState = Playing;
            }
            if ((code == DIG_LEFT) || (code == DIG_RIGHT)) {
                dbe2 "T %04d recIndex %03d PLAY dig code %d\n",
                     T, recIndex, code);
                startDigging ((Direction) (code));
                recIndex++;
                code = recording->content [recIndex];
                recCount = 0;
                continue;
            }
            else {
                if (recCount <= 0) {
                    recCount = (uchar)(recording->content [recIndex + 1]);
                    dbe2 "T %04d recIndex %03d PLAY codes %d %d - KEY PRESS\n",
                         T, recIndex, code, recCount);
                    direction = ((Direction) (code));
                    dX = movement [direction][X];
                    dY = movement [direction][Y];
                }
                else {
                    dbe2 "T %04d recIndex %03d PLAY codes %d %d mode %d\n",
                         T, recIndex, code, recCount, controlMode);
                }
                if (--recCount <= 0) {
                    recIndex = recIndex + 2;
                    dbe2 "T %04d recIndex %03d PLAY - next index\n",
                         T, recIndex);
                }
            }
            break;
        }

        // Replay a change of control-mode.
        else if (code < KEY_OPT_CODE) {
            dbe2 "T %04d recIndex %03d PLAY control-mode code %d\n",
                 T, recIndex, code);
            setControlMode (code - MODE_CODE);
            recIndex++;
            code = recording->content [recIndex];
            recCount = 0;
            continue;
        }

        // Replay a change of keyboard click/hold option.
        else if (code < ACTION_CODE) {
            dbe2 "T %04d recIndex %03d PLAY key-option code %d\n",
                 T, recIndex, code);
            setHoldKeyOption (code - KEY_OPT_CODE + CLICK_KEY);
            recIndex++;
            code = recording->content [recIndex];
            recCount = 0;
            continue;
        }

        // Replay an action, such as KILL_HERO.
        else if (code < SPEED_CODE) {
            if (code == (ACTION_CODE + KILL_HERO)) {
                dbe2 "T %04d recIndex %03d PLAY kill-hero code %d\n",
                     T, recIndex, code);
                emit endLevel (DEAD);
                return false;
            }
        }

        // Replay a change of speed.
        else {
            dbe2 "T %04d recIndex %03d PLAY speed-change code %d\n",
                 T, recIndex, code);
            setTimeScale (code - SPEED_CODE);
            recIndex++;
            code = recording->content [recIndex];
            recCount = 0;
            continue;
        }
    }
    return true;
}

void KGrLevelPlayer::interruptPlayback()
{
    // Check if still replaying the wait-time before the first move.
    if (playState != Playing) {
        return;
    }

    uchar code = recording->content [recIndex];
    // Check for end-of-recording already reached.
    if ((code == END_CODE) || (code == 0)) {
        return;
    }

// Start debug stuff.
    dbk2 << "recIndex" << recIndex << "recCount" << recCount
        << "randIndex" << randIndex;
    int ch = 0;
    int i  = 0;
    while (i < recording->content.size()) {
        ch = (uchar)(recording->content.at(i));
        dbe2 "%03d ", ch);
        if (ch == 0)
            break;
        i++;
    }
    dbe2 "\n%d bytes\n", i - 1);
    i  = 0;
    while (i < recording->draws.size()) {
        ch = (uchar)(recording->draws.at(i));
        dbe2 "%03d ", ch);
        if (ch == 0)
            break;
        i++;
    }
    dbe2 "\n%d bytes\n", i - 1);
// End debug stuff.

    if (recCount > 0) {
        if ((code >= DIRECTION_CODE) && (code < (DIRECTION_CODE + nDirections)))
        {
            // Set keyboard counter to show how many ticks have been replayed.
            recCount = (uchar)(recording->content [recIndex + 1]) - recCount;
            recording->content [recIndex + 1] = (uchar) (recCount);
            recIndex = recIndex + 1;	// Count here if same key after pause.
        }
        else if (code < DIRECTION_CODE) {
            // Set mouse-move counter to show how many ticks have been replayed.
            recCount = (uchar)(recording->content [recIndex + 2]) - recCount;
            recording->content [recIndex + 2] = (uchar) (recCount);
            recIndex = recIndex + 2;	// Count here if mouse in same position.
        }
    }

    recording->content [recIndex + 1] = END_CODE;
    for (int i = (recIndex + 2); i < recording->content.size(); i++) {
        recording->content [i] = 0;
    }
    for (int i = randIndex; i < recording->draws.size(); i++) {
        recording->draws [i] = 0;
    }

// Start debug stuff.
    dbk2 << "recIndex" << recIndex << "recCount" << recCount
        << "randIndex" << randIndex;
    i  = 0;
    while (i < recording->content.size()) {
        ch = (uchar)(recording->content.at(i));
        dbe2 "%03d ", ch);
        if (ch == 0)
            break;
        i++;
    }
    dbe2 "\n%d bytes\n", i - 1);
    i  = 0;
    while (i < recording->draws.size()) {
        ch = (uchar)(recording->draws.at(i));
        dbe2 "%03d ", ch);
        if (ch == 0)
            break;
        i++;
    }
    dbe2 "\n%d bytes\n", i - 1);
// End debug stuff.

    playback = false;
    emit interruptDemo();
    // dbk << "INTERRUPT - emit interruptDemo();";
}

void KGrLevelPlayer::killHero()
{
    if (! playback) {
        // Record that KILL_HERO is how the level ended.
        record (1, ACTION_CODE + KILL_HERO);

        emit endLevel (DEAD);
        //qCDebug(KGOLDRUNNER_LOG) << "END OF LEVEL";
    }
}

void KGrLevelPlayer::setControlMode  (const int mode)
{
    controlMode = mode;

    if (! playback) {
        // Record the change of mode.
        record (1, MODE_CODE + mode);
    }
}

void KGrLevelPlayer::setHoldKeyOption  (const int option)
{
    holdKeyOption = option;

    if (! playback) {
        // Record the change of keyboard click/hold option.
        record (1, KEY_OPT_CODE + option - CLICK_KEY);
    }
}

void KGrLevelPlayer::setTimeScale  (const int timeScale)
{
    timer->setScale ((float) (timeScale * 0.1));

    if (! playback) {
        record (1, SPEED_CODE + timeScale);
    }
}

/******************************************************************************/
/**************************  AUTHORS' DEBUGGING AIDS **************************/
/******************************************************************************/

void KGrLevelPlayer::dbgControl (int code)
{
    switch (code) {
    case DO_STEP:
        timer->step();			// Do one timer step only.
        break;
    case BUG_FIX:
        bugFix();			// Turn a bug fix on/off dynamically.
        break;
    case LOGGING:
        startLogging();			// Turn logging on/off.
        break;
    case S_POSNS:
        showFigurePositions();		// Show everybody's co-ordinates.
        break;
    case S_HERO:
        hero->showState();		// Show hero's co-ordinates and state.
        break;
    case S_OBJ:
        showObjectState();		// Show an object's state.
        break;
    default:
        showEnemyState (code - ENEMY_0); // Show enemy co-ords and state.
        break;
    }
}

void KGrLevelPlayer::bugFix()
{
    // Toggle a bug fix on/off dynamically.
    KGrGame::bugFix = (KGrGame::bugFix) ? false : true;
    fprintf (stderr, "%s", (KGrGame::bugFix) ? "\n" : "");
    fprintf (stderr, ">> Bug fix is %s\n", (KGrGame::bugFix) ? "ON" : "OFF\n");
}

void KGrLevelPlayer::startLogging()
{
    // Toggle logging on/off dynamically.
    KGrGame::logging = (KGrGame::logging) ? false : true;
    fprintf (stderr, "%s", (KGrGame::logging) ? "\n" : "");
    fprintf (stderr, ">> Logging is %s\n", (KGrGame::logging) ? "ON" : "OFF\n");
}

void KGrLevelPlayer::showFigurePositions()
{
    hero->showState();
    for (KGrEnemy * enemy : qAsConst(enemies)) {
        enemy->showState();
    }
}

void KGrLevelPlayer::showObjectState()
{
    int   i       = targetI;
    int   j       = targetJ;
    char  here    = grid->cellType (i, j);
    Flags access  = grid->heroMoves (i, j);
    int   enemyId = grid->enemyOccupied (i, j);

    int enter     = (access & ENTERABLE)         ? 1 : 0;
    int stand     = (access & dFlag [STAND])     ? 1 : 0;
    int u         = (access & dFlag [UP])        ? 1 : 0;
    int d         = (access & dFlag [DOWN])      ? 1 : 0;
    int l         = (access & dFlag [LEFT])      ? 1 : 0;
    int r         = (access & dFlag [RIGHT])     ? 1 : 0;
    fprintf (stderr,
             "[%02d,%02d] [%c] %02x E %d S %d U %d D %d L %d R %d occ %02d\n",
	     i, j, here, access, enter, stand, u, d, l, r, enemyId);

    Flags eAccess = grid->enemyMoves (i, j);
    if (eAccess != access) {
        access    = eAccess;
        enter     = (access & ENTERABLE)         ? 1 : 0;
        stand     = (access & dFlag [STAND])     ? 1 : 0;
        u         = (access & dFlag [UP])        ? 1 : 0;
        d         = (access & dFlag [DOWN])      ? 1 : 0;
        l         = (access & dFlag [LEFT])      ? 1 : 0;
        r         = (access & dFlag [RIGHT])     ? 1 : 0;
        fprintf (stderr,
             "[%02d,%02d] [%c] %02x E %d S %d U %d D %d L %d R %d Enemy\n",
	     i, j, here, access, enter, stand, u, d, l, r);
    }
}

void KGrLevelPlayer::showEnemyState (int enemyId)
{
    if (enemyId < enemies.count()) {
        enemies.at(enemyId)->showState();
    }
}



