#include <QTime>
#include <QTimer>
#include <QtXml/QDomDocument>
#include <QtXml/QDomNode>
//#include <Phonon>

using namespace std;

#include <cstdlib>

#include <QDebug>

#include "EvidenceHuntGame.h"

void tilesPropAppend(QDeclarativeListProperty<TileData>* prop, TileData* value) {
    Q_UNUSED(prop);
    Q_UNUSED(value);
    return; //Append not supported
}

int tilesPropCount(QDeclarativeListProperty<TileData>* prop) {
    return static_cast<QList<TileData*>*>(prop->data)->count();
}

TileData* tilesPropAt(QDeclarativeListProperty<TileData>* prop, int index) {
    return static_cast<QList<TileData*>*>(prop->data)->at(index);
}

QDeclarativeListProperty<TileData> EvidencehuntGame::tiles() {
    return QDeclarativeListProperty<TileData>(this, &_tiles, &tilesPropAppend, &tilesPropCount, &tilesPropAt, 0);
}

void suspectsPropAppend(QDeclarativeListProperty<SuspectData>* prop, SuspectData* value) {
    Q_UNUSED(prop);
    Q_UNUSED(value);
    return;
}

int suspectsPropCount(QDeclarativeListProperty<SuspectData>* prop) {
    return static_cast<QList<SuspectData*>*>(prop->data)->count();
}

SuspectData* suspectsPropAt(QDeclarativeListProperty<SuspectData>* prop, int index) {
    return static_cast<QList<SuspectData*>*>(prop->data)->at(index);
}

QDeclarativeListProperty<SuspectData> EvidencehuntGame::suspects(){
    return QDeclarativeListProperty<SuspectData>(this, &_suspects, &suspectsPropAppend, &suspectsPropCount, &suspectsPropAt, 0);
}

void playersPropAppend(QDeclarativeListProperty<TopPlayerData>* prop, TopPlayerData* value) {
    Q_UNUSED(prop);
    Q_UNUSED(value);
    return;
}

int playersPropCount(QDeclarativeListProperty<TopPlayerData>* prop) {
    return static_cast<QList<TopPlayerData*>*>(prop->data)->count();
}

TopPlayerData* playersPropAt(QDeclarativeListProperty<TopPlayerData>* prop, int index) {
    return static_cast<QList<TopPlayerData*>*>(prop->data)->at(index);
}


QDeclarativeListProperty<TopPlayerData> EvidencehuntGame::topPlayers(){

    /*
    QList< TopPlayerData *>::iterator i;
    for (i = _players.begin(); i != _players.end(); ++i) {
        TopPlayerData *currentPlayerData = *i;
        qDebug() << currentPlayerData->name();
    }
    */

    return QDeclarativeListProperty<TopPlayerData>(this, &_players, &playersPropAppend, &playersPropCount, &playersPropAt, 0);
}

void EvidenceInfo::loadEvidence(TileData *t) {
    _title = t->type()->description();
    _type = t->type();
    _description = t->description();
    _image = t->type()->image();
    _isVisible = true;
    emit evidenceChanged();
}

void EvidenceInfo::loadCustomMessage(QString title, EvidenceType *type, QString message, QString image) {
    _title = title;
    _type = type;
    _description = message;
    _image = image;
    _isVisible = true;
    emit evidenceChanged();
}

