/***************************************************************************
 *   Copyright (C) 2008 by SK Communications.                              *
 *   http://kldp.net/projects/nateon/                                      *
 *                                                                         *
 *   This program 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     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program 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 this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/

#include <stdlib.h>
#include <qdir.h>
#include <qsocket.h>
#include <qsocketdevice.h>
#include <qsocketnotifier.h>
#include <qtimer.h>
#include <qstringlist.h>
#include <kdebug.h>
#include "nomp2pbase.h"
#include "sqlitedb.h"
#include "util/common.h"

extern nmconfig stConfig;

NOMP2PBase::NOMP2PBase( QObject *parent, const char *name ):
    QObject( parent, name ),
    sMyID( QString::null ),
    sYourID( QString::null ),
    sMyIP( QString::null ),
    sYourIP( QString::null ),
    sMyPort( QString::null ),
    sYourPort( QString::null ),
    eType(SEND),
    eUsedIP(MYIP),
    eStatus(STOP), /*! 전송 시작 안됨 */
    eConnectType(SERVER),
    bOverWrite( TRUE ),
    sFileName( QString::null ),
    fFileSize(0),
    fFileOffset(0),
    fFileSumSize(0),
    pSocket(0),
    sFileCookie( QString::null ),
    sP2PCookie( QString::null ),
    sFRCookie( QString::null ),
    pP2PTimer(0),
    pSQLiteDB(0),
    bBin(false),
    pPacketData(0),
    nPacketSize(0),
    nPacketSum(0),
    pSocketRestData(0),
    nSocketRestSize(0),
    nTID(0),
    nNegoPort(6004),
    qBuffer(0)
{
	pSQLiteDB = new SQLiteDB();
    slCommandQueue.clear();
}

NOMP2PBase::~ NOMP2PBase()
{
    if ( !qBuffer.isEmpty() ) {
        qBuffer.truncate(0);
        qBuffer.resize(0);
    }
}

// void NOMP2PBase::startTimer()
// {
// 	if (!pP2PTimer)
// 	{
// 		pP2PTimer = new QTimer( this, "timer" );
// 		connect( pP2PTimer, SIGNAL( timeout() ), this, SLOT( slotP2PTimeout() ) );
// 	}
// 	pP2PTimer->start(5000, TRUE);
// }


// void NOMP2PBase::slotP2PTimeout()
// {
// 	emit P2PTimeout( this );
// }

void NOMP2PBase::setSocket( QSocket *socket )
{
    pSocket = socket;

    connect( pSocket, SIGNAL( connected() ), SLOT( slotConnected() ) );
    connect( pSocket, SIGNAL( readyRead() ), SLOT( slotReadyRead() ));
//     connect( pSocket, SIGNAL( connectionClosed() ), SLOT( slotDisconnected() ) );
    connect( pSocket, SIGNAL( error ( int ) ), SLOT ( slotError( int ) ) );
}

void NOMP2PBase::setSocket( int nSocket )
{
    pSocket = new QSocket( this, "mySocket" );
	pSocket->setSocket( nSocket );

    connect( pSocket, SIGNAL( connected() ), SLOT( slotConnected() ) );
    connect( pSocket, SIGNAL( readyRead() ), SLOT( slotReadyRead() ));
//     connect( pSocket, SIGNAL( connectionClosed() ), SLOT( slotDisconnected() ) );
}

