/*
  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-2008 Eike Hein <hein@kde.org>
*/

#include "chatwindow.h"
#include "channel.h"
#include "ircview.h"
#include "server.h"
#include "konversationapplication.h"
#include "logfilereader.h"

#include <tqdatetime.h>
#include <tqdir.h>
#include <tqregexp.h>
#include <tqtextcodec.h>
#include <tqtooltip.h>
#include <tqlayout.h>

#include <tdelocale.h>
#include <kdialog.h>
#include <kdebug.h>
#include <tdeactioncollection.h>
#include <tdeaction.h>


ChatWindow::ChatWindow(TQWidget* parent) : TQVBox(parent)
{
    setName("ChatWindowObject");
    setTextView(0);
    parentWidget=parent;
    firstLog=true;
    m_server=0;
    m_notificationsEnabled = true;
    m_channelEncodingSupported = false;
    m_currentTabNotify = Konversation::tnfNone;

    setMargin(margin());
    setSpacing(spacing());

    // The font size of the KTabWidget container may be inappropriately
    // small due to the "Tab bar" font size setting.
    setFont(TDEGlobalSettings::generalFont());
}

ChatWindow::~ChatWindow()
{
    emit closing(this);
    m_server=0;
}

void ChatWindow::updateAppearance()
{
    // The font size of the KTabWidget container may be inappropriately
    // small due to the "Tab bar" font size setting.
    setFont(TDEGlobalSettings::generalFont());

    if (textView)
    {
        if (Preferences::showIRCViewScrollBar())
            textView->setVScrollBarMode(TQScrollView::AlwaysOn);
        else
            textView->setVScrollBarMode(TQScrollView::AlwaysOff);
    }
}

void ChatWindow::setName(const TQString& newName)
{
    name=newName;
    emit nameChanged(this,newName);
}

TQString ChatWindow::getName()
{
    return name;
}

void ChatWindow::setType(WindowType newType)
{
    type=newType;
}

ChatWindow::WindowType ChatWindow::getType()
{
    return type;
}

void ChatWindow::setServer(Server* newServer)
{
    if (!newServer)
    {
        kdDebug("ChatWindow::setServer(0)!") << endl;
    }
    else
    {
        m_server=newServer;
        connect(m_server,TQT_SIGNAL (serverOnline(bool)),this,TQT_SLOT (serverOnline(bool)) );

        // check if we need to set up the signals
        if(getType() != ChannelList)
        {
            if(textView) textView->setServer(newServer);
            else kdDebug() << "ChatWindow::setServer(): textView==0!" << endl;
        }

        emit serverOnline(m_server->isConnected());
    }
}

Server* ChatWindow::getServer()
{
    return m_server;
}

void ChatWindow::serverOnline(bool /* state */)
{
    //emit online(this,state);
}

void ChatWindow::setTextView(IRCView* newView)
{
    textView = newView;

    if(!textView)
    {
        return;
    }

    if(Preferences::showIRCViewScrollBar())
    {
        textView->setVScrollBarMode(TQScrollView::Auto);
    }
    else
    {
        textView->setVScrollBarMode(TQScrollView::AlwaysOff);
    }

    textView->setChatWin(this);
    connect(textView,TQT_SIGNAL(textToLog(const TQString&)), this,TQT_SLOT(logText(const TQString&)));
    connect(textView,TQT_SIGNAL(setStatusBarTempText(const TQString&)), this, TQT_SIGNAL(setStatusBarTempText(const TQString&)));
    connect(textView,TQT_SIGNAL(clearStatusBarTempText()), this, TQT_SIGNAL(clearStatusBarTempText()));
}

void ChatWindow::appendRaw(const TQString& message, bool suppressTimestamps)
{
    if(!textView) return;
    textView->appendRaw(message, suppressTimestamps);
}

void ChatWindow::append(const TQString& nickname,const TQString& message)
{
    if(!textView) return ;
    textView->append(nickname,message);
}

void ChatWindow::appendQuery(const TQString& nickname,const TQString& message, bool inChannel)
{
    if(!textView) return ;
    textView->appendQuery(nickname,message, inChannel);
}

void ChatWindow::appendAction(const TQString& nickname, const TQString& message)
{
    if(!textView) return;

    if (getType() == Query || getType() == DccChat)
        textView->appendQueryAction(nickname, message);
    else
        textView->appendChannelAction(nickname, message);
}

void ChatWindow::appendServerMessage(const TQString& type,const TQString& message, bool parseURL)
{
    if(!textView) return ;
    textView->appendServerMessage(type,message, parseURL);
}

