/*
  Copyright (C) 2008-2014 by Eike Hein <hein@kde.org>
  Copyright (C) 2009 by Juan Carlos Torres <carlosdgtorres@gmail.com>

  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) version 3 or any later version
  accepted by the membership of KDE e.V. (or its successor appro-
  ved by the membership of KDE e.V.), which shall act as a proxy
  defined in Section 14 of version 3 of the license.

  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, see http://www.gnu.org/licenses/.
*/


#include "sessionstack.h"
#include "settings.h"
#include "terminal.h"
#include "visualeventoverlay.h"

#include <KMessageBox>
#include <KLocalizedString>

#include <QtDBus/QtDBus>


SessionStack::SessionStack(QWidget* parent) : QStackedWidget(parent)
{
    QDBusConnection::sessionBus().registerObject(QStringLiteral("/yakuake/sessions"), this, QDBusConnection::ExportScriptableSlots);

    m_activeSessionId = -1;

    m_visualEventOverlay = new VisualEventOverlay(this);
    connect(this, SIGNAL(removeTerminalHighlight()), m_visualEventOverlay, SLOT(removeTerminalHighlight()));
}

SessionStack::~SessionStack()
{
}

int SessionStack::addSession(Session::SessionType type)
{
    Session* session = new Session(type, this);
    connect(session, SIGNAL(titleChanged(int,QString)), this, SIGNAL(titleChanged(int,QString)));
    connect(session, SIGNAL(terminalManuallyActivated(Terminal*)), this, SLOT(handleManualTerminalActivation(Terminal*)));
    connect(session, SIGNAL(keyboardInputBlocked(Terminal*)), m_visualEventOverlay, SLOT(indicateKeyboardInputBlocked(Terminal*)));
    connect(session, SIGNAL(activityDetected(Terminal*)), parentWidget(), SLOT(handleTerminalActivity(Terminal*)));
    connect(session, SIGNAL(silenceDetected(Terminal*)), parentWidget(), SLOT(handleTerminalSilence(Terminal*)));
    connect(parentWidget(), SIGNAL(windowClosed()), session, SLOT(reconnectMonitorActivitySignals()));
    connect(session, SIGNAL(destroyed(int)), this, SLOT(cleanup(int)));

    addWidget(session->widget());

    m_sessions.insert(session->id(), session);

    if (Settings::dynamicTabTitles())
        emit sessionAdded(session->id(), session->title());
    else
        emit sessionAdded(session->id(), QString());

    return session->id();
}

int SessionStack::addSessionTwoHorizontal()
{
    return addSession(Session::TwoHorizontal);
}

int SessionStack::addSessionTwoVertical()
{
    return addSession(Session::TwoVertical);
}

int SessionStack::addSessionQuad()
{
    return addSession(Session::Quad);
}

void SessionStack::raiseSession(int sessionId)
{
    if (sessionId == -1 || !m_sessions.contains(sessionId)) return;
    Session* session = m_sessions.value(sessionId);

    if (!m_visualEventOverlay->isHidden())
        m_visualEventOverlay->hide();

    if (m_activeSessionId != -1 && m_sessions.contains(m_activeSessionId))
    {
        Session* oldActiveSession = m_sessions.value(m_activeSessionId);

        disconnect(oldActiveSession, SLOT(closeTerminal()));
        disconnect(oldActiveSession, SLOT(focusPreviousTerminal()));
        disconnect(oldActiveSession, SLOT(focusNextTerminal()));
        disconnect(oldActiveSession, SLOT(manageProfiles()));
        disconnect(oldActiveSession, SIGNAL(titleChanged(QString)),
            this, SIGNAL(activeTitleChanged(QString)));

        oldActiveSession->reconnectMonitorActivitySignals();
    }

    m_activeSessionId = sessionId;

    setCurrentWidget(session->widget());

    if (session->widget()->focusWidget())
        session->widget()->focusWidget()->setFocus();

    if (session->hasTerminalsWithKeyboardInputDisabled())
        m_visualEventOverlay->show();

    connect(this, SIGNAL(closeTerminal()), session, SLOT(closeTerminal()));
    connect(this, SIGNAL(previousTerminal()), session, SLOT(focusPreviousTerminal()));
    connect(this, SIGNAL(nextTerminal()), session, SLOT(focusNextTerminal()));
    connect(this, SIGNAL(manageProfiles()), session, SLOT(manageProfiles()));
    connect(session, SIGNAL(titleChanged(QString)), this, SIGNAL(activeTitleChanged(QString)));

    emit sessionRaised(sessionId);

    emit activeTitleChanged(session->title());
}

