/**
 * @file ThreadDataTransfert.cpp
 * @author   Ulrich Kemloh <kemlohulrich@gmail.com>
 * @version 0.1
 * Copyright (C) <2009-2010>
 *
 * @section LICENSE
 * This file is part of OpenPedSim.
 *
 * OpenPedSim is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * any later version.
 *
 * OpenPedSim is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with OpenPedSim. If not, see <http://www.gnu.org/licenses/>.
 *
 * @section DESCRIPTION
 * This class provides a thread utility that is charged of
 * asynchronously receive trajectories (streaming)
 * and  put them in the adequate data structure to be read by the Visualisation Thread.
 *
 * \brief Thread that reads data from the socket and parse it
 *
 *
 *
 *  Created on: 24.05.2009
 *
 */


#include <cstdlib>
#include <iostream>
#include <limits>


#include <QtXml/QtXml>
#include <QtNetwork/QtNetwork>
#include <QThread>
#include <QObject>
#include <QMessageBox>
#include <QString>


#include "SystemSettings.h"
#include "ThreadDataTransfert.h"
#include "SyncData.h"
#include "Frame.h"
#include "FrameElement.h"

#include "network/TraVisToServer.h"
#include "geometry/FacilityGeometry.h"
#include "general/Macros.h"
#include "Debug.h"


using namespace std;


ThreadDataTransfer::ThreadDataTransfer(QObject *parent):
    QThread(parent)
{
    m_port=SystemSettings::getListeningPort();
    tcpServer=NULL;
    headerParsed=false;
    errNr=0;
    finished=false;
    //Debug::setDebugLevel(Debug::ALL);
}

ThreadDataTransfer::~ThreadDataTransfer()
{

}

void ThreadDataTransfer::shutdown()
{
    finished=true;
}

void ThreadDataTransfer::run()
{

    finished=false;

    //
    //	std::cerr <<">> starting server" <<std::endl;
    //
    //	//	udpSocket = new QUdpSocket();
    //	//	udpSocket->bind(QHostAddress::LocalHost,SystemSettings::getListeningPort());
    //	//
    //	//	if(!connect(udpSocket, SIGNAL(readyRead()),
    //	//			this, SLOT(slotProcessPendingDatagrams()))){
    //	//		cerr<<"could not create the udp connection"<<endl;
    //	//	}
    //
    //	tcpServer = new QTcpServer(/*this->parent()*/);
    //	if(!connect(tcpServer, SIGNAL(newConnection()), this, SLOT(slotHandleConnection()))){
    //		cerr<<"could not create connection newconnection"<<endl;
    //	}
    //	connect(tcpServer, SIGNAL(error(QAbstractSocket::SocketError)),
    //				this, SLOT(displayError(QAbstractSocket::SocketError)));
    //
    //	if (!tcpServer->listen(QHostAddress::LocalHost,SystemSettings::getListeningPort())) {
    //		cerr<<"\nI can't listen, sorry"<<endl;
    //		QMessageBox::critical(0, tr("TraVisTo Server"),
    //				tr("Unable to start the server: %1.")
    //				.arg(tcpServer->errorString()));
    //		quit();
    //	}
    //
    //	if(tcpServer->isListening ())
    //		cout<<"\nTraVisTo is listening on port "<< tcpServer->serverPort()<<endl;

    QString data;
    TraVisToServer* server = new TraVisToServer(SystemSettings::getListeningPort());
    if(!server->isListening()) {
        emit signal_errorMessage("could not connect, make sure that the port is not used by another program.\n "
                                 "Restarting the application may solve the issue!");
        emit signal_controlSequence("CONTROL_RESET");

    } else
        do {
            emit signal_CurrentAction("waiting for data");
            server->receiveMessage(data);

            if(!data.isEmpty())
                slotProcessMessage(data);

        } while(finished!=true);

    server->close();
}

void ThreadDataTransfer::slotHandleConnection()
{

    Debug::Messages("handling new connection");

    QTcpSocket *clientConnection = tcpServer->nextPendingConnection();
    connect(clientConnection, SIGNAL(disconnected()),
            clientConnection, SLOT(deleteLater()));

    connect(clientConnection, SIGNAL(readyRead()), this, SLOT(slotReadMessage()));

    connect(clientConnection, SIGNAL(error(QAbstractSocket::SocketError)),
            this, SLOT(displayError(QAbstractSocket::SocketError)));
    //Q_DECLARE_METATYPE( QAbstractSocket::SocketError );
    Debug::Messages("juhuuuu");
}

void ThreadDataTransfer::slotReadMessage()
{
    Debug::Messages("new post");
    //slotProcessMessage();
}

