/*
  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 2 of the License, or
  (at your option) any later version.
*/

/*
  Copyright (C) 2002 Dario Abatianni <eisfuchs@tigress.com>
  Copyright (C) 2006 Eike Hein <hein@kde.org>
  Copyright (C) 2004,2007 Shintaro Matsuoka <shin@shoegazed.org>
*/

#include "dccchat.h"
#include "dcccommon.h"
#include "konversationapplication.h"
#include "konversationmainwindow.h"
#include "irccharsets.h"
#include "ircview.h"
#include "ircviewbox.h"
#include "ircinput.h"
#include "topiclabel.h"
#include "server.h"
#include "channel.h"

#include <stdlib.h>
#include <sys/types.h>
#include <netinet/in.h>

#include <tqvbox.h>
#include <tqhostaddress.h>
#include <tqtextcodec.h>
#include <tqsplitter.h>
#include <tqpopupmenu.h>

#include <klineedit.h>
#include <tdelocale.h>
#include <tdemessagebox.h>
#include <kdebug.h>
#include <kserversocket.h>
#include <tdesocketaddress.h>
#include <kstreamsocket.h>
#include <tdeaction.h>

#define DCCCHAT_BUFFER_SIZE 1024


DccChat::DccChat(TQWidget* parent, bool listen, Server* server, const TQString& ownNick, const TQString& partnerNick, const TQString& partnerHost, int partnerPort)
    : ChatWindow(parent)
{
    kdDebug() << "DccChat::DccChat() [BEGIN]" << endl;

    m_dccSocket = 0;
    m_listenSocket = 0;

    m_ownNick = ownNick;

    m_partnerNick = partnerNick;
    m_partnerHost = partnerHost;
    m_partnerPort = partnerPort;

    setType(ChatWindow::DccChat);
    setChannelEncodingSupported(true);

    m_headerSplitter = new TQSplitter(TQt::Vertical, this);

    m_sourceLine = new Konversation::TopicLabel(m_headerSplitter);

    IRCViewBox* ircViewBox = new IRCViewBox(m_headerSplitter, NULL);
    setTextView(ircViewBox->ircView());

    m_dccChatInput = new IRCInput(this);
    getTextView()->installEventFilter(m_dccChatInput);
    m_dccChatInput->setEnabled( false );

    ChatWindow::setName( '-' + m_partnerNick + '-' );
    ChatWindow::setLogfileName( '-' + m_partnerNick + '-' );

    TQPopupMenu* popup = textView->getPopup();

    if (popup)
    {
        TDEAction* action = KonversationApplication::instance()->getMainWindow()->actionCollection()->action("open_logfile");

        if (action)
        {
            popup->insertSeparator();
            action->plug(popup);
        }
    }

    // connect the signals and slots
    connect( m_dccChatInput, TQ_SIGNAL( submit() ), this, TQ_SLOT( dccChatTextEntered() ) );
    connect( m_dccChatInput, TQ_SIGNAL( textPasted( const TQString& ) ), this, TQ_SLOT( textPasted( const TQString& ) ) );

    connect( getTextView(), TQ_SIGNAL( textPasted(bool) ), m_dccChatInput, TQ_SLOT( paste(bool) ) );
    connect( getTextView(), TQ_SIGNAL( gotFocus() ), m_dccChatInput, TQ_SLOT( setFocus() ) );
    connect( getTextView(), TQ_SIGNAL( autoText(const TQString&) ), this, TQ_SLOT( sendDccChatText( const TQString& ) ) );

    if (listen)
    {
        listenForPartner();
        TQString ownNumericalIp = DccCommon::textIpToNumericalIp( DccCommon::getOwnIp( server ) );
        server->requestDccChat( m_partnerNick, ownNumericalIp, TQString::number( m_ownPort ) );
    }
    else
    {
        connectToPartner();
    }

    kdDebug() << "DccChat::DccChat() [END]" << endl;

    updateAppearance();
}

DccChat::~DccChat()
{
    kdDebug() << "DccChat::~DccChat()" << endl;
    if(m_dccSocket)
        m_dccSocket->close();
    if(m_listenSocket)
        m_listenSocket->close();
}

