/*
  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 "session.h"
#include "terminal.h"


int Session::m_availableSessionId = 0;

Session::Session(SessionType type, QWidget* parent) : QObject(parent)
{
    m_sessionId = m_availableSessionId;
    m_availableSessionId++;

    m_activeTerminalId = -1;

    m_closable = true;

    m_baseSplitter = new Splitter(Qt::Horizontal, parent);
    connect(m_baseSplitter, SIGNAL(destroyed()), this, SLOT(prepareShutdown()));

    setupSession(type);
}

Session::~Session()
{
    if (m_baseSplitter) delete m_baseSplitter;

    emit destroyed(m_sessionId);
}

void Session::setupSession(SessionType type)
{
    switch (type)
    {
        case Single:
        {
            Terminal* terminal = addTerminal(m_baseSplitter);
            setActiveTerminal(terminal->id());

            break;
        }

        case TwoHorizontal:
        {
            int splitterWidth = m_baseSplitter->width();

            Terminal* terminal = addTerminal(m_baseSplitter);
            addTerminal(m_baseSplitter);

            QList<int> newSplitterSizes;
            newSplitterSizes << (splitterWidth / 2) << (splitterWidth / 2);
            m_baseSplitter->setSizes(newSplitterSizes);

            QWidget* terminalWidget = terminal->terminalWidget();

            if (terminalWidget)
            {
                terminalWidget->setFocus();
                setActiveTerminal(terminal->id());
            }

            break;
        }

        case TwoVertical:
        {
            m_baseSplitter->setOrientation(Qt::Vertical);

            int splitterHeight = m_baseSplitter->height();

            Terminal* terminal = addTerminal(m_baseSplitter);
            addTerminal(m_baseSplitter);

            QList<int> newSplitterSizes;
            newSplitterSizes << (splitterHeight / 2) << (splitterHeight / 2);
            m_baseSplitter->setSizes(newSplitterSizes);

            QWidget* terminalWidget = terminal->terminalWidget();

            if (terminalWidget)
            {
                terminalWidget->setFocus();
                setActiveTerminal(terminal->id());
            }

            break;
        }

        case Quad:
        {
            int splitterWidth = m_baseSplitter->width();
            int splitterHeight = m_baseSplitter->height();

            m_baseSplitter->setOrientation(Qt::Vertical);

            Splitter* upperSplitter = new Splitter(Qt::Horizontal, m_baseSplitter);
            connect(upperSplitter, SIGNAL(destroyed()), this, SLOT(cleanup()));

            Splitter* lowerSplitter = new Splitter(Qt::Horizontal, m_baseSplitter);
            connect(lowerSplitter, SIGNAL(destroyed()), this, SLOT(cleanup()));

            Terminal* terminal = addTerminal(upperSplitter);
            addTerminal(upperSplitter);

            addTerminal(lowerSplitter);
            addTerminal(lowerSplitter);

            QList<int> newSplitterSizes;
            newSplitterSizes << (splitterHeight / 2) << (splitterHeight / 2);
            m_baseSplitter->setSizes(newSplitterSizes);

            newSplitterSizes.clear();
            newSplitterSizes << (splitterWidth / 2) << (splitterWidth / 2);
            upperSplitter->setSizes(newSplitterSizes);
            lowerSplitter->setSizes(newSplitterSizes);

            QWidget* terminalWidget = terminal->terminalWidget();

            if (terminalWidget)
            {
                terminalWidget->setFocus();
                setActiveTerminal(terminal->id());
            }

            break;
        }

        default:
        {
            addTerminal(m_baseSplitter);

            break;
        }
    }
}

Terminal* Session::addTerminal(QWidget* parent)
{
    Terminal* terminal = new Terminal(parent);
    connect(terminal, SIGNAL(activated(int)), this, SLOT(setActiveTerminal(int)));
    connect(terminal, SIGNAL(manuallyActivated(Terminal*)), this, SIGNAL(terminalManuallyActivated(Terminal*)));
    connect(terminal, SIGNAL(titleChanged(int,QString)), this, SLOT(setTitle(int,QString)));
    connect(terminal, SIGNAL(keyboardInputBlocked(Terminal*)), this, SIGNAL(keyboardInputBlocked(Terminal*)));
    connect(terminal, SIGNAL(silenceDetected(Terminal*)), this, SIGNAL(silenceDetected(Terminal*)));
    connect(terminal, SIGNAL(destroyed(int)), this, SLOT(cleanup(int)));

    m_terminals.insert(terminal->id(), terminal);

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

    return terminal;
}

void Session::closeTerminal(int terminalId)
{
    if (terminalId == -1) terminalId = m_activeTerminalId;
    if (terminalId == -1) return;
    if (!m_terminals.contains(terminalId)) return;

    m_terminals.value(terminalId)->deletePart();
}

void Session::focusPreviousTerminal()
{
    if (m_activeTerminalId == -1) return;
    if (!m_terminals.contains(m_activeTerminalId)) return;

    QMapIterator<int, Terminal*> it(m_terminals);

    it.toBack();

    while (it.hasPrevious())
    {
        it.previous();

        if (it.key() == m_activeTerminalId)
        {
            if (it.hasPrevious())
            {
                QWidget* terminalWidget = it.peekPrevious().value()->terminalWidget();
                if (terminalWidget) terminalWidget->setFocus();
            }
            else
            {
                it.toBack();

                QWidget* terminalWidget = it.peekPrevious().value()->terminalWidget();
                if (terminalWidget) terminalWidget->setFocus();
            }

            break;
        }
    }
}

void Session::focusNextTerminal()
{
    if (m_activeTerminalId == -1) return;
    if (!m_terminals.contains(m_activeTerminalId)) return;

    QMapIterator<int, Terminal*> it(m_terminals);

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

        if (it.key() == m_activeTerminalId)
        {
            if (it.hasNext())
            {
                QWidget* terminalWidget = it.peekNext().value()->terminalWidget();
                if (terminalWidget) terminalWidget->setFocus();
            }
            else
            {
                it.toFront();

                QWidget* terminalWidget = it.peekNext().value()->terminalWidget();
                if (terminalWidget) terminalWidget->setFocus();
            }

            break;
        }
    }
}

int Session::splitLeftRight(int terminalId)
{
    if (terminalId == -1) terminalId = m_activeTerminalId;
    if (terminalId == -1) return -1;
    if (!m_terminals.contains(terminalId)) return -1;

    Terminal* terminal = m_terminals.value(terminalId);

    if (terminal)
        return split(terminal, Qt::Horizontal);
    else
        return -1;
}

int Session::splitTopBottom(int terminalId)
{
    if (terminalId == -1) terminalId = m_activeTerminalId;
    if (terminalId == -1) return -1;
    if (!m_terminals.contains(terminalId)) return -1;

    Terminal* terminal = m_terminals.value(terminalId);

    if (terminal)
        return split(terminal, Qt::Vertical);
    else
        return -1;
}

int Session::split(Terminal* terminal, Qt::Orientation orientation)
{
    Splitter* splitter = static_cast<Splitter*>(terminal->splitter());

    if (splitter->count() == 1)
    {
        int splitterWidth = splitter->width();

        if (splitter->orientation() != orientation)
            splitter->setOrientation(orientation);

        terminal = addTerminal(splitter);

        QList<int> newSplitterSizes;
        newSplitterSizes << (splitterWidth / 2) << (splitterWidth / 2);
        splitter->setSizes(newSplitterSizes);

        QWidget* partWidget = terminal->partWidget();
        if (partWidget) partWidget->show();

        m_activeTerminalId = terminal->id();
    }
    else
    {
        QList<int> splitterSizes = splitter->sizes();

        Splitter* newSplitter = new Splitter(orientation, splitter);
        connect(newSplitter, SIGNAL(destroyed()), this, SLOT(cleanup()));

        if (splitter->indexOf(terminal->partWidget()) == 0)
            splitter->insertWidget(0, newSplitter);

        QWidget* partWidget = terminal->partWidget();
        if (partWidget) partWidget->setParent(newSplitter);

        terminal->setSplitter(newSplitter);

        terminal = addTerminal(newSplitter);

        splitter->setSizes(splitterSizes);
        QList<int> newSplitterSizes;
        newSplitterSizes << (splitterSizes[1] / 2) << (splitterSizes[1] / 2);
        newSplitter->setSizes(newSplitterSizes);

        newSplitter->show();

        partWidget = terminal->partWidget();
        if (partWidget) partWidget->show();

        m_activeTerminalId = terminal->id();
    }

    return m_activeTerminalId;
}

int Session::tryGrowTerminal(int terminalId, GrowthDirection direction, uint pixels)
{
    Terminal* terminal = getTerminal(terminalId);
    Splitter* splitter = static_cast<Splitter*>(terminal->splitter());
    QWidget* child = terminal->partWidget();

    while (splitter)
    {
        bool isHorizontal = (direction == Right || direction == Left);
        bool isForward = (direction == Down || direction == Right);

        // Detecting correct orientation.
        if ((splitter->orientation() == Qt::Horizontal && isHorizontal)
            || (splitter->orientation() == Qt::Vertical && !isHorizontal))
        {

            int currentPos = splitter->indexOf(child);

            if (currentPos != -1 // Next/Prev movable element detection.
                && (currentPos != 0 || isForward)
                && (currentPos != splitter->count() - 1 || !isForward))
            {
                QList<int> currentSizes = splitter->sizes();
                int oldSize = currentSizes[currentPos];

                int affected = isForward ? currentPos + 1: currentPos -1;
                currentSizes[currentPos] += pixels;
                currentSizes[affected] -= pixels;
                splitter->setSizes(currentSizes);

                return splitter->sizes()[currentPos] - oldSize;
            }
        }
        // Try with a higher level.
        child = splitter;
        splitter = static_cast<Splitter*>(splitter->parentWidget());
    }

    return -1;
}

void Session::setActiveTerminal(int terminalId)
{
    m_activeTerminalId = terminalId;

    setTitle(m_activeTerminalId, m_terminals.value(m_activeTerminalId)->title());
}

void Session::setTitle(int terminalId, const QString& title)
{
    if (terminalId == m_activeTerminalId)
    {
        m_title = title;

        emit titleChanged(m_title);
        emit titleChanged(m_sessionId, m_title);
    }
}

void Session::cleanup(int terminalId)
{
    if (m_activeTerminalId == terminalId && m_terminals.count() > 1)
        focusPreviousTerminal();

    m_terminals.remove(terminalId);

    cleanup();
}

void Session::cleanup()
{
    if (!m_baseSplitter) return;

    m_baseSplitter->recursiveCleanup();

    if (m_terminals.count() == 0)
        m_baseSplitter->deleteLater();
}

void Session::prepareShutdown()
{
    m_baseSplitter = NULL;

    deleteLater();
}

const QString Session::terminalIdList()
{
    QList<int> keyList = m_terminals.uniqueKeys();
    QStringList idList;

    QListIterator<int> i(keyList);

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

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

bool Session::hasTerminal(int terminalId)
{
    return m_terminals.contains(terminalId);
}

Terminal* Session::getTerminal(int terminalId)
{
    if (!m_terminals.contains(terminalId)) return 0;

    return m_terminals.value(terminalId);
}

void Session::runCommand(const QString& command, int terminalId)
{
    if (terminalId == -1) terminalId = m_activeTerminalId;
    if (terminalId == -1) return;
    if (!m_terminals.contains(terminalId)) return;

    m_terminals.value(terminalId)->runCommand(command);
}

void Session::manageProfiles()
{
    if ( m_activeTerminalId == -1) return;
    if (!m_terminals.contains(m_activeTerminalId)) return;

    m_terminals.value(m_activeTerminalId)->manageProfiles();
}

void Session::editProfile()
{
    if ( m_activeTerminalId == -1) return;
    if (!m_terminals.contains(m_activeTerminalId)) return;

    m_terminals.value(m_activeTerminalId)->editProfile();
}

bool Session::keyboardInputEnabled()
{
    int keyboardInputEnabledCount = 0;

    QMapIterator<int, Terminal*> i(m_terminals);

    while (i.hasNext())
        if (i.next().value()->keyboardInputEnabled())
            ++keyboardInputEnabledCount;

    return m_terminals.count() == keyboardInputEnabledCount;
}

void Session::setKeyboardInputEnabled(bool enabled)
{
    QMapIterator<int, Terminal*> i(m_terminals);

    while (i.hasNext())
        setKeyboardInputEnabled(i.next().key(), enabled);
}

bool Session::keyboardInputEnabled(int terminalId)
{
    if (!m_terminals.contains(terminalId)) return false;

    return m_terminals.value(terminalId)->keyboardInputEnabled();
}

void Session::setKeyboardInputEnabled(int terminalId, bool enabled)
{
    if (!m_terminals.contains(terminalId)) return;

    m_terminals.value(terminalId)->setKeyboardInputEnabled(enabled);
}

bool Session::hasTerminalsWithKeyboardInputEnabled()
{
    QMapIterator<int, Terminal*> i(m_terminals);

    while (i.hasNext())
        if (i.next().value()->keyboardInputEnabled())
            return true;

    return false;
}

bool Session::hasTerminalsWithKeyboardInputDisabled()
{
    QMapIterator<int, Terminal*> i(m_terminals);

    while (i.hasNext())
        if (!i.next().value()->keyboardInputEnabled())
            return true;

    return false;
}

bool Session::monitorActivityEnabled()
{
    int monitorActivityEnabledCount = 0;

    QMapIterator<int, Terminal*> i(m_terminals);

    while (i.hasNext())
        if (i.next().value()->monitorActivityEnabled())
            ++monitorActivityEnabledCount;

    return m_terminals.count() == monitorActivityEnabledCount;
}

void Session::setMonitorActivityEnabled(bool enabled)
{
    QMapIterator<int, Terminal*> i(m_terminals);

    while (i.hasNext())
        setMonitorActivityEnabled(i.next().key(), enabled);
}

bool Session::monitorActivityEnabled(int terminalId)
{
    if (!m_terminals.contains(terminalId)) return false;

    return m_terminals.value(terminalId)->monitorActivityEnabled();
}

void Session::setMonitorActivityEnabled(int terminalId, bool enabled)
{
    if (!m_terminals.contains(terminalId)) return;

    Terminal* terminal = m_terminals.value(terminalId);

    connect(terminal, SIGNAL(activityDetected(Terminal*)), this, SIGNAL(activityDetected(Terminal*)),
        Qt::UniqueConnection);

    terminal->setMonitorActivityEnabled(enabled);
}

bool Session::hasTerminalsWithMonitorActivityEnabled()
{
    QMapIterator<int, Terminal*> i(m_terminals);

    while (i.hasNext())
        if (i.next().value()->monitorActivityEnabled())
            return true;

    return false;
}

bool Session::hasTerminalsWithMonitorActivityDisabled()
{
    QMapIterator<int, Terminal*> i(m_terminals);

    while (i.hasNext())
        if (!i.next().value()->monitorActivityEnabled())
            return true;

    return false;
}

void Session::reconnectMonitorActivitySignals()
{
    QMapIterator<int, Terminal*> i(m_terminals);

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

        connect(i.value(), SIGNAL(activityDetected(Terminal*)), this, SIGNAL(activityDetected(Terminal*)),
            Qt::UniqueConnection);
    }
}

bool Session::monitorSilenceEnabled()
{
    int monitorSilenceEnabledCount = 0;

    QMapIterator<int, Terminal*> i(m_terminals);

    while (i.hasNext())
        if (i.next().value()->monitorSilenceEnabled())
            ++monitorSilenceEnabledCount;

    return m_terminals.count() == monitorSilenceEnabledCount;
}

void Session::setMonitorSilenceEnabled(bool enabled)
{
    QMapIterator<int, Terminal*> i(m_terminals);

    while (i.hasNext())
        setMonitorSilenceEnabled(i.next().key(), enabled);
}

bool Session::monitorSilenceEnabled(int terminalId)
{
    if (!m_terminals.contains(terminalId)) return false;

    return m_terminals.value(terminalId)->monitorSilenceEnabled();
}

void Session::setMonitorSilenceEnabled(int terminalId, bool enabled)
{
    if (!m_terminals.contains(terminalId)) return;

    m_terminals.value(terminalId)->setMonitorSilenceEnabled(enabled);
}

bool Session::hasTerminalsWithMonitorSilenceDisabled()
{
    QMapIterator<int, Terminal*> i(m_terminals);

    while (i.hasNext())
        if (!i.next().value()->monitorSilenceEnabled())
            return true;

    return false;
}

bool Session::hasTerminalsWithMonitorSilenceEnabled()
{
    QMapIterator<int, Terminal*> i(m_terminals);

    while (i.hasNext())
        if (i.next().value()->monitorSilenceEnabled())
            return true;

    return false;
}
