#include <stdlib.h>
#include <ctype.h>

#include <tdeapplication.h>
#include <kstandarddirs.h>

#include <tdeaccel.h>
#include <tdeconfig.h>
#include <tdemessagebox.h>
#include <tqpixmap.h>
#include <tqstring.h>
#include <tqdatastream.h>
#include <tqkeycode.h>
#include <tqtimer.h>
#include <tqfileinfo.h>

#include "bitfont.h"
#include "score.h"

Score::Score(TQWidget *parent, const char *name, int Scheme, int Mode, Bitfont *font) : TQWidget(parent, name)
{
    setFocusPolicy(TQWidget::StrongFocus);

    paused = false;

    lastScore = -1;
    lastPlayer = -1;

    cursorBlinkTimer = 0;
    cursorBlinkMS = -1;
    cursor.x = -1;
    cursor.y = -1;
    cursor.on = false;
    cursor.chr = TQChar('?');

    initKeys();

    scheme = Scheme;
    mode = Mode;
    confScheme();

    bitfont = font;

    highscoreFile.setName(locateHighscoreFilePath().filePath());
    read();

    for (int p = 0; p < maxPlayer; p++) {
        playerScore[p] = 0;
        playerName[p] = getenv("LOGNAME");
        if (playerName[p].length() < minPlayerNameLength)
            playerName[p].setExpand(minPlayerNameLength-1, ' ');

        for (uint i = 0; i < playerName[p].length(); i++)
            if (playerName[p].at(i) < bitfont->firstChar() ||
                playerName[p].at(i) > bitfont->lastChar())
                playerName[p].at(i) = playerName[p].at(i).upper();
    }
}

Score::~Score()
{
    // write();
}

void Score::paintEvent( TQPaintEvent *e)
{
    if (rect(1, 0, i18n("  1UP ")).intersects(e->rect())) {
        TQPixmap pix;
        TQColor fg = BLACK;
        if (cursor.on || paused || lastPlayer != 0)
            fg = WHITE;
        pix = bitfont->text(i18n("  1UP "), fg, BLACK);
        bitBlt(this, x(1), y(0), &pix);
    }

    if (rect(8, 0, i18n(" HIGH SCORE ")).intersects(e->rect())) {
            TQPixmap pix = bitfont->text(i18n(" HIGH SCORE "), WHITE, BLACK);
        bitBlt(this, x(8), y(0), &pix);
    }

    if (maxPlayer > 1 && rect(21, 0, i18n("  2UP ")).intersects(e->rect())) {
        TQPixmap pix;
        TQColor fg = BLACK;
        if (cursor.on || paused || lastPlayer != 1)
            fg = WHITE;
        pix = bitfont->text(i18n("  2UP "), fg, BLACK);
        bitBlt(this, x(21), y(0), &pix);
    }

    TQString s;

    s.sprintf("%6d0", playerScore[0]/10);
    if (rect(0, 1, s).intersects(e->rect())) {
            TQPixmap pix = bitfont->text(s, WHITE, BLACK);
            bitBlt(this, x(0), y(1), &pix);
    }

    s.sprintf("%8d0", HighScore/10);
    if (rect(8, 1, s).intersects(e->rect())) {
            TQPixmap pix = bitfont->text(s, WHITE, BLACK);
            bitBlt(this, x(8), y(1), &pix);
    }

    if (lastScore >= 0) {
        if (rect(1, 4*1.25, i18n("     CONGRATULATIONS      ")).intersects(e->rect())) {
            TQPixmap pix = bitfont->text(i18n("     CONGRATULATIONS      "), YELLOW, BLACK);
            bitBlt(this, x(1), y(4*1.25), &pix);
        }
        if (rect(1, 6*1.25, i18n("    YOU HAVE ARCHIEVED    ")).intersects(e->rect())) {
            TQPixmap pix = bitfont->text(i18n("    YOU HAVE ARCHIEVED    "), CYAN, BLACK);
            bitBlt(this, x(1), y(6*1.25), &pix);
        }
        if (rect(1, 7*1.25, i18n("  A SCORE IN THE TOP 10.  ")).intersects(e->rect())) {
            TQPixmap pix = bitfont->text(i18n("  A SCORE IN THE TOP 10.  "), CYAN, BLACK);
            bitBlt(this, x(1), y(7*1.25), &pix);
        }
        if (rect(1, 8*1.25, i18n("                          ")).intersects(e->rect())) {
            TQPixmap pix = bitfont->text(i18n("                          "), CYAN, BLACK);
            bitBlt(this, x(1), y(8*1.25), &pix);
        }
    }

    if (rect(1, 9.5*1.25, i18n("RNK   SCORE  NAME   DATE")).intersects(e->rect())) {
            TQPixmap pix = bitfont->text(i18n("RNK   SCORE  NAME   DATE"), WHITE, BLACK);
        bitBlt(this, x(1), y(9.5*1.25), &pix);
    }

    for (int i = 0; i < 10; i++) {
        s.sprintf("%2d%9d  %-3.3s  %-8.8s",
            i+1, hallOfFame[i].points, hallOfFame[i].name.utf8().data(),
            formatDate(hallOfFame[i].moment.date()).utf8().data());
        if (rect(1, (11+i)*1.25, s).intersects(e->rect())) {
            TQPixmap pix = bitfont->text(s, (i == lastScore) ? YELLOW : WHITE, BLACK);
            bitBlt(this, x(1), y((11+i)*1.25), &pix);
        }
    }

    if (cursor.x != -1 && cursor.y != -1 && cursor.on) {
        if (rect(cursor.x, (cursor.y*1.25), cursor.chr).intersects(e->rect())) {
            TQPixmap pix = bitfont->text(cursor.chr, BLACK, YELLOW);
            bitBlt(this, x(cursor.x), y(cursor.y*1.25), &pix);
        }
    }

    if (paused) {

        TQPixmap pix = bitfont->text(i18n("PAUSED"), RED, BLACK);
        TQRect r = bitfont->rect(i18n("PAUSED"));
        r.moveCenter(TQPoint(this->width()/2, this->height()/2));

        bitBlt(this, r.x(), r.y(), &pix);
    }
}

