#include "GameParameters.h"

#include "Gamemode.h"
#include "Player.h"


GameParameters::GameParameters(Gamemode gamemode, const Player& controls) :
    gamemode(gamemode),
    controls(controls) {
    
    this->reset();
}

void GameParameters::reset() {
    this->clearedLines = 0;
    this->grade = 0;

    switch (this->gamemode) {
        // lowest gravity
        case SPRINT : {this->level = 1; break;}
        // lowest gravity
        case ULTRA : {this->level = 1; break;}
        // goes from level 1 to 20
        case MARATHON : {this->level = 1; break;}
        // goes from level 20 to 39
        case MASTER : {this->level = 20; break;}
        // goes from level 1 to 19
        case INVISIBLE : {this->level = 1; break;}
        // no gravity
        case ZEN : {this->level = 0; break;}
        default : this->level = 1;
    }

    this->updateStats();
}

void GameParameters::lockedPiece(const LineClear& lineClear) {
    switch (this->gamemode) {
        // modes where level increases with lines
        case MARATHON :
        case MASTER : {
                int previousLines = this->clearedLines;
                this->clearedLines += lineClear.lines;

                // level increments every 10 lines, stats only changes on level up
                if (previousLines / 10 < this->clearedLines / 10) {
                    this->level += (this->clearedLines / 10 - previousLines / 10);
                    this->updateStats();
                }
                break;
            }
        // other modes
        default : this->clearedLines += lineClear.lines;
    }

    int previousGrade = this->grade;
    if (!((lineClear.lines == 0) && ((this->grade % 100) == 99))) {
        this->grade += (1 + lineClear.lines);
    }

    switch (this->gamemode) {
        // modes where level increases with grade
        case INVISIBLE : {
            if (previousGrade / 100 < this->grade / 100) {
                this->level += 2;
                this->updateStats();
            }
            break;
        }
        // other modes
        default : break;
    }
}

bool GameParameters::hasWon(int framesPassed) const {
    switch (this->gamemode) {
        // win once 40 lines have been cleared
        case SPRINT : return this->clearedLines >= 40;
        // win once 2mn have passed
        case ULTRA : return (framesPassed / 60) >= 120;
        // win once 200 lines have been cleared
        case MARATHON : return this->clearedLines >= 200;
        // win once 200 lines have been cleared
        case MASTER : return this->clearedLines >= 200;
        // win once 1000 grade has been passed
        case INVISIBLE : return this->grade >= 1000;
        // infinite mode
        case ZEN :
        default : return false;
    }
}

