#ifndef SYNTH_MULTI_TU_PROCESSOR_HPP_INCLUDED
#define SYNTH_MULTI_TU_PROCESSOR_HPP_INCLUDED

#include "FileIdSupport.hpp"
#include "output.hpp"

#include <boost/filesystem/path.hpp>
#include <clang-c/Index.h>

#include <atomic>
#include <mutex>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>

namespace synth {

class SimpleTemplate;

namespace fs = boost::filesystem;

struct FileEntry {
    std::atomic_flag processed;
    HighlightedFile hlFile;
};

using PathMap = std::vector<std::pair<fs::path, fs::path>>;

// Trys to link m to an external URL that represents what refcur references.
// The callee must be thread safe.
using ExternalRefLinker = std::function<void(Markup& m, CXCursor mcur)>;

class MultiTuProcessor {
    struct SymbolId {
        HighlightedFile const* file;
        unsigned offset; // UINT_MAX: Whole file.

        bool operator== (SymbolId const& other) const {
            return offset == other.offset && file == other.file;
        }
    };

    struct SymbolIdHasher {
        std::size_t operator() (SymbolId const& sym) const {
            std::size_t h = std::hash<decltype(sym.file)>()(sym.file);
            boost::hash_combine(h,
                std::hash<decltype(sym.offset)>()(sym.offset));
            return h;
        }
    };

    using SymbolMap = std::unordered_map<SymbolId, SymbolDeclaration, SymbolIdHasher>;
public:
    explicit MultiTuProcessor(
        PathMap const& rootdir_, ExternalRefLinker&& refLinker);

    // Setter is not threadsafe!
    void setMaxIdSz(std::size_t maxIdSz) noexcept { m_maxIdSz = maxIdSz; }
    std::size_t maxIdSz() const noexcept { return m_maxIdSz; }

    bool isFileIncluded(fs::path const& p) const;

    // Returns nullptr if references to f should be ignored.
    // Pass 0 for lineno and UINT_MAX for offset if referencing
    // the file as a whole.
    SymbolDeclaration const* referenceSymbol(
        CXFile f, unsigned lineno, unsigned offset);

    SymbolDeclaration& createSymbol(
        HighlightedFile const& hlFile, unsigned lineno, unsigned offset);

    HighlightedFile* prepareToProcess(CXFile f);

    void registerDef(std::string&& usr, SymbolDeclaration const* def);

    // Not threadsafe!
    void writeOutput(SimpleTemplate const& tpl);

    // Not threadsafe!
    SymbolDeclaration const* findMissingDef(std::string const& usr)
    {
        auto it = m_defs.find(usr);
        return it == m_defs.end() ? nullptr : it->second;
    }

    void linkExternalRef(Markup& m, CXCursor mcur)
    {
        m_refLinker(m, mcur);
    }

private:

    using FileEntryMap = std::unordered_map<CXFileUniqueID, FileEntry>;

    // Returns nullptr if f should be ignored.
    FileEntry* obtainFileEntry(CXFile f);

    PathMap::value_type const* getFileMapping(fs::path const& p) const;


    FileEntryMap m_processedFiles;
    PathMap m_dirs;

    // Maps from USRs to symbol declarations (referencing m_syms)
    std::unordered_map<std::string, SymbolDeclaration const*> m_defs;
    SymbolMap m_syms;

    // Common prefix of all keys in m_dirs
    fs::path m_rootInDir;

    ExternalRefLinker m_refLinker;

    std::size_t m_maxIdSz; // Maximum length for fileUniqueNames in m_syms.


    std::mutex m_mut;
};

} // namespace synth

#endif