/****************************************************************************
**
** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Digia.  For licensing terms and
** conditions see http://qt.digia.com/licensing.  For further information
** use the contact form at http://qt.digia.com/contact-us.
**
** 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.
**
** In addition, as a special exception, Digia gives you certain additional
** rights.  These rights are described in the Digia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
****************************************************************************/

#include "unit.h"
#include "unsavedfiledata.h"
#include "utils_p.h"

#include <clang-c/Index.h>

#include <QtCore/QByteArray>
#include <QtCore/QVector>
#include <QtCore/QSharedData>
#include <QtAlgorithms>

#ifdef DEBUG_UNIT_COUNT
#  include <QAtomicInt>
#  include <QDebug>
static QBasicAtomicInt unitDataCount = Q_BASIC_ATOMIC_INITIALIZER(0);
#endif // DEBUG_UNIT_COUNT

using namespace ClangCodeModel;
using namespace ClangCodeModel::Internal;

static const int DisplayDiagnostics = (qgetenv("QTC_CLANG_VERBOSE") == "1") ? 1 : 0;

Unit::Unit()
    : m_index(0)
    , m_tu(0)
    , m_managementOptions(0)
{}

Unit::Unit(const QString &fileName)
    : m_index(clang_createIndex(/*excludeDeclsFromPCH*/ 1, DisplayDiagnostics))
    , m_tu(0)
    , m_fileName(fileName.toUtf8())
    , m_managementOptions(0)
{}

Unit::~Unit()
{
    unload();
    clang_disposeIndex(m_index);
    m_index = 0;
}

Unit::Ptr Unit::create()
{
    return Unit::Ptr(new Unit);
}

Unit::Ptr Unit::create(const QString &fileName)
{
    return Unit::Ptr(new Unit(fileName));
}

const QString Unit::fileName() const
{
    return QString::fromUtf8(m_fileName.data(), m_fileName.size());
}

bool Unit::isLoaded() const
{
    return m_tu && m_index;
}

const QDateTime &Unit::timeStamp() const
{
    return m_timeStamp;
}

QStringList Unit::compilationOptions() const
{
    return m_compOptions;
}

void Unit::setCompilationOptions(const QStringList &compOptions)
{
    m_compOptions = compOptions;
    m_sharedCompOptions.reloadOptions(compOptions);
}

UnsavedFiles Unit::unsavedFiles() const
{
    return m_unsaved;
}

void Unit::setUnsavedFiles(const UnsavedFiles &unsavedFiles)
{
    m_unsaved = unsavedFiles;
}

unsigned Unit::managementOptions() const
{
    return m_managementOptions;
}

void Unit::setManagementOptions(unsigned managementOptions)
{
    m_managementOptions = managementOptions;
}

void Unit::parse()
{
    unload();

    updateTimeStamp();

    UnsavedFileData unsaved(m_unsaved);
    m_tu = clang_parseTranslationUnit(m_index, m_fileName.constData(),
                                      m_sharedCompOptions.data(), m_sharedCompOptions.size(),
                                      unsaved.files(), unsaved.count(),
                                      m_managementOptions);
}

void Unit::reparse()
{
    Q_ASSERT(isLoaded());

    UnsavedFileData unsaved(m_unsaved);
    const unsigned opts = clang_defaultReparseOptions(m_tu);
    if (clang_reparseTranslationUnit(m_tu, unsaved.count(), unsaved.files(), opts) != 0)
        unload();
}

int Unit::save(const QString &unitFileName)
{
    Q_ASSERT(isLoaded());

    return clang_saveTranslationUnit(m_tu, unitFileName.toUtf8().constData(),
                                     clang_defaultSaveOptions(m_tu));
}

void Unit::unload()
{
    if (m_tu) {
        clang_disposeTranslationUnit(m_tu);
        m_tu = 0;

#ifdef DEBUG_UNIT_COUNT
        qDebug() << "# translation units:" << (unitDataCount.fetchAndAddOrdered(-1) - 1);
#endif // DEBUG_UNIT_COUNT
    }
}

CXFile Unit::getFile() const
{
    Q_ASSERT(isLoaded());

    return clang_getFile(m_tu, m_fileName.constData());
}

CXCursor Unit::getCursor(const CXSourceLocation &location) const
{
    Q_ASSERT(isLoaded());

    return clang_getCursor(m_tu, location);
}

CXSourceLocation Unit::getLocation(const CXFile &file, unsigned line, unsigned column) const
{
    Q_ASSERT(isLoaded());

    return clang_getLocation(m_tu, file, line, column);
}

