//
// configview.cpp
//
// Description: View for configuring the set of targets to be used with the debugger
//
//
// Copyright (c) 2010 Ian Wakeling <ian.wakeling@ntlworld.com>
// Copyright (c) 2012 Kåre Särs <kare.sars@iki.fi>
//
//  This library is free software; you can redistribute it and/or
//  modify it under the terms of the GNU Library General Public
//  License version 2 as published by the Free Software Foundation.
//
//  This library 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
//  Library General Public License for more details.
//
//  You should have received a copy of the GNU Library General Public License
//  along with this library; see the file COPYING.LIB.  If not, write to
//  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
//  Boston, MA 02110-1301, USA.

#include "configview.h"
#include "configview.moc"

#include <QtGui/QCompleter>
#include <QtGui/QDirModel>
#include <QtGui/QLayout>
#include <QtCore/QTimer>

#include <klocale.h>
#include <kicon.h>
#include <ktexteditor/view.h>
#include <ktexteditor/document.h>
#include <kfiledialog.h>
#include <kmessagebox.h>

ConfigView::ConfigView(QWidget* parent, Kate::MainWindow* mainWin)
:   QWidget(parent),
    m_mainWindow(mainWin)
{
    m_targetCombo = new KComboBox();
    m_targetCombo->setEditable(true);
    // don't let Qt insert items when the user edits; new targets are only
    // added when the user explicitly says so
    m_targetCombo->setInsertPolicy(QComboBox::NoInsert);
    m_targetCombo->setDuplicatesEnabled(true);

    m_addTarget = new QToolButton();
    m_addTarget->setIcon(SmallIcon("document-new"));
    m_addTarget->setToolTip(i18n("Add new target"));

    m_copyTarget = new QToolButton();
    m_copyTarget->setIcon(SmallIcon("document-copy"));
    m_copyTarget->setToolTip(i18n("Copy target"));

    m_deleteTarget = new QToolButton();
    m_deleteTarget->setIcon(SmallIcon("edit-delete"));
    m_deleteTarget->setToolTip(i18n("Delete target"));

    m_line = new QFrame(this);
    m_line->setFrameShadow(QFrame::Sunken);

    m_execLabel = new QLabel(i18n("Executable:"));
    m_execLabel->setBuddy(m_targetCombo);

    m_executable = new KLineEdit();
    QCompleter* completer1 = new QCompleter(this);
    completer1->setModel(new QDirModel(QStringList(),
                                         QDir::AllDirs|QDir::NoDotAndDotDot,
                                         QDir::Name, this));
    m_executable->setCompleter(completer1);
    m_executable->setClearButtonShown(true);
    m_browseExe = new QToolButton(this);
    m_browseExe->setIcon(KIcon("application-x-executable"));

    m_workingDirectory = new KLineEdit();
    QCompleter* completer2 = new QCompleter(this);
    completer2->setModel(new QDirModel(completer2));
    m_workingDirectory->setCompleter(completer2);
    m_workingDirectory->setClearButtonShown(true);
    m_workDirLabel = new QLabel(i18n("Working Directory:"));
    m_workDirLabel->setBuddy(m_workingDirectory);
    m_browseDir = new QToolButton(this);
    m_browseDir->setIcon(KIcon("inode-directory"));

    m_arguments = new KLineEdit();
    m_arguments->setClearButtonShown(true);
    m_argumentsLabel = new QLabel(i18nc("Program argument list", "Arguments:"));
    m_argumentsLabel->setBuddy(m_arguments);

    m_takeFocus = new QCheckBox(i18nc("Checkbox to for keeping focus on the command line",
                                        "Keep focus"));
    m_takeFocus->setToolTip(i18n("Keep the focus on the command line"));

    m_redirectTerminal = new QCheckBox(i18n("Redirect IO"));
    m_redirectTerminal->setToolTip(i18n("Redirect the debugged programs IO to a separate tab"));

    m_advancedSettings = new KPushButton(i18n("Advanced Settings"));

    m_checBoxLayout = 0;

    // first false then true to make sure a layout is set
    m_useBottomLayout = false;
    resizeEvent(0);
    m_useBottomLayout = true;
    resizeEvent(0);

    // calculate the approximate height to exceed before going to "Side Layout"
    m_widgetHeights = (m_execLabel->sizeHint().height() + /*layout spacing */6) * 12;

    m_advanced = new AdvancedGDBSettings(this);
    m_advanced->hide();

    connect(m_targetCombo,  SIGNAL(editTextChanged(QString)), this, SLOT(slotTargetEdited(QString)));
    connect(m_targetCombo,  SIGNAL(currentIndexChanged(int)), this, SLOT(slotTargetSelected(int)));
    connect(m_addTarget,    SIGNAL(clicked()),                this, SLOT(slotAddTarget()));
    connect(m_copyTarget,   SIGNAL(clicked()),                this, SLOT(slotCopyTarget()));
    connect(m_deleteTarget, SIGNAL(clicked()),                this, SLOT(slotDeleteTarget()));
    connect(m_browseExe,    SIGNAL(clicked()),                this, SLOT(slotBrowseExec()));
    connect(m_browseDir,    SIGNAL(clicked()),                this, SLOT(slotBrowseDir()));
    connect(m_redirectTerminal, SIGNAL(toggled(bool)),        this, SIGNAL(showIO(bool)));
    connect(m_advancedSettings, SIGNAL(clicked()),            this, SLOT(slotAdvancedClicked()));
}