EvidencehuntGame::EvidencehuntGame(QApplication *app)
: numCols(8), numRows(8), numSuspects(5), playing(true), won(false), defaultScore(5), scoreMultiplier(1), wonStreak(0), isSolveMode(false)
{

    _app = app;
    _settings = new QSettings();
    _translator = new QTranslator();

    _app->installTranslator(_translator);

    setObjectName("mainObject");
    srand(QTime(0,0,0).secsTo(QTime::currentTime()));

    tileTypes << new EvidenceType("grey","#ffffff",0,"empty.png") //0 Blank fields
              << new EvidenceType("red","#ff0000",1,"terrible-murder.jpg") //1
              << new EvidenceType("red","#ff0000",1,"knife-gun.jpg") //2
              << new EvidenceType("blue","#0000ff",8,"evidence-elimination.jpg") //3
              << new EvidenceType("green","#00ff00",2,"evidence-incriminating.jpg") //4
              << new EvidenceType("purple","#ff00ff",1,"detective-office.jpg") //5
              << new EvidenceType("purple","#ff00ff",1,"alibi.jpg") //6
              << new EvidenceType("purple","#ff00ff",1,"detective-office.jpg") //7
              << new EvidenceType("yellow","#ffff00",10,"evidence-suspect.jpg") //8
              << new EvidenceType("purple","#ff00ff",1,"boss.jpg"); //9

    loadLanguage();

    //initialize array
    for(int ii = 0; ii < numRows * numCols; ++ii) {
        _tiles << new TileData;
    }
    for(int ii = 0; ii < numSuspects; ++ii) {
        _suspects << new SuspectData();
    }

    _evidenceInfo = new EvidenceInfo();

    connect(_evidenceInfo, SIGNAL(evidenceChanged()), this, SLOT(changeEvidenceInfo()));

    _settings->setValue("currentScore", 0);

    _scoreboard = new Scoreboard();

    connect(_scoreboard, SIGNAL(boardChanged(QList< QHash<QString,QString> >)), this, SLOT(loadScoreboardPlayers(QList< QHash<QString,QString> >)));
    connect(_scoreboard, SIGNAL(boardError()), this, SIGNAL(openBoardErrorDialog()));
    connect(_scoreboard, SIGNAL(boardSubmitSuccess(int)), this, SLOT(boardSubmitSuccess(int)));

    //Phonon::MediaObject *music = Phonon::createPlayer(Phonon::MusicCategory,
    //#if defined(Q_EVHUNT_MEEGO)
    //Phonon::MediaSource("/opt/evidencehunt/music/music.mp3"));
    //#else
    //Phonon::MediaSource("music/music.mp3"));
    //#endif
    //music->play();

    reset();

}

void EvidencehuntGame::loadLanguage() {

    if (_settings->value("lang")!="en_US" && _settings->value("lang")!="hu_HU" && _settings->value("lang")!="sr_RS" && _settings->value("lang")!="it_IT") {
        _settings->setValue("lang", "en_US");
    }

    if (!_translator->load("evidencehunt." + _settings->value("lang").toString() + "qm", ":/qml/")) {
        _translator->load("evidencehunt." + _settings->value("lang").toString() + "qm", ":/qml/");
    }

    roleTypes = loadFromXml(":/internationalization/"+_settings->value("lang").toString().left(2)+"/roleTypes.xml");

    foreach(SuspectData* s, _suspects) {
        s->reloadEvidences();
    }

    tileTypes[0]->setDescription("");
    tileTypes[1]->setDescription(tr("__SHOCKINGMURDER__"));
    tileTypes[2]->setDescription(tr("__MURDERWEAPON__"));
    tileTypes[3]->setDescription(tr("__ELIMINATIONCLUE__"));
    tileTypes[4]->setDescription(tr("__INCRIMINATINGCLUE__"));
    tileTypes[5]->setDescription(tr("__HIGHPROFILE__"));
    tileTypes[6]->setDescription(tr("__AIRTIGHTALIBI__"));
    tileTypes[7]->setDescription(tr("__SEARCHWARRANT__"));
    tileTypes[8]->setDescription(tr("__SUSPECTINFO__"));
    tileTypes[9]->setDescription(tr("__LARGEREWARD__"));

    suspectProperties = loadFromXml(":/internationalization/"+_settings->value("lang").toString().left(2)+"/suspectProperties.xml");
    suspectNegativeProperties = loadFromXml(":/internationalization/"+_settings->value("lang").toString().left(2)+"/suspectNegativeProperties.xml");
    startScenes = loadFromXml(":/internationalization/"+_settings->value("lang").toString().left(2)+"/startScenes.xml");

    refreshLanguage();

    emit languageChanged();

}

