#include "map.h"

Map::Map(QByteArray data, QSize size, QString name, int maxPlayers, int image, QObject *parent) :
    QObject(parent)
{
    QByteArray ba = QByteArray::fromBase64(data);
    ba = qUncompress(ba);
    QDataStream s(ba);
    mapName = name;
    s >> Cells;
    int chei;
    if (Cells.size() <= 0)
        qFatal("Map has invalid width");
    if (Cells.size() != size.width())
        qCritical("Map has wrong width");
    mapSize.setWidth(Cells.size());
    chei = Cells.at(0).size();
    for (int i=0; i<Cells.size(); i++)
    {
        if (Cells[i].size() <= 0)
            qFatal("Map has invalid height");
        if (Cells[i].size() != size.height())
        {
            qCritical("Map has wrong height");
            if (Cells[i].size() != chei)
                qFatal("Corrupted map data");
        }
        for (int j=0; j<Cells[i].size(); j++)
        {
            if (Cells[i][j]==Start) Starts.push_back(QPoint(i,j));
            if (Cells[i][j]==Finish) Finishs.push_back(QPoint(i,j));
        }
    }
    mapSize.setHeight(chei);
    if (Starts.isEmpty() || Finishs.isEmpty()) qFatal("invalid map, not found start/finish");
    // TODO: check for maxPlayers
    players = maxPlayers;
    imageType = image;
}


void Map::printMap()
{
    for (int i=0; i<Cells.size(); i++) qDebug()<<Cells.at(i);
}


void Map::removeAliensPath()
{
    for (int i=0; i<Cells.size(); i++)
    {
        for (int j=0; j<Cells[i].size(); j++)
        {
            if (Cells[i][j] > 1000 && Cells[i][j] < 2000) Cells[i][j]=Cells[i][j]-1000;
        }
    }
}


void Map::delTowerOnMap(int playerId, QPoint pos)
{
    Cells[pos.x()][pos.y()]=Player+playerId;
    Cells[pos.x()-1][pos.y()]=Player+playerId;
    Cells[pos.x()][pos.y()-1]=Player+playerId;
    Cells[pos.x()-1][pos.y()-1]=Player+playerId;
    emit recreateAlienPath(new bool());
}


bool Map::isReachable(int playerId, QPoint pos)
{
    Cells[pos.x()][pos.y()]=PlayerTower-Player-playerId;
    Cells[pos.x()-1][pos.y()]=PlayerTower-Player-playerId;
    Cells[pos.x()][pos.y()-1]=PlayerTower-Player-playerId;
    Cells[pos.x()-1][pos.y()-1]=PlayerTower-Player-playerId;
    for (int i=0; i<Starts.size(); i++) {
        for (int j=0; j<Finishs.size(); j++) {
            if (waveTrace(Starts.at(i),Finishs.at(j))<0) {
                Cells[pos.x()][pos.y()]=Player+playerId;
                Cells[pos.x()-1][pos.y()]=Player+playerId;
                Cells[pos.x()][pos.y()-1]=Player+playerId;
                Cells[pos.x()-1][pos.y()-1]=Player+playerId;
                return false;
            }
        }
    }
    return true;
}


bool Map::addTowerOnMap(int playerId, QPoint pos)
{
    if (playerId < 0 || playerId > players) return false;
    QRect r(QPoint(),mapSize);
    if (!r.contains(pos) ||
        !r.contains(pos-QPoint(1,0)) ||
        !r.contains(pos-QPoint(0,1)) ||
        !r.contains(pos-QPoint(1,1))) return false;
    if (Cells[pos.x()][pos.y()]==Player+playerId &&
        Cells[pos.x()-1][pos.y()]==Player+playerId &&
        Cells[pos.x()][pos.y()-1]==Player+playerId &&
        Cells[pos.x()-1][pos.y()-1]==Player+playerId)
        return isReachable(playerId,pos);
    if ((Cells[pos.x()][pos.y()]==AlienPath+Player+playerId || Cells[pos.x()][pos.y()]==Player+playerId) &&
        (Cells[pos.x()-1][pos.y()]==AlienPath+Player+playerId || Cells[pos.x()-1][pos.y()]==Player+playerId)  &&
        (Cells[pos.x()][pos.y()-1]==AlienPath+Player+playerId || Cells[pos.x()][pos.y()-1]==Player+playerId)  &&
        (Cells[pos.x()-1][pos.y()-1]==AlienPath+Player+playerId || Cells[pos.x()-1][pos.y()-1]==Player+playerId))
    {
        if (!isReachable(playerId,pos)) return false;
        bool * pathOK;
        pathOK = new bool(true);
        emit recreateAlienPath(pathOK);
//        qDebug() << *pathOK;
        if (*pathOK)
            return true;
        else
            delTowerOnMap(playerId,pos);
    }
    qWarning("Dont buid here");
    return false;
}


