/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: Nokia Corporation (info@qt.nokia.com)
**
**
** GNU Lesser General Public License Usage
**
** This file may be used under the terms of the GNU Lesser General Public
** License version 2.1 as published by the Free Software Foundation and
** appearing in the file LICENSE.LGPL included in the packaging of this file.
** Please review the following information to ensure the GNU Lesser General
** Public License version 2.1 requirements will be met:
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights. These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** Other Usage
**
** Alternatively, this file may be used in accordance with the terms and
** conditions contained in a signed written agreement between you and Nokia.
**
** If you have questions regarding the use of this file, please contact
** Nokia at qt-info@nokia.com.
**
**************************************************************************/

#include "branchdialog.h"
#include "branchmodel.h"
#include "gitclient.h"
#include "gitplugin.h"
#include "ui_branchdialog.h"
#include "stashdialog.h" // Label helpers

#include <vcsbase/vcsbaseoutputwindow.h>

#include <QtGui/QItemSelectionModel>
#include <QtGui/QPushButton>
#include <QtGui/QMessageBox>

#include <QtCore/QDebug>

enum { debug = 0 };

// Single selection helper
static inline int selectedRow(const QAbstractItemView *listView)
{
    const QModelIndexList indexList = listView->selectionModel()->selectedIndexes();
    if (indexList.size() == 1)
        return indexList.front().row();
    return -1;
}

// Helper to select a row. No sooner said then done
static inline void selectListRow(QAbstractItemView *iv, int row)
{
    const QModelIndex index = iv->model()->index(row, 0);
    iv->selectionModel()->select(index, QItemSelectionModel::Select);
}