void DccChat::listenForPartner()
{
    kdDebug() << "DccChat::listenForPartner() [BEGIN]" << endl;

    // Set up server socket
    TQString failedReason;
    if ( Preferences::dccSpecificChatPorts() )
        m_listenSocket = DccCommon::createServerSocketAndListen( this, &failedReason, Preferences::dccChatPortsFirst(), Preferences::dccChatPortsLast() );
    else
        m_listenSocket = DccCommon::createServerSocketAndListen( this, &failedReason );
    if ( !m_listenSocket )
    {
        getTextView()->appendServerMessage( i18n( "DCC" ), i18n( "Could not open a socket for listening: %1" ).arg( failedReason ) );
        return;
    }

    connect( m_listenSocket, TQ_SIGNAL(readyAccept()), this, TQ_SLOT(heardPartner()) );

    // Get our own port number
    m_ownPort = DccCommon::getServerSocketPort( m_listenSocket );
    kdDebug() << "DccChat::listenForPartner(): using port " << m_ownPort << endl;

    getTextView()->appendServerMessage( i18n("DCC"), i18n("Offering DCC Chat connection to %1 on port %2...").arg( m_partnerNick ).arg( m_ownPort ) );
    m_sourceLine->setText(i18n( "DCC chat with %1 on port %2." ).arg( m_partnerNick ).arg( m_ownPort ) );
    kdDebug() << "DccChat::listenForPartner() [END]" << endl;
}

void DccChat::connectToPartner()
{
    TQHostAddress ip;

    ip.setAddress(m_partnerHost.toUInt());
    m_partnerHost=ip.toString();

    getTextView()->appendServerMessage( i18n( "DCC" ), i18n( "%1 = nickname, %2 = IP, %3 = port",
        "Establishing DCC Chat connection to %1 (%2:%3)..." ).arg( m_partnerNick ).arg( m_partnerHost ).arg( m_partnerPort ) );

    m_sourceLine->setText( i18n( "%1 = nickname, %2 = IP, %3 = port", "DCC chat with %1 on %2:%3." ).arg( m_partnerNick ).arg( host ).arg( m_partnerPort ) );

    m_dccSocket = new KNetwork::KStreamSocket( m_partnerHost, TQString::number( m_partnerPort ), this );

    m_dccSocket->setBlocking(false);
    m_dccSocket->setFamily(KNetwork::KResolver::InetFamily);
    m_dccSocket->enableRead(false);
    m_dccSocket->enableWrite(false);
    m_dccSocket->setTimeout(10000);
    m_dccSocket->blockSignals(false);

    connect( m_dccSocket, TQ_SIGNAL( hostFound() ),                        this, TQ_SLOT( lookupFinished() )           );
    connect( m_dccSocket, TQ_SIGNAL( connected( const KResolverEntry& ) ), this, TQ_SLOT( dccChatConnectionSuccess() ) );
    connect( m_dccSocket, TQ_SIGNAL( gotError( int ) ),                    this, TQ_SLOT( dccChatBroken( int ) )       );
    connect( m_dccSocket, TQ_SIGNAL( readyRead() ),                        this, TQ_SLOT( readData() )                 );
    connect( m_dccSocket, TQ_SIGNAL( closed() ),                           this, TQ_SLOT( socketClosed() )             );

    m_dccSocket->connect();
    
#if 0
    //getTextView()->appendServerMessage(i18n("DCC"),i18n("Looking for host %1...").arg(host));
#endif

}

void DccChat::lookupFinished()
{
	
#if 0
	//getTextView()->appendServerMessage(i18n("DCC"),i18n("Host found, connecting..."));
#endif

}

void DccChat::dccChatConnectionSuccess()
{
    getTextView()->appendServerMessage( i18n( "DCC" ), i18n( "Established DCC Chat connection to %1." ).arg( m_partnerNick ) );
    m_dccSocket->enableRead(true);
    m_dccChatInput->setEnabled(true);
}