void ChatWindow::appendCommandMessage(const TQString& command,const TQString& message, bool important, bool parseURL, bool self)
{
    if(!textView) return ;
    textView->appendCommandMessage(command,message,important, parseURL, self);
}

void ChatWindow::appendBacklogMessage(const TQString& firstColumn,const TQString& message)
{
    if(!textView) return ;
    textView->appendBacklogMessage(firstColumn,message);
}

void ChatWindow::cdIntoLogPath()
{
    TQDir logPath=TQDir::home();
    // Try to "cd" into the logfile path
    if(!logPath.cd(Preferences::logfilePath(),true))
    {
        // Only create log path if logging is enabled
        if(log)
        {
            // Try to create the logfile path and "cd" into it again
            logPath.mkdir(Preferences::logfilePath(),true);
            logPath.cd(Preferences::logfilePath(),true);
        }
    }

    // add the logfile name to the path
    logfile.setName(logPath.path()+'/'+logName);
}

void ChatWindow::setLogfileName(const TQString& name)
{
    // Only change name of logfile if the window was new.
    if(firstLog)
    {
        // status panels get special treatment here, since they have no server at the beginning
        if (getType() == Status || getType() == DccChat)
        {
            logName = name + ".log";
        }
        else if (m_server)
        {
            // make sure that no path delimiters are in the name
            logName = TQString(TQString(m_server->getDisplayName().lower()).append('_').append(name).append(".log")).replace('/','_');
        }

        // load backlog to show
        if(Preferences::showBacklog())
        {
            // "cd" into log path or create path, if it's not there
            cdIntoLogPath();
            // Show last log lines. This idea was stole ... um ... inspired by PMP :)
            // Don't do this for the server status windows, though
            if((getType() != Status) && logfile.open(IO_ReadOnly))
            {
                unsigned long filePosition;

                TQString backlogLine;
                TQTextStream backlog(&logfile);
                backlog.setEncoding(TQTextStream::UnicodeUTF8);

                TQStringList firstColumns;
                TQStringList messages;
                int offset = 0;
                unsigned int lastPacketHeadPosition = backlog.device()->size();
                const unsigned int packetSize = 4096;
                while(messages.count() < (unsigned int)Preferences::backlogLines() && backlog.device()->size() > packetSize * offset)
                {
                    TQStringList firstColumnsInPacket;
                    TQStringList messagesInPacket;

                    // packetSize * offset < size <= packetSize * ( offset + 1 )

                    // Check if the log is bigger than packetSize * ( offset + 1 )
                    if(backlog.device()->size() > packetSize * ( offset + 1 ))
                    {
                        // Set file pointer to the packet size above the offset
                        backlog.device()->at(backlog.device()->size() - packetSize * ( offset + 1 ));
                        // Skip first line, since it may be incomplete
                        backlog.readLine();
                    }
                    else
                    {
                        // Set file pointer to the head
                        backlog.device()->reset();
                    }

                    unsigned int currentPacketHeadPosition = backlog.device()->at();

                    // Loop until end of file reached
                    while(!backlog.atEnd() && backlog.device()->at() < lastPacketHeadPosition)
                    {
                        // remember actual file position to check for deadlocks
                        filePosition = backlog.device()->at();
                        backlogLine = backlog.readLine();

                        // check for deadlocks
                        if(backlog.device()->at() == filePosition) backlog.device()->at(filePosition + 1);

                        // if a tab character is present in the line
                        if(backlogLine.find('\t') != -1)
                        {
                            // extract first column from log
                            TQString backlogFirst = backlogLine.left(backlogLine.find('\t'));
                            // cut first column from line
                            backlogLine = backlogLine.mid(backlogLine.find('\t') + 1);
                            // Logfile is in utf8 so we don't need to do encoding stuff here
                            // append backlog with time and first column to text view
                            firstColumnsInPacket << backlogFirst;
                            messagesInPacket << backlogLine;
                        }
                    } // while

                    // remember the position not to read the same lines again
                    lastPacketHeadPosition = currentPacketHeadPosition;
                    ++offset;

                    firstColumns = firstColumnsInPacket + firstColumns;
                    messages = messagesInPacket + messages;
                }
                backlog.unsetDevice();
                logfile.close();

                // trim
                int surplus = messages.count() - Preferences::backlogLines();
                // "surplus" can be a minus value. (when the backlog is too short)
                if(surplus > 0)
                {
                    for(int i = 0 ; i < surplus ; ++i)
                    {
                        firstColumns.pop_front();
                        messages.pop_front();
                    }
                }

                TQStringList::Iterator itFirstColumn = firstColumns.begin();
                TQStringList::Iterator itMessage = messages.begin();
                for( ; itFirstColumn != firstColumns.end() ; ++itFirstColumn, ++itMessage )
                    appendBacklogMessage(*itFirstColumn, *itMessage);
            }
        } // if(Preferences::showBacklog())
    }
}