void NOMP2PBase::slotReadyRead()
{
	char *pRawData = 0;
	Q_ULONG nByte = pSocket->bytesAvailable();

	pRawData = (char *)malloc( nByte + 1 );
	memset( pRawData, 0x00, nByte + 1 );

    Q_ULONG nBytesRead = pSocket->readBlock( pRawData, nByte );
	if ( nBytesRead <= 0 )
        return;
	/*! 이전에 소켓 데이터가 남아 있는것을 방금 받은 소켓 데이터와 합친다. */
	qBuffer.add( pRawData, nBytesRead );
	/*!
	 * Bugs 305159 - modified by luciferX2@gmail.com, 20081103
	 */
	free(pRawData);

	QStringList slCommand;
	while( qBuffer.length() > 0 ) {
        if ( bBin ) {
            if ( qBuffer.length() >= nPacketSize ) {
	            unsigned long nWrite = fileWrite( qBuffer.data(), nPacketSize );
	            fFileSumSize += nWrite;
	            emit updateProgress( sFileCookie, fFileSumSize );
	            if ( fFileSumSize == getFileSize() ) {
		            QString sCommand;
		            sCommand = "END";
		            sCommand += " ";
		            sCommand += "N";
		            sCommand += " ";
		            sCommand += "0";
		            sCommand += "\r\n";
		            sendCommand( "FILE", sCommand );
                    emit endProgress( sFileCookie );
		            break;
	            }
	            qBuffer.remove( nWrite );
                if ( nWrite != nPacketSize ) {
                    kdDebug() << "Not equal nWrite and nPacketSize!!!" << endl;
                    nPacketSize -= nWrite;
	                continue;
                }
                setBin( false );
            }
            else {
                break;
            }
        }
        else {
            int nRet = qBuffer.findNewline();
            if ( nRet == -1 ) {
	            kdDebug() << "1. EMPTY DATA qBuffer.len : " << qBuffer.length() << ", [" << qBuffer.data() << "]" << endl;
                break;
            }
            QByteArray aCmd = qBuffer.left( nRet );
            emit IncomingMessage( "[ P2P_B ]-{" + aCmd + "}-" );
            slCommand.clear();
            slCommand = QStringList::split( " ", aCmd );
            if ( slCommand.empty() ) {
 	            kdDebug() << "2. EMPTY DATA" << endl;
                break;
            }
            qBuffer.remove( nRet + 2 );

            if ( slCommand[0] == "ATHC" ) {
                gotATHC( slCommand );
            }
            else if ( slCommand[0] == "FRIN" ) {
                gotFRIN( slCommand );
            }
            else if ( slCommand[0] == "FILE" ) {
                if ( slCommand[2] =="ACCEPT" ) {
                    gotACCEPT( slCommand );
                }
                else if ( slCommand[2] == "INFO" ) {
                    gotINFO( slCommand );
                }
                else if ( slCommand[2] == "START" ) {
                    gotSTART( slCommand );
                }
                else if ( slCommand[2] == "DATA" ) {
                    setTID( slCommand[1].toInt() );
                    setPacketSize( slCommand[3].toULong() );
                    setBin( true );
                    kdDebug() << "TID :" << nTID << ", Data Byte : " << nPacketSize << endl;
                }
                else if ( slCommand[2] == "END" ) {
                    gotEND( slCommand );
                }
                else {
                    kdDebug() << "Unknown Protocol(2) : " << slCommand[0] << ", " << slCommand[2] << endl;
                }
            } /*! FILE */
            else {
                kdDebug() << "Unknown Protocol(1) : " << slCommand[0] << endl;
            }
        }
	}
}

void NOMP2PBase::slotConnected()
{
    if ( pP2PTimer )
        pP2PTimer->stop();

    if ( slCommandQueue.count() > 0 )
    {
        for ( QStringList::Iterator it = slCommandQueue.begin(); it != slCommandQueue.end(); ++it ) {
            /*! ex) slCommand : "ATHC|sender@nate.com receiver@nate.com 12222222:2222 6004 0\r\n" */
            QStringList slCommand = QStringList::split( "|", *it );
            sendCommand( slCommand[0], slCommand[1] );
        }
    }
}

// void NOMP2PBase::slotDisconnected()
// {
// }

#include "nomp2pbase.moc"


/**
 * 패킷 전송 함수
 * TID 인자가 없으면 전역적으로 증가되는 값으로 TID가 들어감.
 *
 * @param sPrefix
 * @param sBody
 * @param TID
 */