void SessionStack::removeSession(int sessionId)
{
    if (sessionId == -1) sessionId = m_activeSessionId;
    if (sessionId == -1) return;
    if (!m_sessions.contains(sessionId)) return;

    if (queryClose(sessionId, QueryCloseSession))
        m_sessions.value(sessionId)->deleteLater();
}

void SessionStack::removeTerminal(int terminalId)
{
    int sessionId = sessionIdForTerminalId(terminalId);

    if (terminalId == -1)
    {
        if (m_activeSessionId == -1) return;
        if (!m_sessions.contains(m_activeSessionId)) return;

        if (m_sessions.value(m_activeSessionId)->closable())
            m_sessions.value(m_activeSessionId)->closeTerminal();
    }
    else
    {
        if (m_sessions.value(sessionId)->closable())
            m_sessions.value(sessionId)->closeTerminal(terminalId);
    }
}

void SessionStack::closeActiveTerminal(int sessionId)
{
    if (sessionId == -1) sessionId = m_activeSessionId;
    if (sessionId == -1) return;
    if (!m_sessions.contains(sessionId)) return;

    if (queryClose(sessionId, QueryCloseTerminal))
        m_sessions.value(sessionId)->closeTerminal();
}


void SessionStack::cleanup(int sessionId)
{
    if (sessionId == m_activeSessionId) m_activeSessionId = -1;

    m_sessions.remove(sessionId);

    emit sessionRemoved(sessionId);
}

int SessionStack::activeTerminalId()
{
    if (!m_sessions.contains(m_activeSessionId)) return -1;

    return m_sessions.value(m_activeSessionId)->activeTerminalId();
}

const QString SessionStack::sessionIdList()
{
    QList<int> keyList = m_sessions.uniqueKeys();
    QStringList idList;

    QListIterator<int> i(keyList);

    while (i.hasNext())
        idList << QString::number(i.next());

    return idList.join(QStringLiteral(","));
}

const QString SessionStack::terminalIdList()
{
    QStringList idList;

    QHashIterator<int, Session*> it(m_sessions);

    while (it.hasNext())
    {
        it.next();

        idList << it.value()->terminalIdList();
    }

    return idList.join(QStringLiteral(","));
}

const QString SessionStack::terminalIdsForSessionId(int sessionId)
{
    if (!m_sessions.contains(sessionId)) return QString::number(-1);

    return m_sessions.value(sessionId)->terminalIdList();
}

int SessionStack::sessionIdForTerminalId(int terminalId)
{
    int sessionId = -1;

    QHashIterator<int, Session*> it(m_sessions);

    while (it.hasNext())
    {
        it.next();

        if (it.value()->hasTerminal(terminalId))
        {
            sessionId = it.key();

            break;
        }
    }

    return sessionId;
}

void SessionStack::runCommand(const QString& command)
{
    if (m_activeSessionId == -1) return;
    if (!m_sessions.contains(m_activeSessionId)) return;

    m_sessions.value(m_activeSessionId)->runCommand(command);
}

void SessionStack::runCommandInTerminal(int terminalId, const QString& command)
{
    QHashIterator<int, Session*> it(m_sessions);

    while (it.hasNext())
    {
        it.next();

        it.value()->runCommand(command, terminalId);
    }
}

bool SessionStack::isSessionClosable(int sessionId)
{
    if (sessionId == -1) sessionId = m_activeSessionId;
    if (sessionId == -1) return false;
    if (!m_sessions.contains(sessionId)) return false;

    return m_sessions.value(sessionId)->closable();
}

