#include <kapp.h>

#include <tdelocale.h>

#include <tqrect.h>
#include <tqregexp.h>
#include <tqmessagebox.h>
#include <tqfile.h>
#include <tqtextstream.h>

#include "board.h"
#include "bitmaps.h"

Board::Board(int size) : TQMemArray<int> (size)
{
    sz = size;                          // set size of board

    map = "";                   
    mapName = "";                       // no map loaded so far

    init(None);                         // initialize varibales
}

void Board::init(Image image, TQString levelName)
{
    prisonEntry = OUT;
    prisonExit = OUT;
    fruitHome = OUT;
    fruitPosition = OUT;
    pacmanHome = OUT;
    pacmanPosition = OUT;
    for (int m = 0; m < 8; m++) {
        monsterHome[m] = OUT;
        monsterPosition[m] = OUT;
    }
    for (int e = 0; e < 8; e++) {
        energizerPosition[e] = OUT;
    }
    for (int e = 0; e < 8; e++) {
        tunnelPosition[e] = OUT;
    }

    fill(0);
    numPoints = 0;
    numEnergizers = 0;
    numMonsters = 0;
    numTunnels = 0;

    if (!levelName.isNull() && !levelName.isEmpty())
        if (mapName == levelName)
            image = File;
        else {
            TQFile levelFile(levelName);
            if (!levelFile.open(IO_ReadOnly)) {

                TQString msg = i18n("The levelmap could not be constructed.\n\n"
                                   "The file '@LEVELNAME@' does not exist,\n"
                                   "or could not be opened for reading.");
                msg.replace(TQRegExp("@LEVELNAME@"), levelName);
                // TQMessageBox::information(0, i18n("Initialization Error"), msg);
                printf("%s\n", msg.local8Bit().data());
            } else {
                map.fill(' ', BoardHeight*BoardWidth);
                int height = 0; 

                TQTextStream levelStream(&levelFile);
                while (!levelStream.eof() && height < BoardHeight) {
                    TQString line = levelStream.readLine();
                
                    if (line.find(TQRegExp("^ *;")) == -1) {     

                        line.replace(TQRegExp(";.*"), "");       // strip off comments
                        line.replace(TQRegExp("\" *$"), "");     // strip off trailing "
                        line.replace(TQRegExp("^ *\""), "");     // strip off leading "

                        map.replace(height*BoardWidth,
                                    (line.length() > BoardWidth) ? BoardWidth : line.length(),
                                    line.latin1());

                        height++;
                    }
                }
                mapName = levelName;
                levelFile.close();
                image = File;
            }
        }

    switch (image) {
        case Intro : // setup(demo_bits);
                     break;
        case Demo  : setup(demo_bits);
                     break;
        case Level : setup(demo_bits);
                     break;
        case File  : setup(reinterpret_cast<const unsigned char *>(map.latin1()));
                     break;
        default    : break;
    }
}

void Board::setup(const uchar *buf)
{
    for ( int index = 0; buf[index] != 0 && index < BoardWidth*BoardHeight; index++ ) {
        switch (buf[index]) {
            case '*' : set(index, brick); break;
            case '+' : set(index, out); break;
            case '#' : set(index, prison); break;
            case '-' : set(index, gate); break;
            case 'E' : set(index, tunnel); break;
            case '.' : set(index, Point); break;
            case 'o' : set(index, energizer); break;
            case 'I' : set(index, prisonentry); break;
            case 'O' : set(index, prisonexit); break;
            case 'F' : set(index, fruithome); break;
            case 'P' : set(index, pacmanhome);  break;
            default  : if (buf[index] >= '0' && buf[index] <= '7') {
                           set(index, monsterhome, buf[index]-(uchar)'0');
                       }
        }
    }
}

bool Board::inBounds(int pos)
{
  return ( pos < 0 || pos > sz-1 ? FALSE : TRUE);
}