void NOMP2PBase::sendCommand(const QString & sPrefix, const QString & sBody, unsigned TID )
{
    QString sCommand;
    sCommand = sPrefix;
    sCommand += " ";
    sCommand += QString::number( TID );
    sCommand += " ";
    sCommand += sBody;

    pSocket->writeBlock( sCommand.data(), sCommand.length() );
    emit OutgoingMessage( "[ P2P_B ]-{" + sCommand + "}-" );
    // kdDebug() << "P2P [" << sCommand << "]" << endl;
    pSocket->flush();
}

void NOMP2PBase::sendBinData(const char * pRawData, Q_ULONG nByte)
{
    pSocket->writeBlock( pRawData, nByte );
    pSocket->flush();
}

void NOMP2PBase::slotCancel()
{
	if ( pSocket->isOpen() ) {
		pSocket->flush();
		pSocket->close();
	}

	if ( fFile.isOpen() ) {
		fFile.flush();
		fFile.close();
	}
}

Q_LONG NOMP2PBase::fileWrite(const char * data, Q_ULONG len)
{
	Q_LONG lRet = fFile.writeBlock( data, len );
	fFile.flush();
	return lRet;
}

// Q_LONG NOMP2PBase::fileRead(char * data, Q_ULONG maxlen)
// {
// }

void NOMP2PBase::setOverWrite(bool overwrite)
{
}

void NOMP2PBase::setOffset(QIODevice::Offset offset)
{
}

