/****************************************************************************
**
** Copyright (C) 2015 The Qt Company Ltd.
** Contact: http://www.qt.io/licensing/
**
** This file is part of the QtDeclarative module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:BSD$
** You may use this file under the terms of the BSD license as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
**   * Redistributions of source code must retain the above copyright
**     notice, this list of conditions and the following disclaimer.
**   * Redistributions in binary form must reproduce the above copyright
**     notice, this list of conditions and the following disclaimer in
**     the documentation and/or other materials provided with the
**     distribution.
**   * Neither the name of The Qt Company Ltd nor the names of its
**     contributors may be used to endorse or promote products derived
**     from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/

#include "gameengine.h"
#include "plugins/levelplugininterface.h"
#include "InvSounds.h"

#include <QDebug>
#include <QTimerEvent>
#include <QTime>
#include <QDesktopServices>

const int TIMER_SPEED = 80;

GameEngine::GameEngine(QObject* parent)
    :QObject(parent)
{
    m_timerId = 0;
    m_doEnemyMissile = 1500 / TIMER_SPEED;
    m_GameGml = 0;
    m_gameLevel = 0;

    clearQmlObjects();

    // For random
    QTime time = QTime::currentTime();
    qsrand((uint)time.msec());

    // Sound engine
    m_soundEngine = new CInvSounds(this);

    // Device profile
    m_silent = false;

#ifdef Q_OS_SYMBIAN
    iVibrate = CHWRMVibra::NewL();
#endif

    // Get device profile, is it silent?
#if defined Q_OS_SYMBIAN || defined Q_WS_MAEMO_5
    m_systemDeviceInfo = new QSystemDeviceInfo(this);
    QObject::connect(m_systemDeviceInfo,SIGNAL(currentProfileChanged(QSystemDeviceInfo::Profile)),this,
                     SLOT(currentProfileChanged(QSystemDeviceInfo::Profile)));
    QSystemDeviceInfo::Profile p = m_systemDeviceInfo->currentProfile();
    if (p == QSystemDeviceInfo::SilentProfile) {
        m_silent = true;
    }
#endif

}


GameEngine::~GameEngine()
{
#ifdef Q_OS_SYMBIAN
    delete iVibrate;
#endif

}

void GameEngine::gameStartSound()
{
    if (!m_silent)
        m_soundEngine->gameStartSound();
}

#if defined Q_OS_SYMBIAN || defined Q_WS_MAEMO_5
void GameEngine::currentProfileChanged(QSystemDeviceInfo::Profile p)
{
    if (p == QSystemDeviceInfo::SilentProfile) {
        enableSounds(QVariant(false));
    } else {
        enableSounds(QVariant(true));
    }
}
#endif

void GameEngine::enableSounds(QVariant enable)
{
    m_silent = !enable.toBool();

    if (m_silent)
        this->m_soundEngine->enableSounds(false);
    else
        this->m_soundEngine->enableSounds(true);

}

QVariant GameEngine::randInt(QVariant low, QVariant high)
{
    // Random number between low and high
    return qrand() % ((high.toInt() + 1) - low.toInt()) + low.toInt();
}

void GameEngine::setGameLevel(LevelPluginInterface* level)
{
    // Set used game level
    m_gameLevel = level;

    if (m_gameLevel) {
        // Set used sound from the level into sound engine
        m_soundEngine->enableSounds(m_gameLevel->levelSounds());
        // Invoke QML to take new level in use
        QMetaObject::invokeMethod(m_GameGml, "levelReadyForCreation", Qt::AutoConnection);

        m_doEnemyMissile = m_gameLevel->enemyFireSpeed().toInt() / TIMER_SPEED;
    }
}

void GameEngine::setPluginList(QList<QPluginLoader*> plugins)
{
    m_pluginList = plugins;
}

QVariant GameEngine::pluginList()
{
    QStringList list;
    QPluginLoader* loader;
    foreach (loader,m_pluginList) {
        QString s = loader->fileName();
        s = s.mid(s.lastIndexOf("/")+1);
        s = s.left(s.lastIndexOf("."));
        s = s.toUpper();
#ifdef Q_WS_MAEMO_5
        if (s.contains("LIB")) {
            s = s.right(s.length() - (s.indexOf("LIB")+3));
        }
#endif
        list.append(s);
    }
    return QVariant(list);
}

void GameEngine::pauseLevel(QVariant doPause)
{
    bool enableTimer = !doPause.toBool();
    enableEngineTimer(QVariant(enableTimer));
    QMetaObject::invokeMethod(m_levelQml, "pause", Qt::AutoConnection,Q_ARG(QVariant, doPause));
}


void GameEngine::findQmlObjects()
{
    if (m_GameGml) {
        qDebug() << "GameEngine::findQmlObjects()";

        // Find Missiles objects
        m_missileList.clear();
        m_enemyMissileList.clear();
        findMissiles(m_GameGml);

        // Set QMLs
        setLevelQml(m_GameGml->findChild<QObject*>("level"));
        setEnemiesGridQml(m_GameGml->findChild<QObject*>("enemiesGrid"));
        setMyShipQml(m_GameGml->findChild<QObject*>("myShip"));

        // Find Enemies objects
        m_enemyList.clear();
        qDebug() << "GameEngine::findQmlObjects() find enemies from: level QML";
        findEnemies(m_levelQml);


    } else {
        qDebug() << "GameEngine::findQmlObjects() rootObject NULL";
    }
}

void GameEngine::clearQmlObjects()
{
    m_missileList.clear();
    m_enemyMissileList.clear();
    m_enemyList.clear();
    m_levelQml = 0;
    m_enemiesGridGml = 0;
    m_myShipGml = 0;
    //m_GameGml = 0; // NOTE: Do not delete this
}


void GameEngine::findMissiles(QObject *rootObject)
{
    if (rootObject) {
        QObjectList list = rootObject->children();
        QObject* item;
        foreach (item,list) {
            if (item->children().count()>0) {
                findMissiles(item);
            } else {
                if (rootObject->objectName()=="missile") {
                    QDeclarativeItem* missile = static_cast<QDeclarativeItem*>(rootObject);
                    m_missileList.append(missile);
                } else if (rootObject->objectName()=="enemy_missile") {
                    QDeclarativeItem* enemyMissile = static_cast<QDeclarativeItem*>(rootObject);
                    m_enemyMissileList.append(enemyMissile);
                }
            }
        }
    } else {
        qDebug() << "GameEngine::findMissiles() rootObject NULL";
    }
}

void GameEngine::findEnemies(QObject *rootObject)
{
    if (rootObject) {
        QObjectList list = rootObject->children();
        QObject* item;
        foreach (item,list) {
            if (item->children().count()>0 && item->objectName()!="enemy") {
                //qDebug() << "Enemy children found from: " << item->objectName();
                findEnemies(item);
            } else {
                if (item->objectName()=="enemy") {
                    //qDebug() << "Enemy child founds: " << item->objectName();
                    QDeclarativeItem* enemy = static_cast<QDeclarativeItem*>(item);
                    m_enemyList.append(enemy);
                }
            }
        }
    } else {
        qDebug() << "GameEngine::findEnemies() rootObject NULL";
    }
}

void GameEngine::setEnemiesGridQml(QObject* o)
{
    m_enemiesGridGml = static_cast<QDeclarativeItem*>(o);
}

void GameEngine::setMyShipQml(QObject* o)
{
    m_myShipGml = static_cast<QDeclarativeItem*>(o);
}

void GameEngine::setGameQml(QObject* o)
{
    m_GameGml = static_cast<QDeclarativeItem*>(o);
}


void GameEngine::timerEvent(QTimerEvent *e)
{
    if (e->timerId()==m_timerId) {
        // Do hit test
        doHitTest();

        m_doEnemyMissile--;

        if (m_gameLevel && m_doEnemyMissile<0) {
            m_doEnemyMissile = m_gameLevel->enemyFireSpeed().toInt() / TIMER_SPEED;
            // Do emeny missile launch
            doEnemyMissile();
        }
    }
}

void GameEngine::enableEngineTimer(QVariant enable)
{
    if (m_gameLevel) {
        if (m_timerId==0 && enable.toBool()) {
            m_timerId = QObject::startTimer(TIMER_SPEED);
        }
        else if (m_timerId != 0 && !enable.toBool()) {
            QObject::killTimer(m_timerId);
            m_timerId = 0;
        }
    }
}

void GameEngine::selectVisibleEnemy(int& start, int& end)
{
    QDeclarativeItem* enemy = 0;
    for (int i=0 ; i<m_enemyList.count() ; i++) {
        enemy = m_enemyList[i];
        if (enemy->opacity()==1) {
            start = i;
            break;
        }
    }
    enemy = 0;
    for (int e=m_enemyList.count()-1 ; e>0 ; e--) {
        enemy = m_enemyList[e];
        if (enemy->opacity()==1) {
            end = e;
            break;
        }
    }
}

void GameEngine::doEnemyMissile()
{
    QMutexLocker locker(&m_enemyListMutex);

    QDeclarativeItem* missile = 0;
    QDeclarativeItem* enemy = 0;

    // Find free missile
    foreach (missile, m_enemyMissileList) {
        if (missile->opacity()==0){
            // Random select enemy who fire
            int start=0; int end=0;
            selectVisibleEnemy(start,end);
            int whoWillFire = randInt(QVariant(start),QVariant(end)).toInt();
            if (m_enemyList.count() < whoWillFire+1)
                break;

            enemy = m_enemyList.at(whoWillFire);
            if (enemy && enemy->opacity()==1) {
                QPointF enemyP =  enemy->pos();
                if (m_enemiesGridGml) {
                    enemyP += m_enemiesGridGml->pos();
                }
                //qDebug() << "QMetaObject::invokeMethod() - fireEnemyMissile";
                QMetaObject::invokeMethod(m_GameGml, "fireEnemyMissile", Qt::AutoConnection,
                                          Q_ARG(QVariant, enemyP.x()+enemy->boundingRect().width()/4),
                                          Q_ARG(QVariant, enemyP.y()+enemy->boundingRect().height()),
                                          Q_ARG(QVariant, m_GameGml->boundingRect().height()));
            }
            break;
        }
    }
}

void GameEngine::doHitTest()
{
    QMutexLocker locker(&m_enemyListMutex);

    QDeclarativeItem* missile = 0;
    QDeclarativeItem* enemy = 0;

    // No enemies?
    if (m_enemyList.count()==0) {
        enableEngineTimer(QVariant(false));
        qDebug() << "No enemies left";
        gameOver(true);
        return;
    }

    if (!m_myShipGml) {
        return;
    }

    // Check ship collision
    if (m_myShipGml->opacity()==1) {
        for (int e=0; e<m_enemyList.count(); e++) {
            enemy = m_enemyList[e];
            if (enemy->opacity()==0) {
                break;
            }
            QPointF enemyP =  enemy->pos();
            if (m_enemiesGridGml) {
                enemyP += m_enemiesGridGml->pos();
            }
            QRectF enemyR(enemyP,QSize(enemy->boundingRect().width(),enemy->boundingRect().height()));
            // Collision?
            if (enemyR.contains(m_myShipGml->pos())) {
                enableEngineTimer(QVariant(false));

                // Collision explosion
                QPointF myP =  m_myShipGml->pos();
                playSound(1);
                QMetaObject::invokeMethod(m_levelQml, "explode", Qt::AutoConnection,
                                          Q_ARG(QVariant, myP.x()+m_myShipGml->boundingRect().width()/2),
                                          Q_ARG(QVariant, myP.y()+m_myShipGml->boundingRect().height()));
                m_myShipGml->setOpacity(0);

                gameOver(false);
                qDebug() << "Collision";
                return;
            }
            // Enemy too deep?
            else if (enemyR.bottomLeft().y() > m_myShipGml->pos().y()+m_myShipGml->pos().y()*0.1) {
                enableEngineTimer(QVariant(false));

                // Enemy too deep explosion
                QPointF myP =  m_myShipGml->pos();
                playSound(1);
                QMetaObject::invokeMethod(m_levelQml, "explode", Qt::AutoConnection,
                                          Q_ARG(QVariant, myP.x()+m_myShipGml->boundingRect().width()/2),
                                          Q_ARG(QVariant, myP.y()+m_myShipGml->boundingRect().height()));
                m_myShipGml->setOpacity(0);

                gameOver(false);
                qDebug() << "Enemy too deep";
                return;
            }
        }
    }

    // Check your missiles hit to enemies
    foreach (missile, m_missileList) {
        if (missile->opacity()==1){
            for (int e=0; e<m_enemyList.count(); e++) {
                enemy = m_enemyList[e];
                if (enemy->opacity()<1) {
                    break;
                }
                QPointF missileP = missile->pos();
                missileP.setX(missileP.rx() + missile->boundingRect().width()/2);

                QPointF enemyP =  enemy->pos();
                if (m_enemiesGridGml) {
                    enemyP += m_enemiesGridGml->pos();
                }

                QRectF r(enemyP,QSize(enemy->boundingRect().width(),enemy->boundingRect().height()));
                if (r.contains(missileP)) {
                    // Hit !
                    playSound(0);
                    //qDebug() << "QMetaObject::invokeMethod() - explode";
                    QMetaObject::invokeMethod(m_levelQml, "explode", Qt::AutoConnection,
                                              Q_ARG(QVariant, enemyP.x()+enemy->boundingRect().width()/2),
                                              Q_ARG(QVariant, enemyP.y()+enemy->boundingRect().height()));
                    missile->setOpacity(0);
                    //fastVibra();
                    if (m_enemiesGridGml) {
                        // Set transparent placeholder for enemy when using GridView
                        enemy->setProperty("source",QVariant("file:/"+m_gameLevel->pathToTransparentEnemyPic().toString()));
                    } else {
                        // Hide enemy after explode
                        enemy->setOpacity(0);
                    }

                    // Remove enemy from list
                    m_enemyList.removeAt(e);
                    e--;
                }
                enemy = 0;
            }
        }
    }

    // Check enemies missiles hit to you
    if (m_myShipGml->opacity()==1) {
        foreach (missile, m_enemyMissileList) {
            if (missile->opacity()==1){
                QPointF missileP = missile->pos();
                missileP.setX(missileP.rx() + missile->boundingRect().width()/2);

                QPointF myP =  m_myShipGml->pos();

                QRectF r(myP,QSize(m_myShipGml->boundingRect().width(),m_myShipGml->boundingRect().height()));
                if (r.contains(missileP)) {
                    // Hit !
                    playSound(1);
                    //qDebug() << "QMetaObject::invokeMethod() - explode";
                    QMetaObject::invokeMethod(m_levelQml, "explode", Qt::AutoConnection,
                                              Q_ARG(QVariant, myP.x()+m_myShipGml->boundingRect().width()/2),
                                              Q_ARG(QVariant, myP.y()+m_myShipGml->boundingRect().height()));
                    missile->setOpacity(0);
                    m_myShipGml->setOpacity(0);
                    break;
                }
            }
        }
    } else {
        // You was killed
        enableEngineTimer(QVariant(false));
        gameOver(false);
        qDebug() << "You was killed by enemy missile";
    }

}


void GameEngine::playSound(QVariant index)
{
    if (!m_silent) {
        int i = index.toInt();
        m_soundEngine->playSound(i);
    }
}

void GameEngine::playSounds(QVariant index, QVariant count)
{
    if (!m_silent) {
        m_soundEngine->playSounds(index.toInt(),count.toInt());
    }
}

void GameEngine::playInternalSound(QVariant index)
{
    if (!m_silent) {
        m_soundEngine->playInternalSound(index.toInt());
    }
}

void GameEngine::playInternalSounds(QVariant index, QVariant count)
{
    if (!m_silent) {
        m_soundEngine->playInternalSounds(index.toInt(),count.toInt());
    }
}

void GameEngine::gameOver(bool youWin)
{
    qDebug() << "GameEngine::gameOver() "<< youWin;
    QMetaObject::invokeMethod(m_GameGml, "gameOver", Qt::AutoConnection,Q_ARG(QVariant, youWin));
}

void GameEngine::pauseGame() {
    QMetaObject::invokeMethod(m_GameGml, "pauseGame", Qt::AutoConnection);
}


QVariant GameEngine::isSymbian()
{
#ifdef Q_OS_SYMBIAN
    return QVariant(true);
#else
    return QVariant(false);
#endif
}

QVariant GameEngine::isMaemo()
{
#ifdef Q_WS_MAEMO_5
    return QVariant(true);
#else
    return QVariant(false);
#endif
}

QVariant GameEngine::isWindows()
{
#ifdef Q_OS_WIN
    return QVariant(true);
#else
    return QVariant(false);
#endif
}

void GameEngine::vibra()
{
#ifdef Q_OS_SYMBIAN
    if (iVibrate){
        TRAPD(err, iVibrate->StartVibraL(4000,KHWRMVibraMaxIntensity));
    }
#endif
}

void GameEngine::fastVibra()
{
#ifdef Q_OS_SYMBIAN
    if (iVibrate){
        TRAPD(err, iVibrate->StartVibraL(100,KHWRMVibraMaxIntensity));
    }
#endif
}

void GameEngine::openLink(QVariant link)
{
QDesktopServices::openUrl(QUrl(link.toString()));
}