void Board::set(int pos, Square sq, int m)
{
    if (inBounds(pos))
        switch (sq) {
            case out         : at(pos) = OUT; break;
            case Point       : at(pos) |= pointBit; numPoints++; break;
            case tunnel      : at(pos) = sq;
                               for (int e = 0; e < numTunnels; e++) {           // if tunnel is already on board
                                   if (tunnelPosition[e] == pos)                // don't do it again.
                                       pos = OUT;
                               }
                               if (pos != OUT) {
                                   tunnelPosition[numTunnels] = pos;
                                   numTunnels++;
                               }
                               break;
            case energizer   : at(pos) |= energizerBit;
                               for (int e = 0; e < numEnergizers; e++) {
                                   if (energizerPosition[e] == pos)     
                                       pos = OUT;
                               }
                               if (pos != OUT) {
                                   energizerPosition[numEnergizers] = pos;
                                   numEnergizers++;
                               }
                               break;
            case fruit       : at(pos) |= fruitBit; fruitPosition = pos; break;
            case pacman      : at(pos) |= pacmanBit; pacmanPosition = pos; break;
            case monster     : at(pos) |= (monsterBit << m); 
                               monsterPosition[m] = pos; break;
            case prisonentry : prisonEntry = pos; at(pos) = empty; break;
            case prisonexit  : prisonExit = pos; at(pos) = empty; break;
            case fruithome   : fruitHome = pos; at(pos) = empty; break;
            case pacmanhome  : pacmanHome = pos; at(pos) = empty; break;
            case monsterhome : monsterHome[m] = pos; at(pos) = empty; 
                               if (m == 0 && prisonExit == OUT)
                                   prisonExit = pos;
                               if (m == 1 && prisonEntry == OUT)
                                   prisonEntry = pos;
                               numMonsters++;
                               break;
            default          : at(pos) = sq;
        }
}

void Board::reset(int pos, Square sq, int m)
{
    bool found = FALSE;
    if (inBounds(pos))
        switch (sq) {
            case out       : at(pos) = empty; break;
            case Point     : at(pos) &= ~ pointBit; numPoints--; break;
            case energizer : at(pos) &= ~ energizerBit;
                             for (int e = 0; e < numEnergizers; e++) {          // delete the position of the eaten
                                 if (found)                                     // energizer in the position array
                                     energizerPosition[e-1] = energizerPosition[e];
                                 if (energizerPosition[e] == pos)               
                                     found = TRUE;
                             }
                             energizerPosition[numEnergizers--] = OUT;
                             break;
            case fruit     : at(pos) &= ~ fruitBit; fruitPosition = OUT; break;
            case pacman    : at(pos) &= ~ pacmanBit; pacmanPosition = OUT; break;
            case monster   : at(pos) &= ~ (monsterBit << m);
                             monsterPosition[m] = OUT; break;
            default        : at(pos) = at(pos) & varBits;
        }
}

int Board::position(Square sq, int m)
{
    switch(sq) {
        case prisonentry  : return prisonEntry;
        case prisonexit   : return prisonExit;
        case fruit        : return fruitPosition;
        case fruithome    : return fruitHome;
        case pacman       : return pacmanPosition;
        case pacmanhome   : return pacmanHome;
        case monster      : return monsterPosition[m];
        case monsterhome  : return monsterHome[m];
        case energizer    : return energizerPosition[m];
        case tunnel       : return tunnelPosition[m];
        default           : return OUT;
    }
}

bool Board::isOut(int pos)
{
    if (inBounds(pos))
        return (at(pos) == OUT ? TRUE : FALSE);
    return TRUE;
}

bool Board::isEmpty(int pos)
{
    if (inBounds(pos))
        return ((at(pos) & fixBits) == empty ? TRUE : FALSE);
    return TRUE;
}

bool Board::isBrick(int pos)
{
    if (inBounds(pos))
        return ((at(pos) & fixBits) == brick ? TRUE : FALSE);
    return FALSE;
}

bool Board::isPrison(int pos)
{
    if (inBounds(pos))
        return ((at(pos) & fixBits) == prison ? TRUE : FALSE);
    return FALSE;
}

bool Board::isGate(int pos)
{
    if (inBounds(pos))
        return ((at(pos) & fixBits) == gate ? TRUE : FALSE);
    return FALSE;
}

bool Board::isTunnel(int pos)
{
    if (inBounds(pos))
        return ((at(pos) & fixBits) == tunnel ? TRUE : FALSE);
    return FALSE;
}

bool Board::isPoint(int pos)
{
    if (inBounds(pos) && at(pos) != OUT)
        return ((at(pos) & pointBit) != 0 ? TRUE : FALSE);
    return FALSE;
}

bool Board::isEnergizer(int pos)
{
    if (inBounds(pos) && at(pos) != OUT)
        return ((at(pos) & energizerBit) != 0 ? TRUE : FALSE);
    return FALSE;
}

bool Board::isFruit(int pos)
{
    if (inBounds(pos) && at(pos) != OUT)
        return ((at(pos) & fruitBit) != 0 ? TRUE : FALSE);
    return FALSE;
}