namespace Git {
    namespace Internal {

static inline GitClient *gitClient()
{
    return GitPlugin::instance()->gitClient();
}

BranchDialog::BranchDialog(QWidget *parent) :
    QDialog(parent),
    m_ui(new Ui::BranchDialog),
    m_checkoutButton(0),
    m_diffButton(0),
    m_logButton(0),
    m_refreshButton(0),
    m_deleteButton(0),
    m_localModel(new LocalBranchModel(gitClient(), this)),
    m_remoteModel(new RemoteBranchModel(gitClient(), this))
{
    setModal(false);
    setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
    setAttribute(Qt::WA_DeleteOnClose, true); // Do not update unnecessarily

    m_ui->setupUi(this);

    m_checkoutButton = m_ui->buttonBox->addButton(tr("Checkout"), QDialogButtonBox::ActionRole);
    connect(m_checkoutButton, SIGNAL(clicked()), this, SLOT(slotCheckoutSelectedBranch()));

    m_diffButton = m_ui->buttonBox->addButton(tr("Diff"), QDialogButtonBox::ActionRole);
    connect(m_diffButton, SIGNAL(clicked()), this, SLOT(slotDiffSelected()));

    m_logButton = m_ui->buttonBox->addButton(tr("Log"), QDialogButtonBox::ActionRole);
    connect(m_logButton, SIGNAL(clicked()), this, SLOT(slotLog()));

    m_refreshButton = m_ui->buttonBox->addButton(tr("Refresh"), QDialogButtonBox::ActionRole);
    connect(m_refreshButton, SIGNAL(clicked()), this, SLOT(slotRefresh()));

    m_deleteButton = m_ui->buttonBox->addButton(tr("Delete..."), QDialogButtonBox::ActionRole);
    connect(m_deleteButton, SIGNAL(clicked()), this, SLOT(slotDeleteSelectedBranch()));

    connect(m_ui->localBranchListView, SIGNAL(doubleClicked(QModelIndex)), this,
            SLOT(slotLocalBranchActivated()));
    connect(m_ui->remoteBranchListView, SIGNAL(doubleClicked(QModelIndex)), this,
            SLOT(slotRemoteBranchActivated(QModelIndex)));

    connect(m_localModel, SIGNAL(newBranchEntered(QString)), this, SLOT(slotCreateLocalBranch(QString)));
    m_ui->localBranchListView->setModel(m_localModel);
    m_ui->remoteBranchListView->setModel(m_remoteModel);

    connect(m_ui->localBranchListView->selectionModel(),
            SIGNAL(selectionChanged(QItemSelection,QItemSelection)),
            this, SLOT(slotEnableButtons(QItemSelection)));
    connect(m_ui->remoteBranchListView->selectionModel(),
            SIGNAL(selectionChanged(QItemSelection,QItemSelection)),
            this, SLOT(slotEnableButtons(QItemSelection)));

    slotEnableButtons();
}

BranchDialog::~BranchDialog()
{
    delete m_ui;
}

void BranchDialog::refresh(const QString &repository, bool force)
{
    if (m_repository == repository && !force)
            return;
        // Refresh
    m_repository = repository;
    m_ui->repositoryLabel->setText(StashDialog::msgRepositoryLabel(m_repository));
    if (m_repository.isEmpty()) {
        m_localModel->clear();
        m_remoteModel->clear();
    } else {
        QString errorMessage;
        const bool success = m_localModel->refresh(m_repository, &errorMessage)
                             && m_remoteModel->refresh(m_repository, &errorMessage);
        if (!success)
            VCSBase::VCSBaseOutputWindow::instance()->appendError(errorMessage);
    }
    slotEnableButtons();
}

int BranchDialog::selectedLocalBranchIndex() const
{
    return selectedRow(m_ui->localBranchListView);
}

int BranchDialog::selectedRemoteBranchIndex() const
{
    return selectedRow(m_ui->remoteBranchListView);
}

void BranchDialog::slotEnableButtons(const QItemSelection &selected)
{
    if (!selected.indexes().isEmpty()) {
        if (selected.indexes().at(0).model() == m_localModel)
            m_ui->remoteBranchListView->clearSelection();
        else
            m_ui->localBranchListView->clearSelection();
    }

    // We can switch to or delete branches that are not current.
    const int selectedLocalRow = selectedLocalBranchIndex();
    const bool hasRepository = !m_repository.isEmpty();
    const bool hasLocalSelection = selectedLocalRow != -1 && !m_localModel->isNewBranchRow(selectedLocalRow);
    const bool otherLocalSelected = hasLocalSelection && selectedLocalRow != m_localModel->currentBranch();
    const bool branchSelected = hasLocalSelection || selectedRemoteBranchIndex() != -1;

    m_checkoutButton->setEnabled(otherLocalSelected);
    m_diffButton->setEnabled(branchSelected);
    m_logButton->setEnabled(branchSelected);
    m_deleteButton->setEnabled(otherLocalSelected);
    m_refreshButton->setEnabled(hasRepository);
    // Also disable <New Branch> entry of list view
    m_ui->localBranchListView->setEnabled(hasRepository);
    m_ui->remoteBranchListView->setEnabled(hasRepository);
}

void BranchDialog::slotRefresh()
{
    refresh(m_repository, true);
}

void BranchDialog::selectLocalBranch(const QString &b)
{
    // Select the newly created branch
    const int row = m_localModel->findBranchByName(b);
    if (row != -1)
        selectListRow(m_ui->localBranchListView, row);
}

bool BranchDialog::ask(const QString &title, const QString &what, bool defaultButton)
{
    return QMessageBox::question(this, title, what, QMessageBox::Yes|QMessageBox::No,
                                 defaultButton ? QMessageBox::Yes : QMessageBox::No) == QMessageBox::Yes;
}

/* Prompt to delete a local branch and do so. */
void BranchDialog::slotDeleteSelectedBranch()
{
    const int idx = selectedLocalBranchIndex();
    if (idx == -1)
        return;
    const QString name = m_localModel->branchName(idx);
    if (!ask(tr("Delete Branch"), tr("Would you like to delete the branch '%1'?").arg(name), true))
        return;
    QString errorMessage;
    bool ok = false;
    do {
        QString output;
        QStringList args(QLatin1String("-D"));
        args << name;
        if (!gitClient()->synchronousBranchCmd(m_repository, args, &output, &errorMessage))
            break;
        if (!m_localModel->refresh(m_repository, &errorMessage))
            break;
        ok = true;
    } while (false);
    slotEnableButtons();
    if (!ok)
        QMessageBox::warning(this, tr("Failed to delete branch"), errorMessage);
}

void BranchDialog::slotCreateLocalBranch(const QString &branchName)
{
    // Create
    QString output;
    QString errorMessage;
    bool ok = false;
    do {
        if (!gitClient()->synchronousBranchCmd(m_repository, QStringList(branchName), &output, &errorMessage))
            break;
        if (!m_localModel->refresh(m_repository, &errorMessage))
            break;
        ok = true;
    } while (false);
    if (!ok) {
        QMessageBox::warning(this, tr("Failed to create branch"), errorMessage);
        return;
    }
    selectLocalBranch(branchName);
}

void BranchDialog::slotLocalBranchActivated()
{
    if (m_checkoutButton->isEnabled())
        m_checkoutButton->animateClick();
}

void BranchDialog::slotDiffSelected()
{
    int idx = selectedLocalBranchIndex();
    if (idx != -1) {
        gitClient()->diffBranch(m_repository, QStringList(), m_localModel->branchName(idx));
        return;
    }
    idx = selectedRemoteBranchIndex();
    if (idx != -1)
        gitClient()->diffBranch(m_repository, QStringList(), m_remoteModel->branchName(idx));
}

void BranchDialog::slotLog()
{
    int idx = selectedLocalBranchIndex();
    if (idx != -1) {
        gitClient()->graphLog(m_repository, m_localModel->branchName(idx));
        return;
    }
    idx = selectedRemoteBranchIndex();
    if (idx != -1)
        gitClient()->graphLog(m_repository, m_remoteModel->branchName(idx));
}

/* Ask to stash away changes and then close dialog and do an asynchronous
 * checkout. */
void BranchDialog::slotCheckoutSelectedBranch()
{
    const int idx = selectedLocalBranchIndex();
    if (idx == -1)
        return;
    const QString name = m_localModel->branchName(idx);
    QString errorMessage;
    switch (gitClient()->ensureStash(m_repository, &errorMessage)) {
        case GitClient::StashUnchanged:
        case GitClient::Stashed:
        case GitClient::NotStashed:
        break;
        case GitClient::StashCanceled:
        return;
        case GitClient::StashFailed:
        QMessageBox::warning(this, tr("Failed to stash"), errorMessage);
        return;
    }
    if (gitClient()->synchronousCheckoutBranch(m_repository, name, &errorMessage)) {
        refresh(m_repository, true);
    } else {
        QMessageBox::warning(this, tr("Checkout failed"), errorMessage);
    }
}

void BranchDialog::slotRemoteBranchActivated(const QModelIndex &i)
{
    // Double click on a remote branch (origin/foo): Switch to matching
    // local (foo) one or offer to create a tracking branch.
    const QString remoteName = m_remoteModel->branchName(i.row());
    // build the name of the corresponding local branch
    // and look for it in the local model.
    const int slashPos = remoteName.indexOf(QLatin1Char('/'));
    if (slashPos == -1)
        return;
    const QString localBranch = remoteName.mid(slashPos + 1);
    if (localBranch == QLatin1String("HEAD") || localBranch == QLatin1String("master"))
        return;
   const int localIndex = m_localModel->findBranchByName(localBranch);
   if (debug)
        qDebug() << Q_FUNC_INFO << remoteName << localBranch << localIndex;
   // There is a matching a local one!
   if (localIndex != -1) {
       // Is it the current one? Just close.
       if (m_localModel->currentBranch() == localIndex) {
           accept();
           return;
       }
       // Nope, select and trigger checkout
       selectListRow(m_ui->localBranchListView, localIndex);
       slotLocalBranchActivated();
       return;
    }
    // Does not exist yet. Ask to create.
    const QString msg = tr("Would you like to create a local branch '%1' tracking the remote branch '%2'?").arg(localBranch, remoteName);
    if (!ask(tr("Create branch"), msg, true))
        return;
    QStringList args(QLatin1String("--track"));
    args << localBranch << remoteName;
    QString errorMessage;
    bool ok = false;
    do {
        QString output;
        if (!gitClient()->synchronousBranchCmd(m_repository, args, &output, &errorMessage))
            break;
        if (!m_localModel->refresh(m_repository, &errorMessage))
            break;
        ok = true;
    } while (false);
    if (!ok) {
        QMessageBox::warning(this, tr("Failed to create a tracking branch"), errorMessage);
        return;
    }
    // Select it
    selectLocalBranch(localBranch);
}

void BranchDialog::changeEvent(QEvent *e)
{
    QDialog::changeEvent(e);
    switch (e->type()) {
    case QEvent::LanguageChange:
        m_ui->retranslateUi(this);
        break;
    default:
        break;
    }
}

} // namespace Internal
} // namespace Git