void ThreadDataTransfer::slotProcessMessage(QString& data)
{
    QDomDocument doc("");

    //data = "<travisto>\n" +data+ "</travisto>";
    //cout<<data.toStdString()<<endl;

    QString errorMsg="";
    doc.setContent(data,&errorMsg);

    if(!errorMsg.isEmpty()) {
        errNr++;
        //Debug::Error(">> %s",(const char *)errorMsg.toStdString().c_str());
        //Debug::Error(">> %s",(const char *)data.toStdString().c_str());
        return;
    }

    QDomNode header =doc.elementsByTagName("header").item(0);
    QDomNode geometry =doc.elementsByTagName("geometry").item(0);
    QDomNode shapes =doc.elementsByTagName("shape").item(0);

    QDomNodeList dataList = doc.elementsByTagName("frame");

    if(!header.isNull()) {
        parseHeaderNode(header);
        Debug::Messages("header received and parsed");
    }
    if(!shapes.isNull()) {
        parseShapeNode(shapes);
        Debug::Messages("header received and parsed");
    }

    if(!geometry.isNull()) {
        //emit signal_loadGeometry(data);
        geoData=data;
        Debug::Messages("geometry received and parsed");
        //parseGeometryNode(geometry);
    }
    if(!dataList.isEmpty()) {
        parseDataNode(dataList);
    }

    data.clear();
}

void ThreadDataTransfer::slotConnectionClosed()
{
    Debug::Error("connection lost");
}


void ThreadDataTransfer::parseHeaderNode(QDomNode header )
{


    emit signal_CurrentAction("parsing new header");

    bool ok;
    numberOfAgents = getTagValueFromElement(header, "agents").toInt(&ok);

    if(!ok) {
        Debug::Error("The number of agents is invalid");
        Debug::Error("The number must be between 1...65355");
        emit signal_errorMessage("The number of agents is invalid");
        numberOfAgents=1000;
    }

    if(numberOfAgents==extern_trajectories_firstSet.getNumberOfAgents()) {
        // only clear
        emit signal_stopVisualisationThread(false);
    } else {
        emit signal_stopVisualisationThread(true);
    }

    QString frameRateStr=getTagValueFromElement(header, "frameRate");
    frameRate =frameRateStr.toFloat(&ok);

    if(!ok) {
        Debug::Error("The frame rate is invalid");
        Debug::Error("The number must be between 1...1000");
        QMessageBox msgBox;
        msgBox.setText("The frame rate is invalid");
        msgBox.setInformativeText("The number must be between 1...1000,  I will consider 25");
        msgBox.setStandardButtons(QMessageBox::Ok);
        msgBox.setIcon(QMessageBox::Critical);
        msgBox.exec();
        frameRate=25;
    }

    //get the header version
    if(header.toElement().hasAttributes()) {
        QString version=header.toElement().attribute("version");
        QStringList query = version.split(".");
        int major=0;
        int minor=0;
        int patch=0;
        switch (query.size() ) {
        case 1:
            major=query.at(0).toInt();
            break;
        case 2:
            major=query.at(0).toInt();
            minor=query.at(1).toInt();
            break;
        case 3:
            major=query.at(0).toInt();
            minor=query.at(1).toInt();
            patch=query.at(2).toInt();
            break;
        }
        InitHeader(major,minor,patch);
        //cout<<"version found:"<<at.value(i).toStdString()<<endl;exit(0);
    }

    headerParsed=true;
}

QString ThreadDataTransfer::getTagValueFromElement(QDomNode node,
        const char * tagName)
{
    if (node.isNull())
        return "";
    return node.toElement().namedItem(tagName).firstChild().nodeValue();

}

void ThreadDataTransfer::parseDataNode(QDomNodeList frames)
{

    for (int i = 0; i < frames.length(); i++) {
        Frame *newFrame = new Frame();
        QDomElement el = frames.item(i).toElement();
        QDomNodeList agents = el.elementsByTagName("agent");

        for (int i = 0; i < agents.length(); i++) {
            bool ok=false;
            int id=agents.item(i).toElement().attribute("ID").toInt(&ok);
            if(!ok) continue; // invalid ID
            double xPos=agents.item(i).toElement().attribute(_jps_xPos,"0").toDouble()*FAKTOR;
            double yPos=agents.item(i).toElement().attribute(_jps_yPos,"0").toDouble()*FAKTOR;
            double zPos=agents.item(i).toElement().attribute(_jps_zPos,"0").toDouble()*FAKTOR;

            double dia_a=agents.item(i).toElement().attribute(_jps_radiusA).toDouble(&ok)*FAKTOR;
            if(!ok)dia_a=std::numeric_limits<double>::quiet_NaN();
            double dia_b=agents.item(i).toElement().attribute(_jps_radiusB).toDouble(&ok)*FAKTOR;
            if(!ok)dia_b=std::numeric_limits<double>::quiet_NaN();
            double el_angle=agents.item(i).toElement().attribute(_jps_ellipseOrientation).toDouble(&ok);
            if(!ok) {
                el_angle=std::numeric_limits<double>::quiet_NaN();
            }
            double el_color=agents.item(i).toElement().attribute(_jps_ellipseColor).toDouble(&ok);
            if(!ok)el_color=std::numeric_limits<double>::quiet_NaN();

            double pos[3]= {xPos,yPos,zPos};
            //double vel[3]={xVel,yPos,zPos};
            //double ellipse[7]={el_x,el_y,el_z,dia_a,dia_b,el_angle,el_color};
            //double para[2]={agent_color,el_angle};
            double angle[3]= {0,0,el_angle};
            double radius[3]= {dia_a,dia_b,30.0};

            FrameElement *element = new FrameElement(id-1);
            element->SetPos(pos);
            element->SetOrientation(angle);
            element->SetRadius(radius);
            element->SetColor(el_color);
            newFrame->addElement(element);
        }

        //adding the new frame to the right dataset
        newFrame->ComputePolyData();
        extern_trajectories_firstSet.addFrame(newFrame);
        //	frameNumbers++;
    }

    if(headerParsed==true) {
        //		static int count=1;
        //		count++;
        //		if (count<100) return; // start after 100 frames
        emit signal_startVisualisationThread(geoData,numberOfAgents,frameRate);
        headerParsed=false;
    }
}