void EvidencehuntGame::refreshLanguage(bool loadStartScene) {

    foreach(SuspectData *s, _suspects) {
        s->setRole(s->role(), roleTypes[s->role()]);
    }

    foreach(TileData* t, _tiles) {

        if (t->type()->id()==1) {
            t->setDescription(startScenes[startScene]+"\n\n" + tr("__SUSPECTNUMBER__").arg(QString::number(numSuspects))+"!") ;
            if (loadStartScene) {
                _evidenceInfo->loadEvidence(t);
            }
        }

        if (t->type()->id()==2) {
            t->setDescription(tr("__FOUNDMURDERWEAPON__") + ".\n\n" + tr("__YOURREWARD__").arg("50"));
        }

        if (t->type()->id()==3) {
            t->setDescription(tr("__KILLER__") + " " + suspectNegativeProperties[t->propertyId()] + "." );
        }

        if (t->type()->id()==4) {
            t->setDescription(tr("__KILLER__") + " " + suspectProperties[t->propertyId()] + ".");
        }

        if (t->type()->id()==5) {
            t->setDescription(tr("__DOUBLEREWARD__"));
        }

        if (t->type()->id()==6) {
            t->setDescription(tr("__HASALIBI__").arg(_suspects[alibiId]->roleText())+"!");
        }

        if (t->type()->id()==7) {
            t->setDescription(tr("__EXTRATIME__"));
        }

        if (t->type()->id()==8) {
            t->setDescription(_suspects[t->suspectId()]->roleText() + " " + suspectProperties[t->propertyId()]+ ".");
        }

        if (t->type()->id()==9) {
            t->setDescription(tr("__FOUNDLARGEREWARD__")+ "\n\n" + tr("__YOURREWARD__").arg("100"));
        }

    }

}

QList<QString> EvidencehuntGame::loadFromXml(QString fileString) {
    QList<QString> xmlData;
    QDomDocument doc("suspectProperties");
    QFile file(fileString);
    file.open(QIODevice::ReadOnly);
    doc.setContent(&file);
    file.close();
    QDomElement docElem = doc.documentElement();
    QDomNode n = docElem.firstChild();
    while(!n.isNull()) {
        QDomElement e = n.toElement();
        if(!e.isNull()) {
            xmlData << e.text();
        }
        n = n.nextSibling();
    }
    return xmlData;
}