QVector<QPointF> Map::createPath(QPoint start, QPoint finish)
{
    QPointF tp;
    QVector<QPoint> srcPath;
    QVector<QPointF> tmpPath;
    QVector<QPointF> path;
    if (Cells[start.x()][start.y()] < 0)
    {
        qCritical("ERROR invalid start");
        return path;
    }
    if (Cells[finish.x()][finish.y()] < 0)
    {
        qCritical("ERROR invalid finish");
        return path;
    }
    srcPath = invWaveTrace(finish,waveTrace(start,finish));
    for (int i=0; i<srcPath.size();i++)
    {
        int x = Cells.at(srcPath.at(i).x()).at(srcPath.at(i).y());
        if (x > 0 && x < 1000) x = x+1000;
        Cells[srcPath.at(i).x()][srcPath.at(i).y()] = x;
        tmpPath.push_back(QPointF(srcPath.at(i)));
    }
    srcPath.clear();
    if (!tmpPath.isEmpty())
    {
        for (int j=0; j<4; j++)
        {
            path.clear();
            tp = tmpPath.at(0);
            path.push_back(tp);
            for (int i = 0; i < tmpPath.size() - 1; i++)
            {
                tp.setX((tmpPath.at(i).x() + tmpPath.at(i + 1).x()) / 2.0);
                tp.setY((tmpPath.at(i).y() + tmpPath.at(i + 1).y()) / 2.0);
                path.push_back(tp);
            }
            tp = tmpPath.at(tmpPath.size() - 1);
            path.push_back(tp);
            tmpPath = path;
        }
        tmpPath.clear();
        if (path.size() >= 6)
        {
            path.remove(1);
            path.remove(1);
            path.remove(path.size()-2);
            path.remove(path.size()-2);
        }
        return path;
    }
    path.clear();
    return path;
}


int Map::waveTrace(QPoint start, QPoint finish)
{
    bool stop = false;
    int step = 2;
    QPoint cp, tp;
    QRect fr(QPoint(), mapSize);
    QVector<QPoint> tmpp, curp;
    TmpCells << Cells;
    for (int i = 0; i < Cells.size(); i++)
    {
        for (int j = 0; j < Cells.at(i).size(); j++)
        {
            if (Cells[i][j] < 0 ) TmpCells[i][j] = -1;
            else TmpCells[i][j] = 0;
        }
    }
    cp = start;
    curp.push_back(cp);
    TmpCells[cp.x()][cp.y()] = 1;
    //qDebug() << "trace";
    while (!stop) {
        tmpp = curp;
        curp.clear();
        stop = true;
        for (int i = 0; i < tmpp.size(); i++) {
            cp = tmpp.at(i);
            if (cp == finish) {
                TmpCells[cp.x()][cp.y()] = step;
                //qDebug() << "trace done!";
                return step;
            }
            tp.setX(cp.x() - 1);
            tp.setY(cp.y());
            if (fr.contains(tp)) {
                if (TmpCells[tp.x()][tp.y()] == 0) {
                    TmpCells[tp.x()][tp.y()] = step;
                    curp.push_back(tp);
                    stop = false;
                }
            }
            tp.setX(cp.x() + 1);
            if (fr.contains(tp)) {
                if (TmpCells[tp.x()][tp.y()] == 0) {
                    TmpCells[tp.x()][tp.y()] = step;
                    curp.push_back(tp);
                    stop = false;
                }
            }
            tp.setX(cp.x());
            tp.setY(cp.y() - 1);
            if (fr.contains(tp)) {
                if (TmpCells[tp.x()][tp.y()] == 0) {
                    TmpCells[tp.x()][tp.y()] = step;
                    curp.push_back(tp);
                    stop = false;
                }
            }
            tp.setY(cp.y() + 1);
            if (fr.contains(tp)) {
                if (TmpCells[tp.x()][tp.y()] == 0) {
                    TmpCells[tp.x()][tp.y()] = step;
                    curp.push_back(tp);
                    stop = false;
                }
            }
        }
        step++;
    }
    qDebug() << "trace false";
    return -1;
}


