//-------------------------------------------------------------------------------------------------------
// Copyright (C) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
//-------------------------------------------------------------------------------------------------------
#pragma once

#include <unordered_set>
#include <queue>
#include "clang/Frontend/FrontendPluginRegistry.h"
#include "clang/AST/AST.h"
#include "clang/AST/ASTConsumer.h"
#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Rewrite/Core/Rewriter.h"
#include "clang/Sema/Sema.h"
#include "Helpers.h"

using namespace std;
using namespace clang;

// To record seen allocators with a Type
//
enum AllocationTypes
{
    Unknown = 0x0,                  // e.g. template dependent
    NonRecycler = 0x1,              // Heap, Arena, JitArena, ...
    Recycler = 0x2,                 // Recycler
    WriteBarrier = 0x4,             // Recycler write barrier

    RecyclerWriteBarrier = Recycler | WriteBarrier,
};

class MainVisitor:
    public RecursiveASTVisitor<MainVisitor>
{
private:
    CompilerInstance& _compilerInstance;
    ASTContext& _context;
    Rewriter _rewriter;
    bool _fix;      // whether user requested to fix missing annotations
    bool _fixed;    // whether this plugin committed any annotation fixes

    // For emitting checker errors
    DiagnosticsEngine& _diagEngine;
    unsigned _diagUnbarrieredField;
    unsigned _diagIllegalBarrierCast;

    bool _barrierTypeDefined;
    map<string, set<string>> _allocatorTypeMap;
    set<string> _pointerClasses;
    set<string> _barrieredClasses;

    map<const Type*, int> _allocationTypes;     // {type -> AllocationTypes}

public:
    MainVisitor(CompilerInstance& compilerInstance, ASTContext& context, bool fix);

    const ASTContext& getContext() const { return _context; }
    const CompilerInstance& getCompilerInstance() const { return _compilerInstance; }

    bool VisitCXXRecordDecl(CXXRecordDecl* recordDecl);
    bool VisitFunctionDecl(FunctionDecl* functionDecl);

    void RecordAllocation(QualType qtype, AllocationTypes allocationType);
    void RecordRecyclerAllocation(
        const string& allocationFunction, const string& type);
    void Inspect();
    bool ApplyFix();

    void ReportUnbarriedField(SourceLocation location);
    void ReportIllegalBarrierCast(SourceLocation location);

private:
    template <class Set, class DumpItemFunc>
    void dump(const char* name, const Set& set, const DumpItemFunc& func);
    template <class Item>
    void dump(const char* name, const set<Item>& set);
    void dump(const char* name, const unordered_set<const Type*> set);

    template <class PushFieldType>
    void ProcessUnbarrieredFields(CXXRecordDecl* recordDecl, const PushFieldType& pushFieldType);

    bool MatchType(const string& type, const char* source, const char** pSourceEnd);
    const char* GetFieldTypeAnnotation(QualType qtype);

    void DiagReport(SourceLocation location, unsigned diagId);
};

class CheckAllocationsInFunctionVisitor:
    public RecursiveASTVisitor<CheckAllocationsInFunctionVisitor>
{
public:
    CheckAllocationsInFunctionVisitor(
            MainVisitor* mainVisitor, FunctionDecl* functionDecl)
        : _mainVisitor(mainVisitor), _functionDecl(functionDecl)
    {}

    bool VisitCXXNewExpr(CXXNewExpr* newExpression);
    bool VisitCallExpr(CallExpr* callExpr);

#define IMPLEMENT_VISIT_CAST(Expr) \
    bool Visit##Expr(Expr *cast) { return CommonVisitCastExpr(cast); }

    IMPLEMENT_VISIT_CAST(CStyleCastExpr)
    IMPLEMENT_VISIT_CAST(CXXFunctionalCastExpr)
    IMPLEMENT_VISIT_CAST(CXXConstCastExpr)
    IMPLEMENT_VISIT_CAST(CXXDynamicCastExpr)
    IMPLEMENT_VISIT_CAST(CXXReinterpretCastExpr)
    IMPLEMENT_VISIT_CAST(CXXStaticCastExpr)
#undef IMPLEMENT_VISIT_CAST

private:
    MainVisitor* _mainVisitor;
    FunctionDecl* _functionDecl;

    template <class A0, class A1, class T>
    void VisitAllocate(const A0& getArg0, const A1& getArg1, const T& getAllocType);

    bool IsFieldPointer(const QualType& qtype, const char* alt = nullptr);
    bool CommonVisitCastExpr(CastExpr *cast);
};

class RecyclerCheckerConsumer: public ASTConsumer
{
private:
    CompilerInstance& _compilerInstance;
    bool _fix;  // whether user requested to fix missing annotations

public:
    RecyclerCheckerConsumer(CompilerInstance& compilerInstance, bool fix)
        : _compilerInstance(compilerInstance), _fix(fix)
    {}

    void HandleTranslationUnit(ASTContext& context);
};

class RecyclerCheckerAction: public PluginASTAction
{
private:
    bool _fix;  // whether user requested to fix missing annotations

public:
    RecyclerCheckerAction() : _fix(false) {}

protected:
    std::unique_ptr<ASTConsumer> CreateASTConsumer(
        CompilerInstance& compilerInstance, llvm::StringRef) override;

    bool ParseArgs(
        const CompilerInstance& compilerInstance,
        const std::vector<std::string>& args) override;
};