void DccChat::dccChatBroken(int error)
{
    getTextView()->appendServerMessage(i18n("Error"),i18n("Connection broken, error code %1.").arg(error));
    m_dccSocket->enableRead(false);
    m_dccSocket->blockSignals(true);
    m_dccSocket->close();
}

void DccChat::readData()
{
    kdDebug() << k_funcinfo << ( m_listenSocket == 0 ) << " BEGIN" << endl;
    int available=0;
    int actual=0;
    char* buffer=0;
    TQString line;
    TQTextCodec* codec = Konversation::IRCCharsets::self()->codecForName(m_encoding.isEmpty() ? Konversation::IRCCharsets::self()->encodingForLocale() : m_encoding);

    available = m_dccSocket->bytesAvailable();
    if( available > 0 )
    {
        buffer = new char[ available + 1 ];
        actual = m_dccSocket->readBlock( buffer, available );
        buffer[ actual ] = 0;
        line.append( codec->toUnicode( buffer ) );
        delete[] buffer;

        TQStringList lines = TQStringList::split( '\n', line );

        for( TQStringList::iterator itLine = lines.begin() ; itLine != lines.end() ; itLine++ )
        {
            if( (*itLine).startsWith( "\x01" ) )
            {
                // cut out the CTCP command
                TQString ctcp = (*itLine).mid( 1, (*itLine).find( 1, 1 ) - 1 );

                TQString ctcpCommand = ctcp.section( " ", 0, 0 );
                TQString ctcpArgument = ctcp.section( " ", 1 );

                if( ctcpCommand.lower() == "action" )
                    appendAction( m_partnerNick, ctcpArgument );
                else
                    getTextView()->appendServerMessage( i18n( "CTCP" ), i18n( "Received unknown CTCP-%1 request from %2" ).arg( ctcp ).arg( m_partnerNick ) );
            }
            else getTextView()->append( m_partnerNick, *itLine );
        }                                         // endfor
    } else {
        dccChatBroken(m_dccSocket->error());
    }

    kdDebug() << k_funcinfo << " END" << endl;
}

void DccChat::dccChatTextEntered()
{
    TQString line = m_dccChatInput->text();
    m_dccChatInput->setText("");
    if ( line.lower() == Preferences::commandChar()+"clear" )
    {
        textView->clear();
    }
    else if ( !line.isEmpty() )
    {
        sendDccChatText(line);
    }
}

void DccChat::sendDccChatText(const TQString& sendLine)
{
    kdDebug() << k_funcinfo << " BEGIN" << endl;
    // create a work copy
    TQString output(sendLine);
    TQString cc=Preferences::commandChar();

    if(!output.isEmpty())
    {
        TQStringList lines = TQStringList::split('\n',output);
        // wrap socket into a stream
        TQTextStream stream(m_dccSocket);
        // init stream props
        stream.setCodec(Konversation::IRCCharsets::self()->codecForName(m_encoding.isEmpty() ? Konversation::IRCCharsets::self()->encodingForLocale() : m_encoding));

        for( TQStringList::iterator itLine = lines.begin() ; itLine != lines.end() ; itLine++ )
        {
            TQString line( *itLine );

            // replace aliases and wildcards
            //  if(filter.replaceAliases(line)) line=server->parseWildcards(line,nick,getName(),TQString(),TQString(),TQString());

            //  line=filter.parse(nick,line,getName());

            // convert /me actions
            TQString cmd=line.section(' ', 0,0).lower();
            if (cmd == cc+"me")
            {
                appendAction( m_ownNick, line.section( " ", 1 ) );
                line=TQString("\x01%1 %2\x01").arg("ACTION").arg(line.section(" ",1));
            }
            else if (cmd == cc+"close")
            {
                closeYourself(false);
                return;
            }
            else
                getTextView()->append( m_ownNick, line );

            stream << line << endl;
        }                                         // endfor

        // detach stream
        stream.unsetDevice();
    }
    kdDebug() << k_funcinfo << " END" << endl;
}