void ThreadDataTransfer::slotDisplayError(QAbstractSocket::SocketError socketError)
{

    switch (socketError) {

    case QAbstractSocket::RemoteHostClosedError:
        Debug::Error( "The host closes the connection ");
        break;

    case QAbstractSocket::HostNotFoundError:
        Debug::Error("The host was not found. Please check the ");
        Debug::Error("host name and port settings.");
        break;

    case QAbstractSocket::ConnectionRefusedError:
        Debug::Error("The connection was refused by the peer. ");
        Debug::Error("Make sure the fortune server is running");
        Debug::Error("and check that the host name and port ");
        Debug::Error("settings are correct.");
        break;

    default:
        Debug::Error("TraVisTo Client:");
        Debug::Error("The following error occurred: ");
        //cerr<< clientConnection->errorString().toStdString()<<endl;
        break;
    }


}

void ThreadDataTransfer::slotProcessPendingDatagrams()
{
    Debug::Messages("connected");
    while (udpSocket->hasPendingDatagrams()) {
        QByteArray datagram;
        datagram.resize(udpSocket->pendingDatagramSize());
        udpSocket->readDatagram(datagram.data(), datagram.size());
        //		Debug::Messages("%s",(const char *) datagram.data().c_str()));
    }
}


void ThreadDataTransfer::parseShapeNode(QDomNode shape)
{


    QDomNodeList agents = shape.toElement().elementsByTagName("agentInfo");
    QStringList heights;
    QStringList colors;
    for (int i = 0; i < agents.length(); i++) {

        bool ok=false;
        int id=agents.item(i).toElement().attribute("ID").toInt(&ok);
        if(!ok) continue; // invalid ID
        double height=agents.item(i).toElement().attribute("height").toDouble(&ok);
        if(!ok)height=std::numeric_limits<double>::quiet_NaN();

        int color=agents.item(i).toElement().attribute("color").toDouble(&ok);
        if(!ok)color=std::numeric_limits<int>::quiet_NaN();
        //cout <<"id= " <<id <<" height= "<<height<<" color= "<<color<<endl;

        if(!std::isnan(height)) {
            heights.append(QString::number(id));
            heights.append(QString::number(height));
        }
        if(!std::isnan(color)) {
            colors.append(QString::number(id));
            colors.append(QString::number(color));
        }

    }

    extern_trajectories_firstSet.setInitialHeights(heights);
    extern_trajectories_firstSet.setInitialColors(colors);
}

void ThreadDataTransfer::InitHeader(int major, int minor, int patch)
{
    // set the parsing String map
    if(minor==5 && patch==0) {
        _jps_xPos=QString("xPos");
        _jps_yPos=QString("yPos");
        _jps_zPos=QString("zPos");
        _jps_radiusA=QString("radiusA");
        _jps_radiusB=QString("radiusB");
        _jps_ellipseOrientation=QString("ellipseOrientation");
        _jps_ellipseColor=QString("ellipseColor");
    } else if ( (minor==6) || (minor==5 && patch==1) ) {
        _jps_xPos=QString("x");
        _jps_yPos=QString("y");
        _jps_zPos=QString("z");
        _jps_radiusA=QString("rA");
        _jps_radiusB=QString("rB");
        _jps_ellipseOrientation=QString("eO");
        _jps_ellipseColor=QString("eC");
    } else {
        cout<<"unsupported header version: "<<major<<"."<<minor<<"."<<patch<<endl;
        cout<<"Please use 0.5 0.5.1 or 0.6 "<<endl;
    }
}