/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** Commercial Usage
**
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Commercial License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Nokia.
**
** GNU Lesser General Public License Usage
**
** Alternatively, 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.
**
** If you are unsure which license is appropriate for your use, please
** contact the sales department at http://qt.nokia.com/contact.
**
**************************************************************************/

#include "cpptypehierarchy.h"
#include "cppeditorconstants.h"
#include "cppeditor.h"
#include "cppelementevaluator.h"
#include "cppplugin.h"

#include <coreplugin/editormanager/ieditor.h>
#include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/coreconstants.h>
#include <utils/navigationtreeview.h>

#include <QtCore/QLatin1Char>
#include <QtCore/QLatin1String>
#include <QtCore/QModelIndex>
#include <QtCore/QSettings>
#include <QtGui/QVBoxLayout>
#include <QtGui/QStandardItemModel>
#include <QtGui/QFontMetrics>
#include <QtGui/QApplication>
#include <QtGui/QPainter>
#include <QtGui/QLabel>

using namespace CppEditor;
using namespace Internal;

// CppTypeHierarchyItem
CppTypeHierarchyItem::CppTypeHierarchyItem(const CppClass &cppClass) :
    QStandardItem(), m_cppClass(cppClass)
{}

CppTypeHierarchyItem::~CppTypeHierarchyItem()
{}

int CppTypeHierarchyItem::type() const
{ return UserType; }

const CppClass &CppTypeHierarchyItem::cppClass() const
{ return m_cppClass; }

// CppTypeHierarchyDelegate
CppTypeHierarchyDelegate::CppTypeHierarchyDelegate(QObject *parent) : QStyledItemDelegate(parent)
{}

CppTypeHierarchyDelegate::~CppTypeHierarchyDelegate()
{}

void CppTypeHierarchyDelegate::paint(QPainter *painter,
                                     const QStyleOptionViewItem &option,
                                     const QModelIndex &index) const
{
    if (const QStyleOptionViewItemV3 *v3 =
            qstyleoption_cast<const QStyleOptionViewItemV3 *>(&option)) {
        QApplication::style()->drawPrimitive(QStyle::PE_PanelItemViewItem, v3, painter, v3->widget);

        QStyleOptionViewItemV4 opt = option;
        initStyleOption(&opt, index);

        const QStandardItemModel *model = static_cast<const QStandardItemModel *>(index.model());
        CppTypeHierarchyItem *item =
            static_cast<CppTypeHierarchyItem *>(model->itemFromIndex(index));

        painter->save();
        const QIcon &icon = item->cppClass().icon();
        const QSize &iconSize = icon.actualSize(opt.decorationSize);
        QRect workingRect(opt.rect);
        QRect decorationRect(workingRect.topLeft(), iconSize);
        icon.paint(painter, decorationRect, opt.decorationAlignment);
        workingRect.setX(workingRect.x() + iconSize.width() + 4);

        QRect boundingRect;
        const QString &name = item->cppClass().name() + QLatin1Char(' ');
        painter->drawText(workingRect, Qt::AlignLeft, name, &boundingRect);
        if (item->cppClass().name() != item->cppClass().qualifiedName()) {
            QFont font(painter->font());
            if (font.pointSize() > 2)
                font.setPointSize(font.pointSize() - 2);
            else if (font.pointSize() > 1)
                font.setPointSize(font.pointSize() - 1);
            font.setItalic(true);
            painter->setFont(font);

            QFontMetrics metrics(font);
            workingRect.setX(boundingRect.x() + boundingRect.width());
            workingRect.setY(boundingRect.y() + boundingRect.height() - metrics.height());
            painter->drawText(workingRect, Qt::AlignLeft, item->cppClass().qualifiedName());
        }
        painter->restore();
    } else {
        QStyledItemDelegate::paint(painter, option, index);
    }
}

QSize CppTypeHierarchyDelegate::sizeHint(const QStyleOptionViewItem &option,
                                        const QModelIndex &index) const
{
    QSize size = QStyledItemDelegate::sizeHint(option, index);
    size.rwidth() += 5; // Extend a bit because of the font processing.
    return size;
}

// CppTypeHierarchyWidget
CppTypeHierarchyWidget::CppTypeHierarchyWidget(Core::IEditor *editor) :
    QWidget(0),
    m_cppEditor(0),
    m_treeView(0),
    m_model(0),
    m_delegate(0)
{
    QVBoxLayout *layout = new QVBoxLayout;
    layout->setMargin(0);
    layout->setSpacing(0);

    if (CPPEditorEditable *cppEditable = qobject_cast<CPPEditorEditable *>(editor)) {
        m_cppEditor = static_cast<CPPEditor *>(cppEditable->widget());

        m_model = new QStandardItemModel;
        m_treeView = new Utils::NavigationTreeView;
        m_delegate = new CppTypeHierarchyDelegate;
        m_treeView->setModel(m_model);
        m_treeView->setEditTriggers(QAbstractItemView::NoEditTriggers);
        m_treeView->setItemDelegate(m_delegate);
        layout->addWidget(m_treeView);

        connect(m_treeView, SIGNAL(clicked(QModelIndex)), this, SLOT(onItemClicked(QModelIndex)));
        connect(CppPlugin::instance(), SIGNAL(typeHierarchyRequested()), this, SLOT(perform()));
    } else {
        QLabel *label = new QLabel(tr("No type hierarchy available"), this);
        label->setAlignment(Qt::AlignCenter);
        label->setAutoFillBackground(true);
        label->setBackgroundRole(QPalette::Base);
        layout->addWidget(label);
    }
    setLayout(layout);
}