ConfigView::~ConfigView()
{
}

void ConfigView::registerActions(KActionCollection* actionCollection)
{
    m_targetSelectAction = actionCollection->add<KSelectAction>("targets");
    m_targetSelectAction->setText(i18n("Targets"));
    connect(m_targetSelectAction, SIGNAL(triggered(int)),
            this, SLOT(slotTargetSelected(int)));
}

void ConfigView::readConfig(KConfigBase* config, QString const& groupPrefix)
{
    m_targetCombo->clear();

    KConfigGroup group = config->group(groupPrefix);
    int          version = group.readEntry("version", 4);
    int          targetCount = group.readEntry("targetCount", 1);
    int          lastTarget = group.readEntry("lastTarget", 0);
    QString      targetKey("target_%1");

    QStringList  targetConfStrs;

    for (int i = 0; i < targetCount; i++) {
        targetConfStrs = group.readEntry(targetKey.arg(i), QStringList());
        if (targetConfStrs.count() == 0) continue;

        if ((version == 1) && (targetConfStrs.count() == 3)) {
            // valid old style config, translate it now; note the
            // reordering happening here!
            QStringList temp;
            temp << targetConfStrs[2];
            temp << targetConfStrs[1];
            targetConfStrs = temp;
        }

        if (version < 4) {
            targetConfStrs.prepend(targetConfStrs[0].right(15));
        }

        if (targetConfStrs.count() > NameIndex) {
            m_targetCombo->addItem(targetConfStrs[NameIndex], targetConfStrs);
        }
    }

    if (version < 4) {
        // all targets now have only one argument string
        int     argListsCount = group.readEntry("argsCount", 0);
        QString argsKey("args_%1");
        QString targetName("%1<%2>");

        QString argStr;
        int count = m_targetCombo->count();

        for (int i = 0; i < argListsCount; i++) {
            argStr = group.readEntry(argsKey.arg(i), QString());
            for (int j=0; j<count; j++) {
                targetConfStrs = m_targetCombo->itemData(j).toStringList();
                if (i>0) {
                    // copy the firsts and change the arguments
                    targetConfStrs[0] = targetName.arg(targetConfStrs[0]).arg(i+1);
                    if (targetConfStrs.count() > 3) targetConfStrs[3] = argStr;
                    m_targetCombo->addItem(targetConfStrs[0], targetConfStrs);
                }
            }
        }
    }
    // make sure there is at least one item.
    if (m_targetCombo->count() == 0) {
        slotAddTarget();
    }

    QStringList targetNames;
    for (int i=0; i<m_targetCombo->count(); i++) {
        targetNames << m_targetCombo->itemText(i);
    }
    m_targetSelectAction->setItems(targetNames);

    m_targetCombo->setCurrentIndex((lastTarget < m_targetCombo->count()) ? lastTarget: 0);

    m_takeFocus->setChecked(group.readEntry("alwaysFocusOnInput",false));

    m_redirectTerminal->setChecked(group.readEntry("redirectTerminal",false));
}

void ConfigView::writeConfig(KConfigBase* config, QString const& groupPrefix)
{
    // make sure the data is up to date before writing
    saveCurrentToIndex(m_currentTarget);

    KConfigGroup    group = config->group(groupPrefix);

    group.writeEntry("version", 4);

    QString targetKey("target_%1");
    QStringList targetConfStrs;

    group.writeEntry("targetCount", m_targetCombo->count());
    group.writeEntry("lastTarget", m_targetCombo->currentIndex());
    for (int i = 0; i < m_targetCombo->count(); i++) {
        targetConfStrs = m_targetCombo->itemData(i).toStringList();
        group.writeEntry(targetKey.arg(i), targetConfStrs);
    }

    group.writeEntry("alwaysFocusOnInput", m_takeFocus->isChecked());
    group.writeEntry("redirectTerminal", m_redirectTerminal->isChecked());
}