void NOMP2PBase::gotATHC(QStringList & slCommand)
{
    /*! ATHC 0 [Your ID] [My ID] [DP Cookie] 6004 0\r\n */
    if ( eConnectType == SERVER ) {
        setYourID( slCommand[2] );
        setP2PCookie( slCommand[4] );
        setNegoPort( slCommand[5].toInt() );

        QString sQuery;
	    sQuery = "SELECT File_Name, File_Size, File_Local_Path, P2P_Status, P2P_Type, File_Cookie  FROM tb_p2p WHERE P2P_Cookie='";
        sQuery += getP2PCookie();
        sQuery += "';";

    	rowList myList;
        myList = pSQLiteDB->getRecords( stConfig.p2pdbfilepath, sQuery );

        if ( myList.size() > 0 ) {

//             kdDebug() << "File_Name:[" << myList.first()[0] << "], "  \
//                       << "File_Size:[" << myList.first()[1] << "], "  \
//                       << "File_Local_Path:[" << myList.first()[2] << "], "  \
//                       << "P2P_Status:[" << myList.first()[3] << "], "  \
//                       << "P2P_Type:[" << myList.first()[4] << "], "  \
//                       << "File_Cookie:[" << myList.first()[5] << "]" << endl;

            if ( myList.first()[3] != "S" ) {
                setType( SEND );
                kdDebug() << "P2P_Status is Not 'S' : " << myList.first()[3] << endl;
                return;
            }

            setFileName( myList.first()[0] );
            setFileSize( myList.first()[1].toInt() );
            setFilePath( myList.first()[2] );
            setFileCookie( myList.first()[5] );

            if ( myList.first()[4] == "R" ) {
                setType( RECEIVE );

                QString sCommand;
                sCommand = "100";
                sCommand += " ";
                sCommand += "6004";
                sCommand += " ";
                sCommand += "0";
                sCommand += "\r\n";
                sendCommand( "ATHC", sCommand );

                sCommand = "ACCEPT";
                sCommand += " ";
                sCommand += getFileCookie();
                sCommand += " ";
                sCommand += "0";
                sCommand += "\r\n";
                sendCommand( "FILE", sCommand );

                sQuery = "UPDATE tb_p2p SET P2P_Status='B' WHERE P2P_Cookie='";
                sQuery += getP2PCookie();
                sQuery += "';";

                pSQLiteDB->execOne( stConfig.p2pdbfilepath, sQuery );

                QDir::setCurrent( getFilePath() );
                fFile.setName( getFileName() );

                if ( fFile.exists () ) {
                    if ( isOverWrite() ) {
                        fFile.remove(); /*! 파일이 존재하면 삭제를 한다. */
                    } else {
                        setOffset( fFile.size() ); /*! 재 파일 수신시 offset */
                    }
                }

                fFile.open( IO_ReadWrite );
            }
            else {
                setType( SEND );
            }
        }
        else {
            /*!
              P2P Cookie 가 없다.
              내가 보내는 경우 ATHC를 받으면 P2P Cookie를 알 수 없음.
              ATHC 응답을 주면,
              FILE ACCEPT가 올 것임.
            */
            kdDebug() << "myList.size() = " << myList.size() << endl;

            QString sCommand;
            sCommand = "100";
            sCommand += " ";
            sCommand += "6004";
            sCommand += " ";
            sCommand += "0";
            sCommand += "\r\n";
            sendCommand( "ATHC", sCommand );
        }
    } else {
        QString sQuery;
	    sQuery = "SELECT File_Name, File_Size, File_Local_Path, P2P_Status, P2P_Type, File_Cookie  FROM tb_p2p WHERE P2P_Cookie='";
        sQuery += getP2PCookie();
        sQuery += "';";

    	rowList myList;
        myList = pSQLiteDB->getRecords( stConfig.p2pdbfilepath, sQuery );

        if ( myList.size() > 0 ) {

//             kdDebug() << "File_Name:[" << myList.first()[0] << "], "  \
//                       << "File_Size:[" << myList.first()[1] << "], "  \
//                       << "File_Local_Path:[" << myList.first()[2] << "], "  \
//                       << "P2P_Status:[" << myList.first()[3] << "], "  \
//                       << "P2P_Type:[" << myList.first()[4] << "], "  \
//                       << "File_Cookie:[" << myList.first()[5] << "]" << endl;

            if ( myList.first()[3] != "S" ) {
                kdDebug() << "P2P_Status is Not 'S' : " << myList.first()[3] << endl;
                return;
            }

            setFileName( myList.first()[0] );
            setFileSize( myList.first()[1].toInt() );
            setFilePath( myList.first()[2] );
            setFileCookie( myList.first()[5] );

            if ( myList.first()[4] == "R" ) {
                setType( RECEIVE );

                if ( sFRCookie != QString::null ) {
                    QString sCommand;
                    sCommand = "100";
                    sCommand += " ";
                    sCommand += "6004";
                    sCommand += " ";
                    sCommand += "0";
                    sCommand += "\r\n";
                    sendCommand( "ATHC", sCommand );
                }

                // emit yesOrNoAccept( getCookie() );
                QString sCommand;
                sCommand = "ACCEPT";
                sCommand += " ";
                sCommand += getFileCookie();
                sCommand += " ";
                sCommand += "0";
                sCommand += "\r\n";
                sendCommand( "FILE", sCommand );

                sQuery = "UPDATE tb_p2p SET P2P_Status='B' WHERE P2P_Cookie='";
                sQuery += getP2PCookie();
                sQuery += "';";

                pSQLiteDB->execOne( stConfig.p2pdbfilepath, sQuery );

                QDir::setCurrent( getFilePath() );
                fFile.setName( getFileName() );

                if ( fFile.exists () ) {
                    if ( isOverWrite() ) {
                        fFile.remove(); /*! 파일이 존재하면 삭제를 한다. */
                    } else {
                        setOffset( fFile.size() ); /*! 재 파일 수신시 offset */
                    }
                }

                fFile.open( IO_ReadWrite );
            }
            else {
                setType( SEND );
            }
        }
    }
}