bool Board::isPacman(int pos)
{
    if (inBounds(pos) && at(pos) != OUT)
        return ((at(pos) & pacmanBit) != 0 ? TRUE : FALSE);
    return FALSE;
}

bool Board::isMonster(int pos)
{
    if (inBounds(pos) && at(pos) != OUT)
        return ((at(pos) & monsterBits) != 0 ? TRUE : FALSE);
    return FALSE;
}

bool Board::isWay(int pos, int dir, Square sq) {
    int p1 = move(pos, dir, 2);
    if (p1 == OUT)
        return (sq == out ? TRUE : FALSE);
    int p2, p3;
    if (dir == N || dir == S) {
        p2 = move(p1, E);
        p3 = move(p1, W);
    } else {
        p2 = move(p1, N);
        p3 = move(p1, S);
    }
    switch (sq) {
        case out    : return isOut(p1) | isOut(p2) | isOut(p3); 
        case empty  : return isEmpty(p1) & isEmpty(p2) & isEmpty(p3);
        case brick  : return isBrick(p1) | isBrick(p2) | isBrick(p3);
        case prison : return isPrison(p1) | isPrison(p2) | isPrison(p3);
        case gate   : return isGate(p1) & isGate(p2) & isGate(p3);
        case tunnel : return isTunnel(p1) &
                             (isTunnel(p2) || isEmpty(p2)) &
                             (isTunnel(p3) || isEmpty(p3));
        default     : return FALSE;
    }
}

bool Board::isJump(int pos, int dir) {
    switch (dir) {
        case NW: return pos < BoardWidth || x(pos) == 0;
        case N:  return pos < BoardWidth;
        case NE: return pos < BoardWidth || x(pos) == BoardWidth-1;
        case W:  return x(pos) == 0;
        case E:  return x(pos) == BoardWidth-1;
        case SW: return pos >= sz-BoardWidth || x(pos) == 0;
        case S:  return pos >= sz-BoardWidth;
        case SE: return pos >= sz-BoardWidth || x(pos) == BoardWidth-1;
    }
    return FALSE;
}

int Board::move(int pos, int dir, int steps)
{
    if (steps < 0) {                    // move backwards
        dir = turn(dir);                // turn around and do your steps
        steps *= -1;
    }

    while (steps-- != 0) {              // until all steps are gone
        switch (dir) {
            case NW: pos = pos >= BoardWidth && x(pos) > 0 ? (pos-BoardWidth)-1 : sz-1;
                     break;
            case N:  pos = pos >= BoardWidth ? pos-BoardWidth : (sz-BoardWidth)+x(pos);
                     break;
            case NE: pos = pos >= BoardWidth && x(pos) < BoardWidth-1 ?
                        (pos-BoardWidth)+1 : sz-BoardWidth;
                     break;
            case W:  pos = x(pos) > 0 ? pos-1 : pos+(BoardWidth-1);
                     break;
            case E:  pos = x(pos) < BoardWidth-1 ? pos+1 : pos-(BoardWidth-1);
                     break;
            case SW: pos = pos < sz-BoardWidth && x(pos) > 0 ? (pos+BoardWidth)-1 : BoardWidth-1;
                     break;
            case S:  pos = pos < sz-BoardWidth ? pos+BoardWidth : x(pos);
                     break;
            case SE: pos = pos < sz-BoardWidth && x(pos) < BoardWidth-1 ? (pos+BoardWidth)+1 : 0;
                     break;
        }
    }
    return pos;                         // here we are
}

int Board::closeup(int pos, int dir, int target)
{
    if (dir == N || dir == S) {
        if (x(target) < x(pos))
            return W;
        if (x(target) > x(pos))
            return E;
    } else {
        if (y(target) < y(pos))
            return N;
        if (y(target) > y(pos))
            return S;
    }
    return dir;
}

int Board::x(int pos)
{
    return pos % BoardWidth;
}

int Board::y(int pos)
{
    return pos/BoardWidth;
}

int Board::turn(int dir)
{
    switch (dir) {
        case N  : return S;
        case NE : return SW;
        case E  : return W;
        case SE : return NW;
        case S  : return N;
        case SW : return NE;
        case W  : return E;
        case NW : return SE;
        default : return dir;
    }
}

int Board::points()
{
    return numPoints;
}

int Board::energizers()
{
    return numEnergizers;
}

int Board::monsters()
{
    return numMonsters;
}

int Board::tunnels()
{
    return numTunnels;
}