void Score::timerEvent(TQTimerEvent *e)
{
    cursor.on = !cursor.on;

    if (paused)
        return;

    if (cursor.x != -1 && cursor.y != -1)
        repaint(rect(cursor.x, cursor.y*1.25, cursor.chr), false);
    scrollRepeat = false;

    if (lastPlayer == 0)
        repaint(rect(1, 0, i18n("  1UP ")), false);

    if (lastPlayer == 1)
        repaint(rect(21, 0, i18n("  2UP ")), false);
}

void Score::keyPressEvent(TQKeyEvent *k)
{
    if (lastScore < 0 || lastPlayer < 0 || lastPlayer >= maxPlayer || paused) {
        k->ignore();
        return;
    }

    int x = cursor.x;
    int y = cursor.y;

    uint key = k->key();

    if (scrollRepeat && (key == UpKey || key == Key_Up || key == DownKey || key == Key_Down)) {
        k->ignore();
        return;
    }

    if (key != Key_Return) {
        if (key == RightKey || key == Key_Right)
            if (++cursor.x > 16)
                cursor.x = 14;
        if (key == LeftKey || key == Key_Left)
            if (--cursor.x < 14)
                cursor.x = 16;
        if (key == UpKey || key == Key_Up)
            if (cursor.chr.unicode() < bitfont->lastChar())
                cursor.chr = cursor.chr.unicode()+1;
            else
                cursor.chr = bitfont->firstChar();
        if (key == DownKey || key == Key_Down)
            if (cursor.chr.unicode() > bitfont->firstChar())
                cursor.chr = cursor.chr.unicode()-1;
            else
                cursor.chr = bitfont->lastChar();

        if (cursor.x == x && cursor.y == y &&
            cursor.chr == hallOfFame[lastScore].name.at(cursor.x-14)) {
            uint ascii = k->ascii();

            if (ascii < bitfont->firstChar() || ascii > bitfont->lastChar())
                ascii = toupper(ascii);

            if (ascii >= bitfont->firstChar() && ascii <= bitfont->lastChar()) {
                cursor.chr = ascii;
                hallOfFame[lastScore].name.at(cursor.x-14) = cursor.chr;
                if (++cursor.x > 16)
                    cursor.x = 14;
            }
        }
    }

    if (key == Key_Return) {
        playerName[lastPlayer] = hallOfFame[lastScore].name;
        write();
        read();
        lastScore = -1;
        lastPlayer = -1;
        cursor.x = -1;
        cursor.y = -1;
        emit gameFinished();
        end();
    }

    if (x != cursor.x || y != cursor.y) {
        if (cursor.x != -1)
            cursor.chr = hallOfFame[lastScore].name.at(cursor.x-14);
        scrollRepeat = false;
        repaint(rect(x, y*1.25, cursor.chr), false);
    } else
        hallOfFame[lastScore].name.at(cursor.x-14) = cursor.chr;

    if (key == UpKey || key == Key_Up || key == DownKey || key == Key_Down)
       scrollRepeat = true;
    else
       repaint(rect(cursor.x, cursor.y*1.25, cursor.chr), false);
}