void Unit::codeCompleteAt(unsigned line, unsigned column, ScopedCXCodeCompleteResults &results)
{
    unsigned flags = clang_defaultCodeCompleteOptions();
#if defined(CINDEX_VERSION) && (CINDEX_VERSION > 5)
    flags |= CXCodeComplete_IncludeBriefComments;
#endif

    UnsavedFileData unsaved(m_unsaved);
    results.reset(clang_codeCompleteAt(m_tu, m_fileName.constData(),
                                       line, column,
                                       unsaved.files(), unsaved.count(),
                                       flags));
}

void Unit::tokenize(CXSourceRange range, CXToken **tokens, unsigned *tokenCount) const
{
    Q_ASSERT(isLoaded());
    Q_ASSERT(tokens);
    Q_ASSERT(tokenCount);
    Q_ASSERT(!clang_Range_isNull(range));

    clang_tokenize(m_tu, range, tokens, tokenCount);
}

void Unit::disposeTokens(CXToken *tokens, unsigned tokenCount) const
{
    Q_ASSERT(isLoaded());

    clang_disposeTokens(m_tu, tokens, tokenCount);
}

CXSourceRange Unit::getTokenExtent(const CXToken &token) const
{
    Q_ASSERT(isLoaded());

    return clang_getTokenExtent(m_tu, token);
}

void Unit::annotateTokens(CXToken *tokens, unsigned tokenCount, CXCursor *cursors) const
{
    Q_ASSERT(isLoaded());
    Q_ASSERT(tokens);
    Q_ASSERT(cursors);

    clang_annotateTokens(m_tu, tokens, tokenCount, cursors);
}

CXTranslationUnit Unit::clangTranslationUnit() const
{
    Q_ASSERT(isLoaded());

    return m_tu;
}

CXIndex Unit::clangIndex() const
{
    Q_ASSERT(isLoaded());

    return m_index;
}

QString Unit::getTokenSpelling(const CXToken &tok) const
{
    Q_ASSERT(isLoaded());

    return getQString(clang_getTokenSpelling(m_tu, tok));
}

CXCursor Unit::getTranslationUnitCursor() const
{
    Q_ASSERT(isLoaded());

    return clang_getTranslationUnitCursor(m_tu);
}

CXString Unit::getTranslationUnitSpelling() const
{
    Q_ASSERT(isLoaded());

    return clang_getTranslationUnitSpelling(m_tu);
}

void Unit::getInclusions(CXInclusionVisitor visitor, CXClientData clientData) const
{
    Q_ASSERT(isLoaded());

    clang_getInclusions(m_tu, visitor, clientData);
}

unsigned Unit::getNumDiagnostics() const
{
    Q_ASSERT(isLoaded());

    return clang_getNumDiagnostics(m_tu);
}

CXDiagnostic Unit::getDiagnostic(unsigned index) const
{
    Q_ASSERT(isLoaded());

    return clang_getDiagnostic(m_tu, index);
}

void Unit::updateTimeStamp()
{
    m_timeStamp = QDateTime::currentDateTime();
}

IdentifierTokens::IdentifierTokens(const Unit &unit, unsigned firstLine, unsigned lastLine)
    : m_unit(unit)
    , m_tokenCount(0)
    , m_tokens(0)
    , m_cursors(0)
    , m_extents(0)
{
    Q_ASSERT(unit.isLoaded());

    // Calculate the range:
    CXFile file = unit.getFile();
    CXSourceLocation startLocation = unit.getLocation(file, firstLine, 1);
    CXSourceLocation endLocation = unit.getLocation(file, lastLine, 1);
    CXSourceRange range = clang_getRange(startLocation, endLocation);

    // Retrieve all identifier tokens:
    unit.tokenize(range, &m_tokens, &m_tokenCount);
    if (m_tokenCount == 0)
        return;

    // Get the cursors for the tokens:
    m_cursors = new CXCursor[m_tokenCount];
    unit.annotateTokens(m_tokens,
                        m_tokenCount,
                        m_cursors);

    m_extents = new CXSourceRange[m_tokenCount];
    // Create the markers using the cursor to check the types:
    for (unsigned i = 0; i < m_tokenCount; ++i)
        m_extents[i] = unit.getTokenExtent(m_tokens[i]);
}

IdentifierTokens::~IdentifierTokens()
{
    dispose();
}

void IdentifierTokens::dispose()
{
    if (!m_unit.isLoaded())
        return;

    if (m_tokenCount && m_tokens) {
        m_unit.disposeTokens(m_tokens, m_tokenCount);
        m_tokens = 0;
        m_tokenCount = 0;
    }

    if (m_cursors) {
        delete[] m_cursors;
        m_cursors = 0;
    }

    if (m_extents) {
        delete[] m_extents;
        m_extents = 0;
    }
}