void SessionStack::setSessionClosable(int sessionId, bool closable)
{
    if (sessionId == -1) sessionId = m_activeSessionId;
    if (sessionId == -1) return;
    if (!m_sessions.contains(sessionId)) return;

    m_sessions.value(sessionId)->setClosable(closable);
}

bool SessionStack::hasUnclosableSessions() const
{
    QHashIterator<int, Session*> it(m_sessions);

    while (it.hasNext())
    {
        it.next();

        if (!it.value()->closable())
            return true;
    }

    return false;
}

bool SessionStack::isSessionKeyboardInputEnabled(int sessionId)
{
    if (sessionId == -1) sessionId = m_activeSessionId;
    if (sessionId == -1) return false;
    if (!m_sessions.contains(sessionId)) return false;

    return m_sessions.value(sessionId)->keyboardInputEnabled();
}

void SessionStack::setSessionKeyboardInputEnabled(int sessionId, bool enabled)
{
    if (sessionId == -1) sessionId = m_activeSessionId;
    if (sessionId == -1) return;
    if (!m_sessions.contains(sessionId)) return;

    m_sessions.value(sessionId)->setKeyboardInputEnabled(enabled);

    if (sessionId == m_activeSessionId)
    {
        if (enabled)
            m_visualEventOverlay->hide();
        else
            m_visualEventOverlay->show();
    }
}

bool SessionStack::isTerminalKeyboardInputEnabled(int terminalId)
{
    int sessionId = sessionIdForTerminalId(terminalId);
    if (sessionId == -1) return false;
    if (!m_sessions.contains(sessionId)) return false;

    return m_sessions.value(sessionId)->keyboardInputEnabled(terminalId);
}

void SessionStack::setTerminalKeyboardInputEnabled(int terminalId, bool enabled)
{
    int sessionId = sessionIdForTerminalId(terminalId);
    if (sessionId == -1) return;
    if (!m_sessions.contains(sessionId)) return;

    m_sessions.value(sessionId)->setKeyboardInputEnabled(terminalId, enabled);

    if (sessionId == m_activeSessionId)
    {
        if (enabled)
            m_visualEventOverlay->hide();
        else
            m_visualEventOverlay->show();
    }
}

bool SessionStack::hasTerminalsWithKeyboardInputEnabled(int sessionId)
{
    if (sessionId == -1) sessionId = m_activeSessionId;
    if (sessionId == -1) return false;
    if (!m_sessions.contains(sessionId)) return false;

    return m_sessions.value(sessionId)->hasTerminalsWithKeyboardInputEnabled();
}

bool SessionStack::hasTerminalsWithKeyboardInputDisabled(int sessionId)
{
    if (sessionId == -1) sessionId = m_activeSessionId;
    if (sessionId == -1) return false;
    if (!m_sessions.contains(sessionId)) return false;

    return m_sessions.value(sessionId)->hasTerminalsWithKeyboardInputDisabled();
}

bool SessionStack::isSessionMonitorActivityEnabled(int sessionId)
{
    if (sessionId == -1) sessionId = m_activeSessionId;
    if (sessionId == -1) return false;
    if (!m_sessions.contains(sessionId)) return false;

    return m_sessions.value(sessionId)->monitorActivityEnabled();
}

void SessionStack::setSessionMonitorActivityEnabled(int sessionId, bool enabled)
{
    if (sessionId == -1) sessionId = m_activeSessionId;
    if (sessionId == -1) return;
    if (!m_sessions.contains(sessionId)) return;

    m_sessions.value(sessionId)->setMonitorActivityEnabled(enabled);
}

bool SessionStack::isTerminalMonitorActivityEnabled(int terminalId)
{
    int sessionId = sessionIdForTerminalId(terminalId);
    if (sessionId == -1) return false;
    if (!m_sessions.contains(sessionId)) return false;

    return m_sessions.value(sessionId)->monitorActivityEnabled(terminalId);
}

void SessionStack::setTerminalMonitorActivityEnabled(int terminalId, bool enabled)
{
    int sessionId = sessionIdForTerminalId(terminalId);
    if (sessionId == -1) return;
    if (!m_sessions.contains(sessionId)) return;

    m_sessions.value(sessionId)->setMonitorActivityEnabled(terminalId, enabled);
}