void Score::initKeys()
{
    TQString up("Up");
    up = kapp->config()->readEntry("upKey", up);
    UpKey = TDEShortcut(up);

    TQString down("Down");
    down = kapp->config()->readEntry("downKey", down);
    DownKey = TDEShortcut(down);

    TQString left("Left");
    left = kapp->config()->readEntry("leftKey", left);
    LeftKey = TDEShortcut(left);

    TQString right("Right");
    right = kapp->config()->readEntry("rightKey", right);
    RightKey = TDEShortcut(right);
}

void Score::confTiming(bool defGroup)
{
    if (defGroup || kapp->config()->hasKey("CursorBlinkMS"))
        cursorBlinkMS = kapp->config()->readNumEntry("CursorBlinkMS", 250);
    if (defGroup || kapp->config()->hasKey("HallOfFameMS"))
        hallOfFameMS = kapp->config()->readNumEntry("HallOfFameMS", 7000);
    if (defGroup || kapp->config()->hasKey("AfterPauseMS"))
        afterPauseMS = kapp->config()->readNumEntry("AfterPauseMS", 1000);
}

void Score::confScheme()
{
    TQString oldgroup = kapp->config()->group();
    TQString newgroup;

    // if not set, read mode and scheme from the configfile
    if (mode == -1 && scheme == -1) {           
        scheme = kapp->config()->readNumEntry("Scheme", -1);
        mode = kapp->config()->readNumEntry("Mode", -1);
        
        // if mode is not set in the defGroup-group, lookup the scheme group
        if (scheme != -1 || mode == -1) {
            newgroup.sprintf("Scheme %d", scheme);
            kapp->config()->setGroup(newgroup);

            mode = kapp->config()->readNumEntry("Mode", -1);
            kapp->config()->setGroup(oldgroup);
        }
    }

    int oldCursorBlinkMS = cursorBlinkMS;

    confTiming();

    if (mode != -1) {
        newgroup.sprintf("Mode %d", mode);
        kapp->config()->setGroup(newgroup);     

        confTiming(false);
    }

    if (scheme != -1) {
        newgroup.sprintf("Scheme %d", scheme);
        kapp->config()->setGroup(newgroup);

        confTiming(false);
    }

    if (cursorBlinkMS != oldCursorBlinkMS) {
        if (cursorBlinkTimer)
            killTimer(cursorBlinkTimer);
        cursorBlinkTimer = startTimer(cursorBlinkMS);
    }

    kapp->config()->setGroup(oldgroup);
}

void Score::setScheme(int Scheme, int Mode, Bitfont *font)
{
    mode = Mode;
    scheme = Scheme;

    confScheme();

    bitfont = font;

    for (int p = 0; p < maxPlayer; p++)
        for (uint i = 0; i < playerName[p].length(); i++)
            if (playerName[p].at(i) < bitfont->firstChar() ||
                playerName[p].at(i) > bitfont->lastChar())
                playerName[p].at(i) = playerName[p].at(i).upper();

    for (int i = 0; i < 10; i++)
        for (uint j = 0; j < hallOfFame[i].name.length(); j++)
            if (hallOfFame[i].name.at(j) < bitfont->firstChar() ||
                hallOfFame[i].name.at(j) > bitfont->lastChar())
                hallOfFame[i].name.at(j) = hallOfFame[i].name.at(j).upper();

    if (cursor.chr.unicode() < bitfont->firstChar() ||
        cursor.chr.unicode() > bitfont->lastChar())
        cursor.chr = cursor.chr.upper();
}

void Score::set(int score)
{
    set(score, 0);
}

void Score::set(int score, int player)
{
    if (player < 0 || player >= maxPlayer)
        return;

    lastPlayer = player;
    playerScore[lastPlayer] = score;

    TQString s;

    s.sprintf("%6d0", playerScore[lastPlayer]/10);
    repaint(rect(0, 1, s), false);

    if (score > HighScore) {
            HighScore = score;
            s.sprintf("%8d0", HighScore/10);
            repaint(rect(8, 1, s), false);
    }
}

/*
 * Set the score for player after the game if over. If the score is in the
 * high scores then the hall of fame is updated (shifted) and the scoreboard
 * is shown.
 */

