/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: Nokia Corporation (qt-info@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 "gitoriousprojectwidget.h"
#include "gitorioushostwizardpage.h"
#include "gitorious.h"
#include "ui_gitoriousprojectwidget.h"

#include <utils/qtcassert.h>

#include <QtCore/QRegExp>
#include <QtCore/QDebug>

#include <QtGui/QStandardItemModel>
#include <QtGui/QSortFilterProxyModel>
#include <QtGui/QStandardItem>
#include <QtGui/QItemSelectionModel>
#include <QtGui/QDesktopServices>
#include <QtGui/QIcon>
#include <QtGui/QStyle>

enum {
    urlRole = Qt::UserRole + 1  // Project has a URL in the description
};

enum { debug = 1 };

namespace Gitorious {
namespace Internal {

enum { ProjectColumn, DescriptionColumn, ColumnCount };

GitoriousProjectWidget::GitoriousProjectWidget(int hostIndex,
                                               QWidget *parent) :
    QWidget(parent),
    m_hostName(Gitorious::instance().hostName(hostIndex)),
    ui(new Ui::GitoriousProjectWidget),
    m_model(new QStandardItemModel(0, ColumnCount, this)),
    m_filterModel(new QSortFilterProxyModel),
    m_valid(false)
{
    ui->setupUi(this);
    ui->infoToolButton->setIcon(style()->standardIcon(QStyle::SP_MessageBoxInformation));
    ui->infoToolButton->setEnabled(false);
    connect(ui->infoToolButton, SIGNAL(clicked()), this, SLOT(slotInfo()));
    // Filter
    connect(ui->filterLineEdit, SIGNAL(filterChanged(QString)), m_filterModel, SLOT(setFilterFixedString(QString)));
    // Updater
    ui->updateCheckBox->setChecked(true);
    if (Gitorious::instance().hostState(hostIndex) != GitoriousHost::ProjectsQueryRunning)
        ui->updateCheckBox->setVisible(false);
    connect(ui->updateCheckBox, SIGNAL(stateChanged(int)), this, SLOT(slotUpdateCheckBoxChanged(int)));
    // Model
    QStringList headers;
    headers << tr("Project") << tr("Description");
    m_model->setHorizontalHeaderLabels(headers);
    // Populate the model
    slotUpdateProjects(hostIndex);
    // Filter on all columns
    m_filterModel->setSourceModel(m_model);
    m_filterModel->setFilterKeyColumn(-1);
    m_filterModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
    m_filterModel->setSortCaseSensitivity(Qt::CaseInsensitive);
    ui->projectTreeView->setModel(m_filterModel);
    // View
    ui->projectTreeView->setAlternatingRowColors(true);
    ui->projectTreeView->setRootIsDecorated(false);
    ui->projectTreeView->setUniformRowHeights(true);
    ui->projectTreeView->setSortingEnabled(true);
    connect(ui->projectTreeView->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)),
            this, SLOT(slotCurrentChanged(QModelIndex,QModelIndex)));
    ui->projectTreeView->setSelectionMode(QAbstractItemView::SingleSelection);
    // Select first, resize columns
    if (Gitorious::instance().projectCount(hostIndex)) {
        for (int r = 0; r < ColumnCount; r++)
            ui->projectTreeView->resizeColumnToContents(r);
        // Select first
        const QModelIndex index = m_filterModel->index(0, 0);
        ui->projectTreeView->selectionModel()->setCurrentIndex(index, QItemSelectionModel::Select|QItemSelectionModel::Current|QItemSelectionModel::Rows);
    }

    // Continuous update
    Gitorious *gitorious = &Gitorious::instance();
    connect(gitorious, SIGNAL(projectListPageReceived(int,int)), this, SLOT(slotUpdateProjects(int)));
    connect(gitorious, SIGNAL(projectListReceived(int)), this, SLOT(slotUpdateProjects(int)));
}

GitoriousProjectWidget::~GitoriousProjectWidget()
{
    Gitorious *gitorious = &Gitorious::instance();
    disconnect(gitorious, SIGNAL(projectListPageReceived(int,int)), this, SLOT(slotUpdateProjects(int)));
    disconnect(gitorious, SIGNAL(projectListReceived(int)), this, SLOT(slotUpdateProjects(int)));
    delete ui;
}

// Map indexes back via filter
QStandardItem *GitoriousProjectWidget::itemFromIndex(const QModelIndex &index) const
{
    if (index.isValid())
        return m_model->itemFromIndex(m_filterModel->mapToSource(index));
    return 0;
}

QStandardItem *GitoriousProjectWidget::currentItem() const
{
    return itemFromIndex(ui->projectTreeView->selectionModel()->currentIndex());
}

void GitoriousProjectWidget::slotCurrentChanged(const QModelIndex &current, const QModelIndex & /* previous */)
{
    // Any info URL to show?
    QString url;
    if (current.isValid())
        if (QStandardItem *item = itemFromIndex(current)) {
        // Project: URL in description?
        const QVariant urlV = item->data(urlRole);
        if (urlV.isValid())
            url = urlV.toString();
    }

    ui->infoToolButton->setEnabled(!url.isEmpty());
    ui->infoToolButton->setToolTip(url);

    const bool isValid = current.isValid();
    if (isValid != m_valid) {
        m_valid = isValid;
        emit validChanged();
    }
}

void GitoriousProjectWidget::slotInfo()
{
    if (const QStandardItem *item = currentItem()) {
        const QVariant url = item->data(urlRole);
        if (url.isValid())
            QDesktopServices::openUrl(QUrl(url.toString()));
    }
}

// Create a model row for a project
static inline QList<QStandardItem *> projectEntry(const GitoriousProject &p)
{
    enum { maxNameLength = 30 };
    // Truncate names with colons
    QString name = p.name;
    const int colonPos = name.indexOf(QLatin1Char(':'));
    if (colonPos != -1)
        name.truncate(colonPos);
    if (name.size() > maxNameLength) {
        name.truncate(maxNameLength);
        name += QLatin1String("...");
    }
    QStandardItem *nameItem = new QStandardItem(name);
    nameItem->setFlags(Qt::ItemIsSelectable|Qt::ItemIsEnabled);
    // Description
    QStandardItem *descriptionItem = new QStandardItem;
    descriptionItem->setFlags(Qt::ItemIsSelectable|Qt::ItemIsEnabled);
    QList<QStandardItem *> rc;
    rc << nameItem << descriptionItem;
    // Should the text contain an URL, store it under 'urlRole' for the info button
    QString url;
    GitoriousProjectWidget::setDescription(p.description, DescriptionColumn, &rc, &url);
    if (!url.isEmpty()) {
        const QVariant urlV = QVariant(url);
        nameItem->setData(urlV, urlRole);
        descriptionItem->setData(urlV, urlRole);
    }
    return rc;
}

// Utility to set description column and tooltip for a row from a free
// format/HTMLish gitorious description. Make sure the description is just one
// row for the item and set a tooltip with full contents. If desired, extract
// an URL.

void GitoriousProjectWidget::setDescription(const QString &description,
                                                int descriptionColumn,
                                                QList<QStandardItem *> *items,
                                                QString *url /* =0 */)
{
    enum { MaxDescriptionLineLength = 70 };
    // Trim description to 1 sensibly long line for the item view
    QString descLine = description;
    const int newLinePos = descLine.indexOf(QLatin1Char('\n'));
    if (newLinePos != -1)
        descLine.truncate(newLinePos);
    if (descLine.size() > MaxDescriptionLineLength) {
        const int dotPos = descLine.lastIndexOf(QLatin1Char('.'), MaxDescriptionLineLength);
        if (dotPos != -1) {
            descLine.truncate(dotPos);
        } else {
            descLine.truncate(MaxDescriptionLineLength);
        }
        descLine += QLatin1String("...");
    }
    items->at(descriptionColumn)->setText(descLine);
    // Set a HTML tooltip to make lines wrap and the markup sprinkled within work
    const QString htmlTip = QLatin1String("<html><body>") + description + QLatin1String("</body></html>");
    const int size = items->size();
    for (int i = 0; i < size; i++)
        items->at(i)->setToolTip(htmlTip);
    if (url) {
        // Should the text contain an URL, extract
        // Do not fall for "(http://XX)", strip special characters
        static const QRegExp urlRegExp(QLatin1String("(http://[\\w\\.-]+/[a-zA-Z0-9/\\-&]*)"));
        Q_ASSERT(urlRegExp.isValid());
        if (urlRegExp.indexIn(description) != -1) {
            *url= urlRegExp.cap(1);
        } else {
            url->clear();
        }
    }
}

void GitoriousProjectWidget::grabFocus()
{
    ui->projectTreeView->setFocus();
}

void GitoriousProjectWidget::slotUpdateCheckBoxChanged(int state)
{
    if (state == Qt::Checked)
        slotUpdateProjects(Gitorious::instance().findByHostName(m_hostName));
}

void GitoriousProjectWidget::slotUpdateProjects(int hostIndex)
{
    if (!ui->updateCheckBox->isChecked())
        return;
    const Gitorious &gitorious = Gitorious::instance();
    // Complete list of projects
    if (m_hostName != gitorious.hostName(hostIndex))
        return;
    // Fill in missing projects
    const GitoriousHost::ProjectList &projects = gitorious.hosts().at(hostIndex).projects;
    const int size = projects.size();
    for (int i = m_model->rowCount(); i < size; i++)
        m_model->appendRow(projectEntry(*projects.at(i)));
    if (gitorious.hostState(hostIndex) == GitoriousHost::ProjectsComplete)
        ui->updateCheckBox->setVisible(false);
}

bool GitoriousProjectWidget::isValid() const
{
    return m_valid;
}

int GitoriousProjectWidget::hostIndex() const
{
    return Gitorious::instance().findByHostName(m_hostName);
}

QSharedPointer<GitoriousProject> GitoriousProjectWidget::project() const
{
    if (const QStandardItem *item = currentItem()) {
        const int projectIndex = item->row();
        return Gitorious::instance().hosts().at(hostIndex()).projects.at(projectIndex);
    }
    return QSharedPointer<GitoriousProject>(new GitoriousProject);
}

void GitoriousProjectWidget::changeEvent(QEvent *e)
{
    QWidget::changeEvent(e);
    switch (e->type()) {
    case QEvent::LanguageChange:
        ui->retranslateUi(this);
        break;
    default:
        break;
    }
}

} // namespace Internal
} // namespace Gitorious