void EvidencehuntGame::setBoard() {

    /*
    _settings->setValue("lastScore", 0);
    _settings->setValue("currentScore", 0);
    _settings->setValue("maxScore", 0);
    _settings->setValue("maxScoreSubmitted", 0);
    */

    foreach(TileData* t, _tiles) {
        t->setHasEvidence(false);
        t->setHint(-1);
        t->setType(tileTypes[0]);
        t->setDescription("");
        t->setPropertyId(-1);
        //t->flip();
    }

    QList<int> usedRoles;
    QList<int> usedProfiles;

    killerId = rand() % numSuspects;

    //qDebug() << killerId;

    emit killerChanged();

    int counter = 0;
    foreach(SuspectData* s, _suspects) {

        int profileId = rand() % 11;
        while (usedProfiles.contains(profileId)) {
            profileId = rand() % 11;
        }
        s->resetEvidences();
        s->setProfile(profileId);
        usedProfiles << profileId;

        int roleId = rand() % roleTypes.count();
        while (usedRoles.contains(roleId)) {
            roleId = rand() % roleTypes.count();
        }
        s->setRole(roleId, roleTypes[roleId]);
        usedRoles << roleId;

        if (counter==killerId) {
            s->setIsKiller(true);
        } else {
            s->setIsKiller(false);
        }

        counter++;
    }

    if (!won) {
        _settings->setValue("currentScore", 0);
        emit totalScoreChanged();
        wonStreak = 0;
        emit wonStreakChanged();
    }

    won = false;
    emit hasWonChanged();

    scoreMultiplier = 1;

    score = totalScore();
    emit currentScoreChanged();

    remaining = 35-wonStreak*3;
    if (remaining < 20) {
        remaining = 20;
    }
    emit timeRemainingChanged();

    //Assign game properties
    QList<int> usedProperties;
    while (usedProperties.count()<10) {
        int propertyId = rand() % suspectProperties.count();
        while (usedProperties.contains(propertyId)) {
            propertyId = rand() % suspectProperties.count();
        }
        usedProperties << propertyId;
    }

    int incriminatingInfo = (rand() % 2) + 3;

    fEvidences = 0;
    emit foundEvidencesChanged();

    nEvidences = -1;

    int evidenceTypeId = 0;
    foreach (EvidenceType *e, tileTypes) {

        e->setId(evidenceTypeId);
        int evidences = e->number();

        while ( evidences ) {

            int col = int((double(rand()) / double(RAND_MAX)) * numCols);
            int row = int((double(rand()) / double(RAND_MAX)) * numRows);

            TileData* t = tile(row,col);

            if (t && !t->hasEvidence()) {

                t->setHasEvidence( true );
                t->setType(e);

                if (evidenceTypeId==1) {
                    startScene = rand() % startScenes.count();
                    t->flip();
                }

                if (evidenceTypeId==3) {
                    t->setPropertyId(usedProperties[evidences+1]);
                }

                if (evidenceTypeId==4) {
                    if (evidences == 2) {
                        t->setPropertyId(usedProperties[0]);
                    } else {
                        t->setPropertyId(usedProperties[1]);
                    }
                }

                if (evidenceTypeId==6) {
                    alibiId = rand() % numSuspects;
                    while (alibiId==killerId) {
                        alibiId = rand() % numSuspects;
                    }
                }

                if (evidenceTypeId==8) {
                    int evidenceProperty = -1;
                    int evidenceSuspect = -1;
                    if (evidences == 10) {
                        evidenceProperty = 0;
                        evidenceSuspect = killerId;
                    } else if (evidences == 9) {
                        evidenceProperty = 1;
                        evidenceSuspect = killerId;
                    } else if (evidences > (10 - incriminatingInfo)) {
                        int suspectId = rand() % numSuspects;
                        while (suspectId==killerId) {
                            suspectId = rand() % numSuspects;
                        }
                        int evidenceId = rand() % 2;
                        if (_suspects[suspectId]->evidences().contains(evidenceId)) {
                            evidenceId = evidenceId == 1 ? 0 : 1;
                        }
                        evidenceProperty = evidenceId;
                        evidenceSuspect = suspectId;
                    } else {
                        int suspectCounter = 0;
                        foreach(SuspectData* s, _suspects) {
                            if (s->evidences().count() < 2) {
                                int evidenceId = (rand()%8)+2;
                                while (s->evidences().contains(evidenceId)) {
                                    evidenceId = (rand()%8)+2;
                                }
                                evidenceProperty = evidenceId;
                                evidenceSuspect = suspectCounter;
                                break;
                            }
                            suspectCounter++;
                        }
                    }

                    _suspects[evidenceSuspect]->assignEvidence(evidenceProperty);
                    t->setSuspectId(evidenceSuspect);
                    t->setPropertyId(usedProperties[evidenceProperty]);

                }

                evidences--;
                nEvidences++;

            }

    }
        evidenceTypeId++;
}
    emit numEvidencesChanged();

    //Set hints
    for (int r = 0; r < numRows; r++) {
        for (int c = 0; c < numCols; c++) {
            TileData* t = tile(r, c);
            if (t) {
                int hint = getHint(r,c);
                t->setHint(hint);
            }
        }
    }

    //Flip 3 random evidence
    int startFlipNumber = 3;
    while (startFlipNumber) {

        int col = int((double(rand()) / double(RAND_MAX)) * numCols);
        int row = int((double(rand()) / double(RAND_MAX)) * numRows);
        TileData* t = tile( row, col );

        while (t->hasEvidence() || t->flipped() || t->hint()==0 ) {
            col = int((double(rand()) / double(RAND_MAX)) * numCols);
            row = int((double(rand()) / double(RAND_MAX)) * numRows);
            t = tile( row, col );
        }

        t->flip();
        startFlipNumber--;
    }

    refreshLanguage(true);

    setPlaying(true);

    if (_settings->value("first")!="1") {
        _settings->setValue("first", "1");
        emit openWelcomeDialog();
    }

}