const GDBTargetConf ConfigView::currentTarget() const
{
    GDBTargetConf cfg;
    cfg.executable = m_executable->text();
    cfg.workDir = m_workingDirectory->text();
    cfg.arguments = m_arguments->text();
    cfg.customInit = m_advanced->configs();
    // Note: AdvancedGDBSettings::GDBIndex == 0
    if ((cfg.customInit.size() >= 0) && !cfg.customInit[0].isEmpty()) {
        cfg.gdbCmd = cfg.customInit[0];
        cfg.customInit.removeFirst();
    }
    else {
        cfg.gdbCmd = "gdb";
    }
    // remove empty strings in the customInit
    int i = cfg.customInit.size()-1;
    while (i>=0) {
        if (cfg.customInit[i].isEmpty()) {
            cfg.customInit.removeAt(i);
        }
        i--;
    }
    return cfg;
}

bool ConfigView::takeFocusAlways() const
{
    return m_takeFocus->isChecked();
}

bool ConfigView::showIOTab() const
{
    return m_redirectTerminal->isChecked();
}

void ConfigView::slotTargetEdited(const QString &newText)
{
    int cursorPosition = m_targetCombo->lineEdit()->cursorPosition();
    m_targetCombo->setItemText(m_targetCombo->currentIndex(), newText);
    m_targetCombo->lineEdit()->setCursorPosition(cursorPosition);

    // rebuild the target menu
    QStringList targets;
    for (int i = 0; i < m_targetCombo->count(); ++i) {
        targets.append(m_targetCombo->itemText(i));
    }
    m_targetSelectAction->setItems(targets);
    m_targetSelectAction->setCurrentItem(m_targetCombo->currentIndex());
}

void ConfigView::slotTargetSelected(int index)
{
    if ((index < 0) || (index >= m_targetCombo->count())) {
        return;
    }

    if ((m_currentTarget > 0) && (m_currentTarget < m_targetCombo->count())) {
        saveCurrentToIndex(m_currentTarget);
    }

    loadFromIndex(index);
    m_currentTarget = index;

    // Keep combo box and menu in sync
    m_targetSelectAction->setCurrentItem(index);
}

void ConfigView::slotAddTarget()
{
    QStringList targetConfStrs;

    targetConfStrs << i18n("Target %1", m_targetCombo->count()+1);
    targetConfStrs << QString();
    targetConfStrs << QString();
    targetConfStrs << QString();

    m_targetCombo->addItem(targetConfStrs[NameIndex], targetConfStrs);
    m_targetCombo->setCurrentIndex(m_targetCombo->count()-1);
}

void ConfigView::slotCopyTarget()
{
    QStringList tmp = m_targetCombo->itemData(m_targetCombo->currentIndex()).toStringList();
    tmp[NameIndex] = i18n("Target %1", m_targetCombo->count()+1);

    m_targetCombo->addItem(tmp[NameIndex], tmp);
    m_targetCombo->setCurrentIndex(m_targetCombo->count()-1);
}

void ConfigView::slotDeleteTarget()
{
    m_targetCombo->blockSignals(true);
    int currentIndex = m_targetCombo->currentIndex();
    m_targetCombo->removeItem(currentIndex);
    if (m_targetCombo->count() == 0) {
        slotAddTarget();
    }

    loadFromIndex(m_targetCombo->currentIndex());
    m_targetCombo->blockSignals(false);
}