void GameParameters::updateStats() {
    /* NEXT QUEUE */
    switch (this->gamemode) {
        // 5 for fast-controls gamemodes
        case SPRINT : 
        case ULTRA :
        case ZEN : {
                this->nextQueueLength = 5;
                break;
            }
        // 3 for slow-controls gamemodes
        case MARATHON :
        case MASTER :
        case INVISIBLE : {
                this->nextQueueLength = 3;
                break;
            }
        default : this->nextQueueLength = 1;
    }

    /* BONE BLOCKS */
    switch (this->gamemode) {
        // blocks turns into bone blocks at level 30
        case MASTER : {this->boneBlocks = (this->level >= 30); break;}
        default : this->boneBlocks = false;
    }

    /* INVISIBLE */
    this->invisibleBoard = (this->gamemode == INVISIBLE);

    /* GRAVITY */
    // get gravity for an assumed 20-rows board
    static const int gravityPerLevel[] = {
        0,      // lvl0 = no gravity
        1,      // 60f/line, 20s total
        2,      // 30f/line, 10s total
        3,      // 20f/line, 6.66s total
        4,      // 15f/line, 5s total
        5,      // 12f/line, 4s total
        6,      // 10f/line, 3.33 total
        7,      // 8.57f/line, 2.85s total
        8,      // 7.5f/line, 2.5s total
        10,     // 6f/line, 2s total
        12,     // 5f/line, 1.66s total
        14,     // 4.28f/line, 1.42s total
        17,     // 3.52f/line, 1.17s total
        20,     // 3f/line, 60f total
        24,     // 2.5f/line, 50f total
        30,     // 2f/line, 40f total
        40,     // 1.5f/line, 30f total
        1 * 60, // 1line/f, 20f total
        2 * 60, // 2line/f, 10f total
        4 * 60, // 4line/f, 5f total
        20 * 60 // lvl20 = instant gravity
    };
    if (this->level < 0) {
        this->gravity = gravityPerLevel[0];
    }
    else if (this->level > 20) {
        this->gravity = gravityPerLevel[20];
    }
    else {
        this->gravity = gravityPerLevel[this->level];
    }

    /* LOCK DELAY */
    switch (this->gamemode) {
        // starts at 500ms (30f) at lvl 20 and ends at 183ms (11f) at lvl 39
        case MASTER : {this->lockDelay = 30 - (this->level - 20); break;}
        // 10s
        case ZEN : {this->lockDelay = 60 * 10; break;}
        // 1s by default
        default : this->lockDelay = 60;
    }

    /* FORCED LOCK DELAY */
    this->forcedLockDelay = this->lockDelay * 10;

    /* ARE */
    switch (this->gamemode) {
        // starts at 400ms (24f) at lvl 1 and ends at 083ms (5f) at lvl 20
        case MARATHON : {this->ARE = 24 - (this->level - 1); break;}
        // starts at 400ms (24f) at lvl 20 and ends at 083ms (5f) at lvl 39
        case MASTER : {this->ARE = 24 - (this->level - 20); break;}
        // fixed at 250ms (15f)
        case INVISIBLE : {this->ARE = 15; break;}
        // no ARE by default
        default : this->ARE = 0;
    }

    /* LINE ARE */
    this->lineARE = this->ARE * 2;

    /* DAS */
    this->DAS = this->controls.getDAS();
    switch (this->gamemode) {
        // for modes with reduced lock delay, ensure DAS is lower than lock delay, but at least 1
        case MASTER : {
                if (this->lockDelay <= this->DAS) {
                    this->DAS = this->lockDelay - 6; // give 6f (100ms) to change directions
                }
                if (this->DAS < 1) {
                    this->DAS = 1;
                }
                break;
            }
        // modes with no reduced lock delay
        default : break;
    }

    /* ARR */
    this->ARR = this->controls.getARR();
    switch (this->gamemode) {
        // for modes with reduced lock delay, ensure ARR is lower than DAS, but not lower than 1
        case MASTER : {
                if (this->DAS <= this->ARR) {
                    this->ARR = this->DAS - 1;
                }
                if (this->ARR < 1) {
                    this->ARR = 1;
                }
                break;
            }
        // modes with no reduced lock delay
        default : break;
    }

    /* SDR */
    this->SDR = this->controls.getSDR();
    switch (this->gamemode) {
        // modes where we don't want instant soft drop to be possible
        case MARATHON : {
            if (this->SDR < 1) {
                this->SDR = 1;
            }
            break;
        }
        // modes where we don't care
        default : break;
    }
}

int GameParameters::getClearedLines() const {
    return this->clearedLines;
}

int GameParameters::getLevel() const {
    return this->level;
}

int GameParameters::getGrade() const {
    return this->grade;
}

int GameParameters::getNextQueueLength() const {
    return this->nextQueueLength;
}

bool GameParameters::getBoneBlocks() const {
    return this->boneBlocks;
}

bool GameParameters::getInvisibleBoard() const {
    return this->invisibleBoard;
}

int GameParameters::getGravity() const {
    return this->gravity;
}

int GameParameters::getLockDelay() const {
    return this->lockDelay;
}

int GameParameters::getForcedLockDelay() const {
    return this->forcedLockDelay;
}

int GameParameters::getARE() const {
    return this->ARE;
}

int GameParameters::getLineARE() const {
    return this->lineARE;
}

int GameParameters::getDAS() const {
    return this->DAS;
}

int GameParameters::getARR() const {
    return this->ARR;
}

int GameParameters::getSDR() const {
    return this->SDR;
}