void EvidencehuntGame::reset() {

    foreach(TileData* t, _tiles){
        t->unflip();
        t->setHasFlag(false);
    }

    foreach(SuspectData* s, _suspects) {
        s->unflip();
        s->include();
    }

    isSolveMode = false;
    emit solveModeChanged();

    nFlags = 0;
    emit numFlagsChanged();

    fEvidences = 0;
    emit foundEvidencesChanged();

    setPlaying(false);
    QTimer::singleShot(600, this, SLOT(setBoard()));

}

int EvidencehuntGame::getHint(int row, int col)
{
    TileData* t = tile(row, col);
    int modifier = 0;
    if (t->hasEvidence()) {
        modifier = -1;
    }
    int hint = 0;
    for (int c = col-1; c <= col+1; c++)
        for (int r = row-1; r <= row+1; r++) {
            TileData* t = tile(r, c);
            if (t && t->hasEvidence()) {
                hint++;
            }
        }
    return hint + modifier;
}

EvidenceType* EvidencehuntGame::getType(int row, int col) {
    TileData* t = tile(row, col);
    return t->type();
}

bool EvidencehuntGame::suspectFlip(int id) {
    SuspectData *s = suspect(id);
    if (!s->flipped()) {
        s->flip();
    } else {
        s->unflip();
    }
    return true;
}

bool EvidencehuntGame::suspectExclude(int id) {
    SuspectData *s = suspect(id);
    if (!s->excluded()) {
        s->exclude();
    } else {
        s->include();
    }
    return true;
}

bool EvidencehuntGame::guessMurderer(int id) {

    if(!playing)
        return false;

    if(id == killerId){
        won = true;
        hasWonChanged();
        setPlaying(false);
        score += remaining*defaultScore*scoreMultiplier;
        emit currentScoreChanged();
        QString message = "\n\n"+tr("__KEEPTHISWAY__") + "!";
        _evidenceInfo->loadCustomMessage(tr("__CONGRATS__"),tileTypes[4],tr("__FOUNDTHEMURDERER__") + message,"arrest.jpg");
        _settings->setValue("currentScore", score);
        emit totalScoreChanged();
        wonStreak++;
        emit wonStreakChanged();
    } else {
        won = false;
        hasWonChanged();
        setPlaying(false);
        QString message;
        if (score > maxScore()) {
            message = "\n\n" + tr("__NEWPERSONALBEST__") + "!";
        }
        _evidenceInfo->loadCustomMessage(tr("__TERRIBLEMISTAKE"), tileTypes[1], tr("__MISSEDTHEMURDERER__") + message, "fail.jpg");
        if (score > maxScore()) {
            _settings->setValue("maxScore", score);
            emit maxScoreChanged();
        }
        _settings->setValue("lastScore", score);
        emit lastScoreChanged();
        wonStreak=0;
        emit wonStreakChanged();
    }

    return true;
}

bool EvidencehuntGame::loadEvidence(int row, int col) {
        TileData *t = tile(row, col);
        _evidenceInfo->loadEvidence(t);
        return true;
}

bool EvidencehuntGame::toggleSolve() {
    isSolveMode = !isSolveMode;
    if (isSolveMode) {
        //setPlaying(false);
        foreach(SuspectData* s, _suspects) {
            s->unflip();
        }
    }/* else {
        setPlaying(true);
    }*/
    emit solveModeChanged();
    return true;
}