void Score::setScore(int level, int player)
{
    // pointer to the array-position of a new archived top score
    lastScore = -1;

    // was it a real game or just a demo (level == 0)
    if (player >= 0 && player < maxPlayer && level != 0) {

      lastPlayer = player;

      for (int i = 0; i < 10; i++)
        if ( playerScore[lastPlayer] > hallOfFame[i].points) {
          lastScore = i;
          break;
        }
    }

    // no new highscore archived, finish after specified time
    if (lastScore < 0) {
        // enable gameNew directly for an immediate next try
        emit gameFinished();
        lastPlayer = -1;
        TQTimer::singleShot(hallOfFameMS, this, TQ_SLOT(end()));
        return;
    }

    // shift old (lower) scores to make place for the new one
    for (int i = 9; i > lastScore && i > 0; i--)
        hallOfFame[i] = hallOfFame[i-1];

    hallOfFame[lastScore].points = playerScore[lastPlayer];
    hallOfFame[lastScore].levels = level;
    hallOfFame[lastScore].moment = TQDateTime::currentDateTime();
    hallOfFame[lastScore].name = playerName[lastPlayer];

    cursor.x = 14;
    cursor.y = 11+lastScore;
    cursor.chr = hallOfFame[lastScore].name.at(cursor.x-14);

//  startTimer(cursorBlinkMS);
    setFocus();
}

/*
 * Read the highscores, if no file or a file shorter than 4 bytes (versions before 0.2.4 stores only
 * the points of one highscore) exists - the highscores were initialized with default values.
 */
void Score::read()
{
    if (highscoreFile.exists() && highscoreFile.size() > 4) {
        if (highscoreFile.open(IO_ReadOnly)) {
            TQDataStream s(&highscoreFile);
            char *name;
            for (int i = 0; i < 10; i++) {
                s >> hallOfFame[i].points >> hallOfFame[i].levels >> hallOfFame[i].duration >>
                     hallOfFame[i].moment >> name;
                hallOfFame[i].name = TQString::fromLatin1(name);
                delete(name);
            }
            highscoreFile.close();
        }
    } else {
        for (int i = 0; i < 10; i++) {
            hallOfFame[i].points = 5000;
            hallOfFame[i].levels = 0;
            hallOfFame[i].duration = TQTime();
            hallOfFame[i].moment = TQDateTime();
            hallOfFame[i].name = "???";
        }
        // write();
    }

    for (int i = 0; i < 10; i++)
        for (uint j = 0; j < hallOfFame[i].name.length(); j++)
            if (hallOfFame[i].name.at(j) < bitfont->firstChar() ||
                hallOfFame[i].name.at(j) > bitfont->lastChar())
                hallOfFame[i].name.at(j) = hallOfFame[i].name.at(j).upper();

    HighScore = hallOfFame[0].points;
}

void Score::write()
{
    if (!highscoreFile.exists() && highscoreFile.name() == systemHighscoreFileInfo.filePath())
        KMessageBox::information(0,
                                 i18n("You're going to create the highscore-file\n"
                                 "'%1'\n"
																 "for your maschine, that should be used systemwide.\n"
																 "\n"
                                 "To grant access to the other users, set the appropriate rights (a+w)\n"
                                 "on that file or ask your systemadministator for that favor.\n"
                                 "\n"
                                 "To use a different directory or filename for the highscores,"
                                 "specify them in the configfile (tdepacmanrc:highscoreFilePath)."
                                 ).arg(systemHighscoreFileInfo.filePath()));

    if (highscoreFile.name() == privateHighscoreFileInfo.filePath())
        KMessageBox::information(0,
                                 i18n("You're using a private highscore-file, that's mostly because of\n"
                                 "missing write-access to the systemwide file\n"
                                 "'%1' .\n"
                                 "\n"
                                 "Ask your systemadministrator for granting you access to that file,\n"
                                 "by setting the appropriate rights (a+w) on it.\n"
                                 "\n"
                                 "To use a different directory or filename for the highscores,"
                                 "specify them in the configfile (tdepacmanrc:highscoreFilePath)."
                                 ).arg(systemHighscoreFileInfo.filePath()),
                                 TQString::null, "PrivateHighscore");

    if (highscoreFile.open(IO_WriteOnly)) {
        TQDataStream s(&highscoreFile);
        for (int i = 0; i < 10; i++)
            s << hallOfFame[i].points << hallOfFame[i].levels << hallOfFame[i].duration <<
                 hallOfFame[i].moment << hallOfFame[i].name.latin1();
        highscoreFile.close();
    }
}