QVector<QPoint> Map::invWaveTrace(QPoint finish, int cnt)
{
    QPoint wp, Ppnt;
    QVector<QPoint> alpath;
    if (cnt < 2) return alpath;
    int Ind, c, xpp, ypp, xnn, ynn;
    unsigned char chk;
    Ppnt = wp = finish;
    xnn=0;
    xpp=0;
    ynn=0;
    ypp=0;
    cnt--;
    alpath.push_front(Ppnt);
    while (cnt > 1)
    {
        cnt--;
        chk = 0;
        Ind = 0;
        c = 0;
        if (wp.x() - 1 >= 0 && TmpCells[wp.x()-1][wp.y()] == cnt)
        {
            chk = chk | 0x01;
            c++;
        }
        if (wp.x() + 1 < mapSize.width() && TmpCells[wp.x()+1][wp.y()] == cnt)
        {
            chk = chk | 0x02;
            c++;
        }
        if (wp.y() - 1 >= 0 && TmpCells[wp.x()][wp.y()-1] == cnt)
        {
            chk = chk | 0x04;
            c++;
        }
        if (wp.y() + 1 < mapSize.height() && TmpCells[wp.x()][wp.y()+1] == cnt)
        {
            chk = chk | 0x08;
            c++;
        }
        if (c == 0 || chk == 0) qDebug("ERROR in InvWaveTrace"),exit(-1);
        if (c > 1)
        {
            if ((chk & 0x01)==0x01 && (chk & 0x04)==0x04)
            {
                if (xnn <= ynn && Ind == 0){
                    wp.rx()--;
                    xnn++;
                    if (xnn == ynn) xnn++;
                    Ind = 1;
                } else if (Ind == 0) {
                    wp.ry()--;
                    ynn++;
                    ynn++;
                    Ind = 1;
                }
            }
            if ((chk & 0x02)==0x02 && (chk & 0x04)==0x04)
            {
                if (xpp <= ynn && Ind == 0){
                    wp.rx()++;
                    xpp++;
                    if (xpp == ynn) xpp++;
                    Ind = 1;
                } else if (Ind == 0) {
                    wp.ry()--;
                    ynn++;
                    ynn++;
                    Ind = 1;
                }
            }
            if ((chk & 0x01)==0x01 && (chk & 0x08)==0x08)
            {
                if (xnn <= ypp && Ind == 0){
                    wp.rx()--;
                    xnn++;
                    if (xnn == ypp) xnn++;
                    Ind = 1;
                } else if (Ind == 0) {
                    wp.ry()++;
                    ypp++;
                    ypp++;
                    Ind = 1;
                }
            }
            if ((chk & 0x02)==0x02 && (chk & 0x08)==0x08)
            {
                if (xpp <= ypp && Ind == 0){
                    wp.rx()++;
                    xpp++;
                    if (xpp == ypp) xpp++;
                    Ind = 1;
                } else if (Ind == 0) {
                    wp.ry()++;
                    ypp++;
                    ypp++;
                    Ind = 1;
                }
            }
        }
        if (c == 1 || Ind == 0)
        {
            xnn=0;
            xpp=0;
            ynn=0;
            ypp=0;
            if ((chk & 0x01)==0x01) {
                wp.rx()--;
                xnn++;
            }
            else if ((chk & 0x02)==0x02) {
                wp.rx()++;
                xpp++;
            }
            else if ((chk & 0x04)==0x04) {
                wp.ry()--;
                ynn++;
            }
            else if ((chk & 0x08)==0x08) {
                wp.ry()++;
                ypp++;
            }
        }
        Ppnt = wp;
        alpath.push_front(Ppnt);
    }
    return alpath;
}