void DccChat::heardPartner()
{
    m_dccSocket = static_cast<KNetwork::KStreamSocket*>( m_listenSocket->accept() );

    if( !m_dccSocket )
    {
        getTextView()->appendServerMessage( i18n( "Error" ),i18n( "Could not accept the client." ) );
        return;
    }

    connect( m_dccSocket, TQ_SIGNAL( readyRead() ),     this, TQ_SLOT( readData() )           );
    connect( m_dccSocket, TQ_SIGNAL( closed() ),        this, TQ_SLOT( socketClosed() )       );
    connect( m_dccSocket, TQ_SIGNAL( gotError( int ) ), this, TQ_SLOT( dccChatBroken( int ) ) );

    // the listen socket isn't needed anymore
    disconnect( m_listenSocket, 0, 0, 0 );
    m_listenSocket->close();
    m_listenSocket = 0;

    m_dccSocket->enableRead(true);
    m_dccChatInput->setEnabled(true);

    getTextView()->appendServerMessage( i18n( "DCC" ), i18n( "Established DCC Chat connection to %1." ).arg( m_partnerNick ) );
}

void DccChat::socketClosed()
{
    getTextView()->appendServerMessage(i18n("DCC"),"Connection closed.");
    m_dccChatInput->setEnabled(false);
    m_dccSocket = 0;
}

void DccChat::textPasted(const TQString& text)
{
    sendDccChatText(text);
}

void DccChat::childAdjustFocus()
{
    m_dccChatInput->setFocus();
}

bool DccChat::canBeFrontView()
{
    return true;
}

bool DccChat::searchView()
{
    return true;
}

int DccChat::getOwnPort()
{
    return m_ownPort;
}

TQString DccChat::getTextInLine() 
{
    return m_dccChatInput->text();
}

void DccChat::appendInputText( const TQString& s, bool fromCursor )
{
    if(!fromCursor)
    {
        m_dccChatInput->append(s);
    }
    else
    {
        int para = 0, index = 0;
        m_dccChatInput->getCursorPosition(&para, &index);
        m_dccChatInput->insertAt(s, para, index);
        m_dccChatInput->setCursorPosition(para, index + s.length());
    }
}

//FIXME uh... where is the confimation for this?
bool DccChat::closeYourself(bool)
{
    deleteLater();
    return true;
}

void DccChat::setChannelEncoding(const TQString& encoding) // virtual
{
    m_encoding = encoding;
}

TQString DccChat::getChannelEncoding() // virtual
{
    return m_encoding;
}

TQString DccChat::getChannelEncodingDefaultDesc()  // virtual
{
    return i18n("Default ( %1 )").arg(Konversation::IRCCharsets::self()->encodingForLocale());
}

void DccChat::showEvent(TQShowEvent* /* event */)
{
    if(m_initialShow) {
        m_initialShow = false;
        TQValueList<int> sizes;
        sizes << m_sourceLine->sizeHint().height() << (height() - m_sourceLine->sizeHint().height());
        m_headerSplitter->setSizes(sizes);
    }
}

void DccChat::updateAppearance()
{
    TQColor fg;
    TQColor bg;

    if(Preferences::inputFieldsBackgroundColor())
    {
        fg=Preferences::color(Preferences::ChannelMessage);
        bg=Preferences::color(Preferences::TextViewBackground);
    }
    else
    {
        fg=colorGroup().foreground();
        bg=colorGroup().base();
    }

    m_dccChatInput->unsetPalette();
    m_dccChatInput->setPaletteForegroundColor(fg);
    m_dccChatInput->setPaletteBackgroundColor(bg);

    getTextView()->unsetPalette();

    if(Preferences::showBackgroundImage())
    {
        getTextView()->setViewBackground(Preferences::color(Preferences::TextViewBackground),
        Preferences::backgroundImage());
    }
    else
    {
        getTextView()->setViewBackground(Preferences::color(Preferences::TextViewBackground),
        TQString());
    }

    if (Preferences::customTextFont())
    {
        getTextView()->setFont(Preferences::textFont());
        m_dccChatInput->setFont(Preferences::textFont());
    }
    else
    {
        getTextView()->setFont(TDEGlobalSettings::generalFont());
        m_dccChatInput->setFont(TDEGlobalSettings::generalFont());
    }

    ChatWindow::updateAppearance();
}

#include "dccchat.moc"