void Score::setPause(bool Paused)
{
    paused = Paused;

    TQRect r = bitfont->rect(i18n("PAUSED"));
    r.moveCenter(TQPoint(this->width()/2, this->height()/2));
    repaint(r, true);

    // repaint 1UP or 2UP
    repaint(false);
}

void Score::end()
{
    if (paused) {
        TQTimer::singleShot(afterPauseMS, this, TQ_SLOT(end()));
        return;
    }

    // repaint 1UP or 2UP
    repaint(false);

    // if lastPlayer != -1 we're already in playing mode
    if (lastPlayer == -1)
      emit gameHighscores();
}

/*
 * Return the date in a formatted TQString. The format can be changed using internationalization
 * of the string "YY/MM/DD". Invalid TQDate's where returned as "00/00/00".
 */
TQString Score::formatDate(TQDate date)
{
    TQString s = i18n("@YY@/@MM@/@DD@");

    TQString dd;
    dd.sprintf("%02d", date.isValid() ? date.year() % 100 : 0);
    s.replace(TQRegExp("@YY@"), dd);
    dd.sprintf("%02d", date.isValid() ? date.month() : 0);
    s.replace(TQRegExp("@MM@"), dd);
    dd.sprintf("%02d", date.isValid() ? date.day() : 0);
    s.replace(TQRegExp("@DD@"), dd);

    return s;
}

TQRect Score::rect(int col, float row, TQString str, int align)
{
    TQRect r = bitfont->rect(str);
    r.moveBy(x(col), y(row));

    int dx = 0;
    int dy = 0;

    if (align & AlignLeft || align & AlignRight) {
        dx = (str.length()-1) * (bitfont->width()/2);
        if (align & AlignRight)
            dx *= -1;
    }

    if (align & AlignTop || align & AlignBottom) {
        dy = bitfont->height()/2;
        if (align & AlignBottom)
            dy *= -1;
    }

    if (dx != 0 || dy != 0)
        r.moveBy(dx, dy);

    return r;
}

int Score::x(int col)
{
    return col*bitfont->width();
}

int Score::y(float row)
{
    return (int) (row*(bitfont->height()+bitfont->height()/4));
}

/**
 * Ermittelt die zu benutzende "highscore"-Datei, in die auch geschrieben werden kann.
 * Über den "highscoreFilePath"-TDEConfig-Eintrag, kann abweichend von der Standardlokation
 * der Standort der "highscore"-Datei spezifiziert werden.
 * Wenn die systemweite "highscore"-Datei nicht beschrieben werden kann, wird mit einer
 * privaten Datei gearbeitet.
 */
TQFileInfo Score::locateHighscoreFilePath()
{
    TQFileInfo systemHighscoreDirPath;
    TQStringList systemHighscoreDirs;

    // Schreibfähige "private" highscore-Datei ermitteln für den fallback.
    privateHighscoreFileInfo.setFile(TDEGlobal::dirs()->saveLocation("appdata")+highscoreName);

    // FilePath aus der Konfigurationsdatei benutzen
    systemHighscoreFileInfo.setFile(kapp->config()->readEntry("HighscoreFilePath"));

    // Kein Wert aus der Konfiguration erhalten, dann die "system"-Datei suchen.
    if (systemHighscoreFileInfo.filePath().isEmpty())
        systemHighscoreDirs = TDEGlobal::dirs()->resourceDirs("appdata");
    else
        systemHighscoreDirs = TQStringList(systemHighscoreFileInfo.filePath());
	
    for (TQStringList::Iterator i = systemHighscoreDirs.begin(); i != systemHighscoreDirs.end(); ++i) {

         systemHighscoreFileInfo.setFile(*i);
         if (systemHighscoreFileInfo.fileName().isEmpty())
             systemHighscoreFileInfo.setFile(systemHighscoreFileInfo.dirPath()+"/"+highscoreName);

         // privateHighscoreFileInfo für die "system" Suche ignorieren
         if (systemHighscoreFileInfo.filePath() != privateHighscoreFileInfo.filePath())
         		 if (!systemHighscoreFileInfo.exists()) {
					       systemHighscoreDirPath.setFile(systemHighscoreFileInfo.dirPath());               		
						     if (systemHighscoreDirPath.exists() && systemHighscoreDirPath.isWritable())
         		         return systemHighscoreFileInfo;
         		 } else
         		     if (systemHighscoreFileInfo.isWritable())
         		         return systemHighscoreFileInfo;
    }

    return privateHighscoreFileInfo;
}

#include "score.moc"