/*!
  파일 보내기
  FILE 0 ACCEPT 71:214783475:156 0
*/
void NOMP2PBase::gotACCEPT(QStringList & slCommand)
{
    setType( SEND );
    if ( getConnectType() == CLIENT ) { /*! 상대 서버에 접속시 */
        setFileCookie( slCommand[3] );

        /*! 내가 ATHC 를 보냈음 */
        QString sQuery;
        sQuery = "SELECT File_Name, File_Size, File_Local_Path, P2P_Status  FROM tb_p2p WHERE File_Cookie='";
        sQuery += slCommand[3];
        sQuery += "';";

        // pSQLiteDB->getRecords( stConfig.p2pdbfilepath, sQuery );
    	rowList myList;
        myList = pSQLiteDB->getRecords( stConfig.p2pdbfilepath, sQuery );
        if ( myList.size() > 0 ) {
            if ( myList.first()[3] != "S" ) {
                kdDebug() << "Status is Not 'S'" << endl;
                return;
            }
            setFileName( myList.first()[0] );
            setFileSize( myList.first()[1].toULong() );
            setFilePath( myList.first()[2] );
            QString sCommand;
            sCommand = "INFO";
            sCommand += " ";
            sCommand += "FILENAME";
            sCommand += " ";
            sCommand += myList.first()[1];
            sCommand += " ";
            sCommand += "CHAT";
            sCommand += " ";
            sCommand += "0";
            sCommand += "\r\n";

            sendCommand( "FILE", sCommand );
        }
    }
    else { /*! 내 서버에 접속시 */
        /*! 상대가 ATHC 를 보냈음 */
    }
}

void NOMP2PBase::gotINFO(QStringList & slCommand)
{
    QString sCommand;
    sCommand = "START";
    sCommand += " ";
    sCommand += QString::number( fFileOffset );
    sCommand += " ";
    sCommand += "0";
    sCommand += "\r\n";

    sendCommand( "FILE", sCommand, getTID() );
}

void NOMP2PBase::slotWriteToSocket( int nSocket )
{
    if ( fFile.atEnd() ) {
        fFile.close();
        return;
    }

    char *pBuffer;
    pBuffer = (char *)malloc( 9216 );
    char *pBlock;
    pBlock = (char *)malloc( 8192 );
    int nHeader = 0;
    int nBody = 0;

    memset( pBlock, 0x00, 8192 );
    nBody = fFile.readBlock( pBlock, 8192 );
    if ( nBody > 0 ) {
        memset( pBuffer, 0x00, 9216 );
        sprintf( pBuffer, "FILE %d DATA %d\r\n", getTID(), nBody );
        emit OutgoingMessage( "[ P2P W ]-{" + QString( pBuffer ) + "}-" );
        nHeader = strlen( pBuffer );

        memcpy( pBuffer + nHeader, pBlock, 8192 );
        sendBinData( pBuffer, nHeader + nBody );
        emit updateProgress( sFileCookie, fFile.at() );
    }
    free( pBuffer );
    free( pBlock );
}

void NOMP2PBase::gotSTART(QStringList & slCommand)
{
    kdDebug() << "gotSTART!!!" << endl;

    /*! 파일 보내기 */
    QDir::setCurrent( getFilePath() );
    fFile.setName( getFileName() );
    if ( fFile.exists() ) {
        setTID( 1 );

        fFile.open( IO_ReadOnly );

        QSocketNotifier *sn = new QSocketNotifier( pSocket->socket(), QSocketNotifier::Write );
        connect( sn, SIGNAL( activated( int ) ), SLOT( slotWriteToSocket( int ) ) );
    }
    else {
        kdDebug() << "File Not Found : [" << getFilePath() << "], [" << getFileName() << "]" << endl;
    }
}

void NOMP2PBase::gotDATA(QStringList & slCommand)
{
}

void NOMP2PBase::gotEND(QStringList & slCommand)
{
    emit endProgress( sFileCookie );

    kdDebug() << "The End!!!" << endl;
    pSocket->flush();
    pSocket->close();
}

void NOMP2PBase::sendATHC( const QString &sMyID, const QString &sYourID, const QString &sP2PCookie, int nNegotiationPort )
{
    QString sCommand;
    sCommand = sMyID;
    sCommand += " ";
    sCommand += sYourID;
    sCommand += " ";
    sCommand += sP2PCookie;
    sCommand += " ";
    sCommand += QString::number( nNegotiationPort );
    sCommand += " ";
    sCommand += "0";
    sCommand += "\r\n";

    sendCommand( "ATHC", sCommand );
}