void EvidencehuntGame::selectLanguage(int lang) {

    if (lang==0) {
        _settings->setValue("lang", QString("en_US"));
    } else if (lang==1) {
        _settings->setValue("lang", QString("hu_HU"));
    } else if (lang==2) {
        _settings->setValue("lang", QString("sr_RS"));
    } else if (lang==3) {
        _settings->setValue("lang", QString("it_IT"));
    }

    loadLanguage();

}

bool EvidencehuntGame::flip(int row, int col)
{
    if(!playing)
        return false;

    TileData *t = tile(row, col);
    if (!t || t->hasFlag()) {
        return false;
    }

    if(t->flipped()){
        return true;
    }

    if(remaining<1) {
        _evidenceInfo->loadCustomMessage(tr("__TIMEISUP__"),tileTypes[8],tr("__TIMETOSOLVE__"),"time.jpg");
        return false;
    } else {
        remaining--;
        emit timeRemainingChanged();
    }

    t->flip();

    if(t->hasEvidence()) {

        if(t->type()->id()==2) {
            score += 50;
        }

        if(t->type()->id()==5) {
            scoreMultiplier = 2;
        }

        if(t->type()->id()==7) {
            remaining += 5;
            emit timeRemainingChanged();
        }

        if(t->type()->id()==9) {
            score += 100;
        }

        fEvidences++;
        emit foundEvidencesChanged();

        score += defaultScore * scoreMultiplier;

    } else {

        score += 1;

    }
    emit currentScoreChanged();

    return true;
}


bool EvidencehuntGame::flag(int row, int col) {
    TileData *t = tile(row, col);
    if(!t || !playing || t->flipped())
        return false;

    t->setHasFlag(!t->hasFlag());
    nFlags += (t->hasFlag()?1:-1);
    emit numFlagsChanged();
    return true;
}


void EvidencehuntGame::shareGame() const {
    #if defined(Q_EVHUNT_MEEGO)
    MDataUri duri;
    duri.setMimeType ("text/x-url");
    duri.setTextData ("http://www.facebook.com/EvidenceHuntGame");
    duri.setAttribute ("title", "EvidenceHunt Game");
    duri.setAttribute ("description", tr("__PLAYING__"));
    if (duri.isValid()) {
        QStringList items;
        items << duri.toString();
        ShareUiInterface shareIf("com.nokia.ShareUi");
        if (shareIf.isValid()) {
            shareIf.share (items);
        }
    }
    #endif
}

QString EvidencehuntGame::player() {
    QString playerName = _settings->value("player").toString();
    if (playerName=="") {
        playerName = tr("__PLAYER__") + QString::number(rand() % 99999 + 100000);
    }
    return playerName;
}

int EvidencehuntGame::lastScore() const {
    return _settings->value("lastScore", 0).toInt();
}

int EvidencehuntGame::totalScore() const {
    return _settings->value("currentScore", 0).toInt();
}

int EvidencehuntGame::maxScore() const {
    return _settings->value("maxScore", 0).toInt();
}

int EvidencehuntGame::maxScoreSubmitted() const {
    return _settings->value("maxScoreSubmitted", 0).toInt();
}

void EvidencehuntGame::openScoreboard() {
    _scoreboard->getList();
}

void EvidencehuntGame::loadScoreboardPlayers(QList< QHash<QString,QString> > boardData) {

    _players.clear();
    QList< QHash<QString,QString> >::iterator i;
    for (i = boardData.begin(); i != boardData.end(); ++i) {
        QHash<QString,QString> currentPlayerData = *i;
        _players << new TopPlayerData(currentPlayerData.value("name"),currentPlayerData.value("score"),currentPlayerData.value("position"));
    }
    emit scoreboardLoaded();

}

bool EvidencehuntGame::submitScore(QString name, int score) {

    _settings->setValue("player", name);
    emit playerChanged();
    _scoreboard->submitScore(name, score);
    return true;

}

void EvidencehuntGame::boardSubmitSuccess(int submittedScore) {
    _settings->setValue("maxScoreSubmitted", submittedScore);
    emit maxScoreSubmittedChanged();
}