void ConfigView::resizeEvent(QResizeEvent *)
{
    if (m_useBottomLayout && (size().height() > m_widgetHeights)) {
        // Set layout for the side
        delete m_checBoxLayout;
        m_checBoxLayout = 0;
        delete layout();
        QGridLayout* layout = new QGridLayout(this);

        layout->addWidget(m_targetCombo, 0, 0);
        layout->addWidget(m_addTarget, 0, 1);
        layout->addWidget(m_copyTarget, 0, 2);
        layout->addWidget(m_deleteTarget, 0, 3);
        m_line->setFrameShape(QFrame::HLine);
        layout->addWidget(m_line, 1, 0, 1, 4);

        layout->addWidget(m_execLabel, 3, 0, Qt::AlignLeft);
        layout->addWidget(m_executable, 4, 0, 1, 3);
        layout->addWidget(m_browseExe, 4, 3);

        layout->addWidget(m_workDirLabel, 5, 0, Qt::AlignLeft);
        layout->addWidget(m_workingDirectory, 6, 0, 1, 3);
        layout->addWidget(m_browseDir, 6, 3);

        layout->addWidget(m_argumentsLabel, 7, 0, Qt::AlignLeft);
        layout->addWidget(m_arguments, 8, 0, 1, 4);

        layout->addWidget(m_takeFocus, 9, 0, 1, 4);
        layout->addWidget(m_redirectTerminal, 10, 0, 1, 4);
        layout->addWidget(m_advancedSettings, 11, 0, 1 , 4);

        layout->addItem(new QSpacerItem(1, 1), 12, 0);
        layout->setColumnStretch(0, 1);
        layout->setRowStretch(12, 1);
        m_useBottomLayout = false;
    }
    else if (!m_useBottomLayout && (size().height() < m_widgetHeights)) {
        // Set layout for the bottom
        delete m_checBoxLayout;
        delete layout();
        m_checBoxLayout = new QHBoxLayout();
        m_checBoxLayout->addWidget(m_takeFocus, 10);
        m_checBoxLayout->addWidget(m_redirectTerminal, 10);
        m_checBoxLayout->addWidget(m_advancedSettings, 0);

        QGridLayout* layout = new QGridLayout(this);

        layout->addWidget(m_targetCombo, 0, 0, 1, 3);
        layout->addWidget(m_addTarget, 1, 0);
        layout->addWidget(m_copyTarget, 1, 1);
        layout->addWidget(m_deleteTarget, 1, 2);
        m_line->setFrameShape(QFrame::VLine);
        layout->addWidget(m_line, 0, 3, 4, 1);

        layout->addWidget(m_execLabel, 0, 5, Qt::AlignRight);
        layout->addWidget(m_executable, 0, 6);
        layout->addWidget(m_browseExe, 0, 7);

        layout->addWidget(m_workDirLabel, 1, 5, Qt::AlignRight);
        layout->addWidget(m_workingDirectory, 1, 6);
        layout->addWidget(m_browseDir, 1, 7);

        layout->addWidget(m_argumentsLabel, 2, 5, Qt::AlignRight);
        layout->addWidget(m_arguments, 2, 6, 1, 2);

        layout->addLayout(m_checBoxLayout, 3, 5, 1, 3);

        layout->addItem(new QSpacerItem(1, 1), 4, 0);
        layout->setColumnStretch(6, 100);
        layout->setRowStretch(4, 100);
        m_useBottomLayout = true;
    }
}

void ConfigView::slotAdvancedClicked()
{
    QStringList tmp = m_targetCombo->itemData(m_targetCombo->currentIndex()).toStringList();
    QStringList newList;

    // make sure we have enough strings;
    while (tmp.count() < CustomStartIndex) tmp << QString();

    if (tmp[GDBIndex].isEmpty()) {
        tmp[GDBIndex] = "gdb";
    }

    // Remove the strings that are not part of the advanced settings
    for(int i=0; i<GDBIndex; i++) {
        newList << tmp.takeFirst();
    }

    m_advanced->setConfigs(tmp);
    if (m_advanced->exec()) {
        // save the new values
        newList << m_advanced->configs();
        m_targetCombo->setItemData(m_targetCombo->currentIndex(), newList);
    }
}

void ConfigView::slotBrowseExec()
{
    KUrl exe(m_executable->text());

    if (m_executable->text().isEmpty()) {
        // try current document dir
        KTextEditor::View*  view = m_mainWindow->activeView();

        if (view != NULL) {
            exe = view->document()->url();
        }
    }
    m_executable->setText(KFileDialog::getOpenFileName(exe, "application/x-executable"));
}

void ConfigView::slotBrowseDir()
{
    KUrl dir(m_workingDirectory->text());

    if (m_workingDirectory->text().isEmpty()) {
        // try current document dir
        KTextEditor::View*  view = m_mainWindow->activeView();

        if (view != NULL) {
            dir = view->document()->url();
        }
    }
    m_workingDirectory->setText(KFileDialog::getExistingDirectory (dir));
}

void ConfigView::saveCurrentToIndex(int index)
{
    if ((index < 0) || (index >= m_targetCombo->count())) {
        return;
    }
    
    QStringList tmp = m_targetCombo->itemData(index).toStringList();
    // make sure we have enough strings. The custom init strings are set in slotAdvancedClicked().
    while (tmp.count() < CustomStartIndex) tmp << QString();
    
    tmp[NameIndex] = m_targetCombo->itemText(index);
    tmp[ExecIndex] = m_executable->text();
    tmp[WorkDirIndex] = m_workingDirectory->text();
    tmp[ArgsIndex] = m_arguments->text();
    
    m_targetCombo->setItemData(index, tmp);
}

void ConfigView::loadFromIndex(int index)
{
    if ((index < 0) || (index >= m_targetCombo->count())) {
        return;
    }

    QStringList tmp = m_targetCombo->itemData(index).toStringList();
    // make sure we have enough strings. The custom init strings are set in slotAdvancedClicked().
    while (tmp.count() < CustomStartIndex) tmp << QString();

    m_executable->setText(tmp[ExecIndex]);
    m_workingDirectory->setText(tmp[WorkDirIndex]);
    m_arguments->setText(tmp[ArgsIndex]);
}