bool SessionStack::hasTerminalsWithMonitorActivityEnabled(int sessionId)
{
    if (sessionId == -1) sessionId = m_activeSessionId;
    if (sessionId == -1) return false;
    if (!m_sessions.contains(sessionId)) return false;

    return m_sessions.value(sessionId)->hasTerminalsWithMonitorActivityEnabled();
}

bool SessionStack::hasTerminalsWithMonitorActivityDisabled(int sessionId)
{
    if (sessionId == -1) sessionId = m_activeSessionId;
    if (sessionId == -1) return false;
    if (!m_sessions.contains(sessionId)) return false;

    return m_sessions.value(sessionId)->hasTerminalsWithMonitorActivityDisabled();
}

bool SessionStack::isSessionMonitorSilenceEnabled(int sessionId)
{
    if (sessionId == -1) sessionId = m_activeSessionId;
    if (sessionId == -1) return false;
    if (!m_sessions.contains(sessionId)) return false;

    return m_sessions.value(sessionId)->monitorSilenceEnabled();
}

void SessionStack::setSessionMonitorSilenceEnabled(int sessionId, bool enabled)
{
    if (sessionId == -1) sessionId = m_activeSessionId;
    if (sessionId == -1) return;
    if (!m_sessions.contains(sessionId)) return;

    m_sessions.value(sessionId)->setMonitorSilenceEnabled(enabled);
}

bool SessionStack::isTerminalMonitorSilenceEnabled(int terminalId)
{
    int sessionId = sessionIdForTerminalId(terminalId);
    if (sessionId == -1) return false;
    if (!m_sessions.contains(sessionId)) return false;

    return m_sessions.value(sessionId)->monitorSilenceEnabled(terminalId);
}

void SessionStack::setTerminalMonitorSilenceEnabled(int terminalId, bool enabled)
{
    int sessionId = sessionIdForTerminalId(terminalId);
    if (sessionId == -1) return;
    if (!m_sessions.contains(sessionId)) return;

    m_sessions.value(sessionId)->setMonitorSilenceEnabled(terminalId, enabled);
}

bool SessionStack::hasTerminalsWithMonitorSilenceEnabled(int sessionId)
{
    if (sessionId == -1) sessionId = m_activeSessionId;
    if (sessionId == -1) return false;
    if (!m_sessions.contains(sessionId)) return false;

    return m_sessions.value(sessionId)->hasTerminalsWithMonitorSilenceEnabled();
}

bool SessionStack::hasTerminalsWithMonitorSilenceDisabled(int sessionId)
{
    if (sessionId == -1) sessionId = m_activeSessionId;
    if (sessionId == -1) return false;
    if (!m_sessions.contains(sessionId)) return false;

    return m_sessions.value(sessionId)->hasTerminalsWithMonitorSilenceDisabled();
}

void SessionStack::editProfile(int sessionId)
{
    if (sessionId == -1) sessionId = m_activeSessionId;
    if (sessionId == -1) return;
    if (!m_sessions.contains(sessionId)) return;

    m_sessions.value(sessionId)->editProfile();
}

int SessionStack::splitSessionLeftRight(int sessionId)
{
    if (sessionId == -1) return -1;
    if (!m_sessions.contains(sessionId)) return -1;

    return m_sessions.value(sessionId)->splitLeftRight();
}

int SessionStack::splitSessionTopBottom(int sessionId)
{
    if (sessionId == -1) return -1;
    if (!m_sessions.contains(sessionId)) return -1;

    return m_sessions.value(sessionId)->splitTopBottom();
}

int SessionStack::splitTerminalLeftRight(int terminalId)
{
    int sessionId = sessionIdForTerminalId(terminalId);

    if (sessionId == -1) return -1;

    return m_sessions.value(sessionId)->splitLeftRight(terminalId);
}

int SessionStack::splitTerminalTopBottom(int terminalId)
{
    int sessionId = sessionIdForTerminalId(terminalId);

    if (sessionId == -1) return -1;

    return m_sessions.value(sessionId)->splitTopBottom(terminalId);
}