void NOMP2PBase::slotError( int nErrNo )
{
    if ( pP2PTimer ) {
        if ( pP2PTimer->isActive() ) {
            pP2PTimer->stop();
            delete pP2PTimer;
        }
    }

    pSocket->close();
    delete pSocket;

    kdDebug() << "Connect Error" << endl;

    if ( nErrNo == QSocket::ErrConnectionRefused ||
         nErrNo == QSocket::ErrHostNotFound ) {
        if ( !sP2PCookie.isNull() ) {
            kdDebug() << "emit tryREFR" << endl;

            emit tryREFR( getP2PCookie() );
        }
    }
}

void NOMP2PBase::slotConnectError()
{
    pSocket->close();
    delete pSocket;

    kdDebug() << "emit tryREFR" << endl;

    if ( !sP2PCookie.isNull() ) {
        if ( eType == RECEIVE ) {
            emit tryREFR( getP2PCookie() );
        }
        else {
            /*! CTOC ... REQC RFR ... Commmand를 상대편에게 날림 */
            emit sendRFR( getP2PCookie() );
        }
    }
}


void NOMP2PBase::connectToServer( const QString &sHost, const int nPort )
{
    pSocket = new QSocket( this, "client_socket" );
    pSocket->connectToHost( sHost, nPort );
	if ( pP2PTimer )
        delete pP2PTimer;
    pP2PTimer = new QTimer( this, "timer" );
    connect( pP2PTimer, SIGNAL( timeout() ), this, SLOT( slotConnectError() ) );
    pP2PTimer->start(5000, TRUE);

    connect( pSocket, SIGNAL( connected() ), SLOT( slotConnected() ) );
    connect( pSocket, SIGNAL( readyRead() ), SLOT( slotReadyRead() ));
//     connect( pSocket, SIGNAL( connectionClosed() ), SLOT( slotDisconnected() ) );
    connect( pSocket, SIGNAL( error ( int ) ), SLOT ( slotError( int ) ) );
}

void NOMP2PBase::connectToFRServer( const QString &sHost, const int nPort )
{
    /* QSocket *pSocket = new QSocket(); */
    pSocket = new QSocket( this, "client_socket" );
    pSocket->connectToHost( sHost, nPort );

    connect( pSocket, SIGNAL( connected() ), SLOT( slotFRConnected() ) );
    connect( pSocket, SIGNAL( readyRead() ), SLOT( slotReadyRead() ));
//     connect( pSocket, SIGNAL( connectionClosed() ), SLOT( slotDisconnected() ) );
    connect( pSocket, SIGNAL( error ( int ) ), SLOT ( slotError( int ) ) );
}

void NOMP2PBase::slotFRConnected()
{
    if ( eType == RECEIVE ) {
        QString sCommand;
        sCommand = sYourID;
        sCommand += " ";
        sCommand += sFRCookie;
        sCommand += "\r\n";

        sendCommand( "FRIN", sCommand );
    }
    else {
        /*!
          FR에서 "보내기"에서 ATHC를 보내야 한다.
        */
        if ( slCommandQueue.count() > 0 )
        {
            for ( QStringList::Iterator it = slCommandQueue.begin(); it != slCommandQueue.end(); ++it ) {
                /*! ex) slCommand : "ATHC|sender@nate.com receiver@nate.com 12222222:2222 6004 0\r\n" */
                QStringList slCommand = QStringList::split( "|", *it );
                sendCommand( slCommand[0], slCommand[1] );
            }
        }
    }
}

void NOMP2PBase::gotFRIN( QStringList &slCommand )
{
    QString sBody;
    sBody = "REQC";
    sBody += " ";
    sBody += "FR";
    sBody += " ";
    sBody += sFRIP;
    sBody += ":";
    sBody += sFRPort;
    sBody += " ";
    sBody += sP2PCookie; /// P2P Cookie
    sBody += " ";
    sBody += sFRCookie;
    sBody += "\r\n";

    QString sCommand;
    sCommand = sYourID; /// Your ID, Source ID
    sCommand += " ";
    sCommand += "N";
    sCommand += " ";
    sCommand += QString::number( sBody.length() );
    sCommand += "\r\n";
    sCommand += sBody;

    emit sendREQCFR( sCommand );
}