CppTypeHierarchyWidget::~CppTypeHierarchyWidget()
{
    delete m_model;
    delete m_delegate;
}

bool CppTypeHierarchyWidget::handleReplacement(Core::IEditor *editor)
{
    if (CPPEditorEditable *cppEditable = qobject_cast<CPPEditorEditable *>(editor)) {
        if (m_cppEditor) {
            m_cppEditor = static_cast<CPPEditor *>(cppEditable->widget());
            return true;
        }
    } else if (!m_cppEditor) {
        return true;
    }
    return false;
}

void CppTypeHierarchyWidget::perform()
{
    if (!m_cppEditor)
        return;

    m_model->clear();

    CppElementEvaluator evaluator(m_cppEditor);
    evaluator.setLookupBaseClasses(true);
    QSharedPointer<CppElement> cppElement = evaluator.identifyCppElement();
    if (!cppElement.isNull()) {
        CppElement *element = cppElement.data();
        if (CppClass *cppClass = dynamic_cast<CppClass *>(element))
            buildModel(*cppClass, m_model->invisibleRootItem());
    }
}

void CppTypeHierarchyWidget::buildModel(const CppClass &cppClass, QStandardItem *parent)
{
    CppTypeHierarchyItem *item = new CppTypeHierarchyItem(cppClass);
    parent->appendRow(item);

    // The delegate retrieves data from the item directly. This is to help size hint.
    const QString &display = cppClass.name() + cppClass.qualifiedName();
    m_model->setData(m_model->indexFromItem(item), display, Qt::DisplayRole);
    m_model->setData(m_model->indexFromItem(item), cppClass.icon(), Qt::DecorationRole);

    foreach (const CppClass &cppBase, cppClass.bases())
        buildModel(cppBase, item);

    m_treeView->expand(m_model->indexFromItem(item));
}

void CppTypeHierarchyWidget::onItemClicked(const QModelIndex &index)
{
    if (QStandardItem *item = m_model->itemFromIndex(index)) {
        CppTypeHierarchyItem *cppItem = static_cast<CppTypeHierarchyItem *>(item);
        m_cppEditor->openLink(cppItem->cppClass().link());
    }
}

// CppTypeHierarchyStackedWidget
CppTypeHierarchyStackedWidget::CppTypeHierarchyStackedWidget(QWidget *parent) :
    QStackedWidget(parent),
    m_typeHiearchyWidgetInstance(
        new CppTypeHierarchyWidget(Core::EditorManager::instance()->currentEditor()))
{
    addWidget(m_typeHiearchyWidgetInstance);

    connect(Core::EditorManager::instance(), SIGNAL(currentEditorChanged(Core::IEditor*)),
            this, SLOT(editorChanged(Core::IEditor*)));
}

CppTypeHierarchyStackedWidget::~CppTypeHierarchyStackedWidget()
{
    delete m_typeHiearchyWidgetInstance;
}

void CppTypeHierarchyStackedWidget::editorChanged(Core::IEditor *editor)
{
    if (!m_typeHiearchyWidgetInstance->handleReplacement(editor)) {
        CppTypeHierarchyWidget *replacement = new CppTypeHierarchyWidget(editor);
        removeWidget(m_typeHiearchyWidgetInstance);
        m_typeHiearchyWidgetInstance->deleteLater();
        m_typeHiearchyWidgetInstance = replacement;
        addWidget(m_typeHiearchyWidgetInstance);
    }
}

// CppTypeHierarchyFactory
CppTypeHierarchyFactory::CppTypeHierarchyFactory()
{}

CppTypeHierarchyFactory::~CppTypeHierarchyFactory()
{}

QString CppTypeHierarchyFactory::displayName() const
{
    return tr("Type Hierarchy");
}

QString CppTypeHierarchyFactory::id() const
{
    return QLatin1String(Constants::TYPE_HIERARCHY_ID);
}

QKeySequence CppTypeHierarchyFactory::activationSequence() const
{
    return QKeySequence();
}

Core::NavigationView CppTypeHierarchyFactory::createWidget()
{
    CppTypeHierarchyStackedWidget *w = new CppTypeHierarchyStackedWidget;
    static_cast<CppTypeHierarchyWidget *>(w->currentWidget())->perform();
    Core::NavigationView navigationView;
    navigationView.widget = w;
    return navigationView;
}