int SessionStack::tryGrowTerminalRight(int terminalId, uint pixels)
{
    int sessionId = sessionIdForTerminalId(terminalId);

    if (sessionId == -1) return -1;

    return m_sessions.value(sessionId)->tryGrowTerminal(terminalId, Session::Right, pixels);
}

int SessionStack::tryGrowTerminalLeft(int terminalId, uint pixels)
{
    int sessionId = sessionIdForTerminalId(terminalId);

    if (sessionId == -1) return -1;

    return m_sessions.value(sessionId)->tryGrowTerminal(terminalId, Session::Left, pixels);
}

int SessionStack::tryGrowTerminalTop(int terminalId, uint pixels)
{
    int sessionId = sessionIdForTerminalId(terminalId);

    if (sessionId == -1) return -1;

    return m_sessions.value(sessionId)->tryGrowTerminal(terminalId, Session::Up, pixels);
}

int SessionStack::tryGrowTerminalBottom(int terminalId, uint pixels)
{
    int sessionId = sessionIdForTerminalId(terminalId);

    if (sessionId == -1) return -1;

    return m_sessions.value(sessionId)->tryGrowTerminal(terminalId, Session::Down, pixels);
}

void SessionStack::emitTitles()
{
    QString title;

    QHashIterator<int, Session*> it(m_sessions);

    while (it.hasNext())
    {
        it.next();

        title = it.value()->title();

        if (!title.isEmpty())
            emit titleChanged(it.value()->id(), title);
    }
}

bool SessionStack::requiresVisualEventOverlay()
{
    if (m_activeSessionId == -1) return false;
    if (!m_sessions.contains(m_activeSessionId)) return false;

    return m_sessions.value(m_activeSessionId)->hasTerminalsWithKeyboardInputDisabled();
}

void SessionStack::handleTerminalHighlightRequest(int terminalId)
{
    Terminal* terminal = 0;

    QHashIterator<int, Session*> it(m_sessions);

    while (it.hasNext())
    {
        it.next();

        terminal = it.value()->getTerminal(terminalId);

        if (terminal && it.value()->id() == m_activeSessionId)
        {
            m_visualEventOverlay->highlightTerminal(terminal, true);

            break;
        }
    }
}

void SessionStack::showEvent(QShowEvent *event)
{
    Q_UNUSED(event)

    if (m_activeSessionId == -1) return;
    if (!m_sessions.contains(m_activeSessionId)) return;

    Terminal *terminal = m_sessions.value(m_activeSessionId)->getTerminal(activeTerminalId());

    if (terminal) {
        QWidget* terminalWidget = terminal->terminalWidget();
        if (terminalWidget) terminalWidget->setFocus();
    }
}

void SessionStack::handleManualTerminalActivation(Terminal* terminal)
{
    if (!Settings::terminalHighlightOnManualActivation())
        return;

    Session* session = qobject_cast<Session*>(QObject::sender());

    if (session->terminalCount() > 1)
        m_visualEventOverlay->highlightTerminal(terminal, false);
}

bool SessionStack::queryClose(int sessionId, QueryCloseType type)
{
    if (!m_sessions.contains(sessionId)) return false;

    if (!m_sessions.value(sessionId)->closable())
    {
        QString closeQuestionIntro = xi18nc("@info", "<warning>You have locked this session to prevent accidental closing of terminals.</warning>");
        QString closeQuestion;

        if (type == QueryCloseSession)
            closeQuestion = xi18nc("@info", "Are you sure you want to close this session?");
        else if (type == QueryCloseTerminal)
            closeQuestion = xi18nc("@info", "Are you sure you want to close this terminal?");

        int result = KMessageBox::warningContinueCancel(this,
            closeQuestionIntro + QStringLiteral("<br/><br/>") + closeQuestion,
            xi18nc("@title:window", "Really Close?"), KStandardGuiItem::close(), KStandardGuiItem::cancel());

        if (result != KMessageBox::Continue)
            return false;
    }

    return true;
}