void ChatWindow::logText(const TQString& text)
{
    if(log)
    {
        // "cd" into log path or create path, if it's not there
        cdIntoLogPath();

        if(logfile.open(IO_WriteOnly | IO_Append))
        {
            // wrap the file into a stream
            TQTextStream logStream(&logfile);
            // write log in utf8 to help i18n
            logStream.setEncoding(TQTextStream::UnicodeUTF8);

            if(firstLog)
            {
                TQString intro(i18n("\n*** Logfile started\n*** on %1\n\n").arg(TQDateTime::currentDateTime().toString()));
                logStream << intro;
                firstLog=false;
            }

            TQTime time=TQTime::currentTime();
            TQString logLine(TQString("[%1] [%2] %3\n").arg(TQDate::currentDate(Qt::LocalTime).toString()).
                arg(time.toString("hh:mm:ss")).arg(text));

            logStream << logLine;

            // detach stream from file
            logStream.unsetDevice();

            // close file
            logfile.close();
        }
        else kdWarning() << "ChatWindow::logText(): open(IO_Append) for " << logfile.name() << " failed!" << endl;
    }
}

void ChatWindow::setChannelEncodingSupported(bool enabled)
{
    m_channelEncodingSupported = enabled;
}

bool ChatWindow::isChannelEncodingSupported() const
{
    return m_channelEncodingSupported;
}

int ChatWindow::spacing()
{
    if(Preferences::useSpacing())
        return Preferences::spacing();
    else
        return KDialog::spacingHint();
}

int ChatWindow::margin()
{
    if(Preferences::useSpacing())
        return Preferences::margin();
    else
        return 0;
}

// Accessors
IRCView* ChatWindow::getTextView() const 
{ 
  return textView; 
}

void ChatWindow::setLog(bool activate) 
{ 
  log=activate; 
}

// reimplement this in all panels that have user input
TQString ChatWindow::getTextInLine()    
{ 
  return TQString(); 
}

bool ChatWindow::canBeFrontView()           
{ 
  return false; 
}

bool ChatWindow::searchView()          
{ 
  return false; 
}

// reimplement this in all panels that have user input
void ChatWindow::indicateAway(bool)
{
}

// reimplement this in all panels that have user input
void ChatWindow::appendInputText(const TQString&, bool)
{
}

// reimplement this if your window needs special close treatment
bool ChatWindow::closeYourself(bool /* askForConfirmation */)
{
    deleteLater();

    return true;
}

bool ChatWindow::eventFilter(TQObject* watched, TQEvent* e)
{
    if(e->type() == TQEvent::KeyPress)
    {
        TQKeyEvent* ke = TQT_TQKEYEVENT(e);

        bool scrollMod = (Preferences::useMultiRowInputBox() ? false : (ke->state() == TQt::ShiftButton));

        if(ke->key() == TQt::Key_Up && scrollMod)
        {
            if(textView)
            {
                TQScrollBar* sbar = textView->verticalScrollBar();
                sbar->setValue(sbar->value() - sbar->lineStep());
            }

            return true;
        }
        else if(ke->key() == TQt::Key_Down && scrollMod)
        {
            if(textView)
            {
                TQScrollBar* sbar = textView->verticalScrollBar();
                sbar->setValue(sbar->value() + sbar->lineStep());
            }

            return true;
        }
        else if(ke->key() == TQt::Key_Prior)
        {
            if(textView)
            {
                TQScrollBar* sbar = textView->verticalScrollBar();
                sbar->setValue(sbar->value() - sbar->pageStep());
            }

            return true;
        }
        else if(ke->key() == TQt::Key_Next)
        {
            if(textView)
            {
                TQScrollBar* sbar = textView->verticalScrollBar();
                sbar->setValue(sbar->value() + sbar->pageStep());
            }

            return true;
        }

    }

    return TQVBox::eventFilter(watched, e);
}

void ChatWindow::adjustFocus()
{
    childAdjustFocus();
}

void ChatWindow::emitUpdateInfo()
{
    TQString info = getName();
    emit updateInfo(info);
}

TQColor ChatWindow::highlightColor()
{
    return getTextView()->highlightColor();
}

void ChatWindow::activateTabNotification(Konversation::TabNotifyType type)
{
    if (!notificationsEnabled())
        return;

    if(type > m_currentTabNotify)
        return;

    m_currentTabNotify = type;

    emit updateTabNotification(this,type);
}

void ChatWindow::resetTabNotification()
{
    m_currentTabNotify = Konversation::tnfNone;
}

#include "chatwindow.moc"
