/*
   This file is part of the clazy static checker.

  Copyright (C) 2016-2017 Sergio Martins <smartins@kde.org>

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Library General Public
  License as published by the Free Software Foundation; either
  version 2 of the License, or (at your option) any later version.

  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.
*/

#ifndef CLAZY_TYPE_UTILS_H
#define CLAZY_TYPE_UTILS_H

#include "clazy_export.h"

#include <clang/AST/Type.h>
#include <clang/AST/Expr.h>
#include <clang/AST/StmtCXX.h>
#include <clang/AST/DeclCXX.h>
#include <clang/AST/ASTContext.h>
#include <string>

namespace clang {
class CompilerInstance;
class Expr;
class LangOptions;
class QualType;
class Stmt;
class VarDecl;
class Type;
class CXXRecordDecl;
class CXXBaseSpecifier;
}

class ClazyContext;

namespace TypeUtils
{
    /**
     * Returns the sizeof(void*) for the platform we're compiling for, in bits.
     */
    inline int sizeOfPointer(const clang::ASTContext *context, clang::QualType qt)
    {
        if (!qt.getTypePtrOrNull())
            return -1;
        // HACK: What's a better way of getting the size of a pointer ?
        return context->getTypeSize(context->getPointerType(qt));
    }

    struct QualTypeClassification {
        bool isConst = false;
        bool isReference = false;
        bool isBig = false;
        bool isNonTriviallyCopyable = false;
        bool passBigTypeByConstRef = false;
        bool passNonTriviallyCopyableByConstRef = false;
        bool passSmallTrivialByValue = false;
        int size_of_T = 0;
    };

    /**
     * Classifies a QualType, for example:
     *
     * This function is useful to know if a type should be passed by value or const-ref.
     * The optional parameter body is in order to advise non-const-ref -> value, since the body
     * needs to be inspected to see if we that would compile.
     */
    CLAZYLIB_EXPORT bool classifyQualType(const ClazyContext *context, const clang::VarDecl *varDecl,
                                          QualTypeClassification &classification,
                                          clang::Stmt *body = nullptr);

    /**
     * If qt is a reference, return it without a reference.
     * If qt is not a reference, return qt.
     *
     * This is useful because sometimes you have an argument like "const QString &", but qualType.isConstQualified()
     * returns false. Must go through qualType->getPointeeType().isConstQualified().
     */
    inline clang::QualType unrefQualType(clang::QualType qualType)
    {
        const clang::Type *t = qualType.getTypePtrOrNull();
        return (t && t->isReferenceType()) ? t->getPointeeType() : qualType;
    }

    /**
     * If qt is a pointer or ref, return it without * or &.
     * Otherwise return qt unchanged.
     */
    inline clang::QualType pointeeQualType(clang::QualType qualType)
    {
        // TODO: Make this recursive when we need to remove more than one level of *
        const clang::Type *t = qualType.getTypePtrOrNull();
        return (t && (t->isReferenceType() || t->isPointerType())) ? t->getPointeeType() : qualType;
    }

    /**
     * Returns if @p arg is stack or heap allocated.
     * true means it is. false means it either isn't or the situation was too complex to judge.
     */
    CLAZYLIB_EXPORT void heapOrStackAllocated(clang::Expr *arg, const std::string &type,
                                              const clang::LangOptions &lo,
                                              bool &isStack, bool &isHeap);

    /**
     * Returns true if t is an AutoType that can't be deduced.
     */
    inline bool isUndeducibleAuto(const clang::Type *t)
    {
        if (!t)
            return false;

        auto at = llvm::dyn_cast<clang::AutoType>(t);
        return at && at->getDeducedType().isNull();
    }

    inline const clang::Type * unpealAuto(clang::QualType q)
    {
        if (q.isNull())
            return nullptr;

        if (auto t = llvm::dyn_cast<clang::AutoType>(q.getTypePtr()))
            return t->getDeducedType().getTypePtrOrNull();

        return q.getTypePtr();
    }

    /**
     * Returns true if childDecl is a descent from parentDecl
     **/
    CLAZYLIB_EXPORT bool derivesFrom(const clang::CXXRecordDecl *derived, const clang::CXXRecordDecl *possibleBase,
                                     std::vector<clang::CXXRecordDecl*> *baseClasses = nullptr);

    // Overload
    CLAZYLIB_EXPORT bool derivesFrom(const clang::CXXRecordDecl *derived, const std::string &possibleBase);

    // Overload
    CLAZYLIB_EXPORT bool derivesFrom(clang::QualType derived, const std::string &possibleBase);

    /**
     * Returns the CXXRecordDecl represented by the CXXBaseSpecifier
     */
    inline clang::CXXRecordDecl * recordFromBaseSpecifier(const clang::CXXBaseSpecifier &base)
    {
        const clang::Type *t = base.getType().getTypePtrOrNull();
        return t ? t->getAsCXXRecordDecl() : nullptr;
    }
    /**
     * Returns true if the value is const. This is usually equivalent to qt.isConstQualified() but
     * takes care of the special case where qt represents a pointer. Many times you don't care if the
     * pointer is const or not and just care about the pointee.
     *
     * A a;        => false
     * const A a;  => true
     * A* a;       => false
     * const A* a; => true
     * A *const a; => false
     */
    inline bool valueIsConst(clang::QualType qt)
    {
        return pointeeQualType(qt).isConstQualified();
    }

    inline clang::CXXRecordDecl* typeAsRecord(clang::QualType qt)
    {
        if (qt.isNull())
            return nullptr;

        return qt->getAsCXXRecordDecl();
    }

    inline clang::CXXRecordDecl* typeAsRecord(clang::Expr *expr)
    {
        if (!expr)
            return nullptr;

        return typeAsRecord(pointeeQualType(expr->getType()));
    }

    inline clang::CXXRecordDecl* typeAsRecord(clang::ValueDecl *value)
    {
        if (!value)
            return nullptr;

        return typeAsRecord(pointeeQualType(value->getType()));
    }

    /**
     * Returns the class that the typedef refered by qt is in.
     *
     * class Foo {
     *     typedef A B;
     * };
     *
     * For the above example Foo would be returned.
     */
    inline clang::CXXRecordDecl* parentRecordForTypedef(clang::QualType qt)
    {
        auto t = qt.getTypePtrOrNull();
        if (!t)
            return nullptr;

        if (t->getTypeClass() == clang::Type::Typedef) {
            auto tdt = static_cast<const clang::TypedefType*>(t);
            clang::TypedefNameDecl *tdnd = tdt->getDecl();
            return llvm::dyn_cast_or_null<clang::CXXRecordDecl>(tdnd->getDeclContext());

        }

        return nullptr;
    }

    // Example: const QString &
    inline bool isConstRef(const clang::Type *t)
    {
        return t && t->isReferenceType() && t->getPointeeType().isConstQualified();
    }
}

#endif
