#pragma once

#include <unordered_map>
#include <unordered_set>
#include <tuple>
#include <utility>

#include "common.h"
#include "makeUnique.h"

#include "LibLsp/lsp/lsPosition.h"
#include "LibLsp/lsp/textDocument/documentColor.h"

namespace AsymptoteLsp
{
  struct SymbolLit
  {
    std::string name;
    std::vector<std::string> scopes;

    SymbolLit(std::string symName) :
      name(std::move(symName))
    {
    }

    SymbolLit(std::string symName, std::vector<std::string> scope) :
            name(std::move(symName)), scopes(std::move(scope))
    {
    }

    ~SymbolLit() = default;

    SymbolLit(SymbolLit const& sym) :
      name(sym.name), scopes(sym.scopes)
    {
    }

    SymbolLit& operator=(SymbolLit const& sym)
    {
      name = sym.name;
      scopes = sym.scopes;
      return *this;
    }

    SymbolLit(SymbolLit&& sym) noexcept :
      name(std::move(sym.name)), scopes(std::move(sym.scopes))
    {
    }

    SymbolLit& operator=(SymbolLit&& sym) noexcept
    {
      name = std::move(sym.name);
      scopes = std::move(sym.scopes);
      return *this;
    }

    bool operator==(SymbolLit const& other) const
    {
      return name == other.name and scopes == other.scopes;
    }

    bool matchesRaw(std::string const& sym) const
    {
      return name == sym;
    }
  };
} // namespace AsymptoteLsp

namespace std
{
  using AsymptoteLsp::SymbolLit;

  template<>
  struct hash<SymbolLit>
  {
    std::size_t operator()(SymbolLit const& sym) const
    {
      size_t final_hash = 0;
      final_hash ^= hash<std::string>()(sym.name);
      for (auto const& accessor : sym.scopes)
      {
        final_hash = (final_hash << 1) ^ hash<std::string>()(accessor);
      }
      return final_hash;
    }
  };
} // namespace std

namespace AsymptoteLsp
{
  using std::unordered_map;
  struct SymbolContext;

  typedef std::pair<std::string, SymbolContext*> contextedSymbol;
  typedef std::pair<size_t, size_t> posInFile;
  typedef std::pair<std::string, posInFile> filePos;
  typedef std::tuple<std::string, posInFile, posInFile> posRangeInFile;
  typedef std::tuple<SymbolLit, posInFile, posInFile> fullSymPosRangeInFile;


  // NOTE: lsPosition is zero-indexed, while all Asymptote positions (incl this struct) is 1-indexed.
  inline posInFile fromLsPosition(lsPosition const& inPos)
  {
    return std::make_pair(inPos.line + 1, inPos.character + 1);
  }

  inline lsPosition toLsPosition(posInFile const& inPos)
  {
    return lsPosition(inPos.first - 1, inPos.second - 1);
  }

  inline bool posLt(posInFile const& p1, posInFile const& p2)
  {
    return (p1.first < p2.first) or ((p1.first == p2.first) and (p1.second < p2.second));
  }

  std::string getPlainFile();
  bool isVirtualFile(std::string const& filename);

  // filename to positions
  struct positions
  {
    std::unordered_map<std::string, std::vector<posInFile>> pos;

    positions() = default;
    explicit positions(filePos const& positionInFile);
    void add(filePos const& positionInFile);
  };


  struct SymbolInfo
  {
    std::string name;
    optional<std::string> type;
    posInFile pos;

    SymbolInfo() : type(nullopt), pos(1, 1) {}

    SymbolInfo(std::string inName, posInFile position):
      name(std::move(inName)), type(nullopt), pos(std::move(position)) {}

    SymbolInfo(std::string inName, std::string inType, posInFile position):
      name(std::move(inName)), type(std::move(inType)), pos(std::move(position)) {}

    SymbolInfo(SymbolInfo const& symInfo) = default;

    SymbolInfo& operator=(SymbolInfo const& symInfo) = default;

    SymbolInfo(SymbolInfo&& symInfo) noexcept :
            name(std::move(symInfo.name)), type(std::move(symInfo.type)), pos(std::move(symInfo.pos))
    {
    }

    SymbolInfo& operator=(SymbolInfo&& symInfo) noexcept
    {
      name = std::move(symInfo.name);
      type = std::move(symInfo.type);
      pos = std::move(symInfo.pos);
      return *this;
    }

    virtual ~SymbolInfo() = default;

    bool operator==(SymbolInfo const& sym) const;

    [[nodiscard]]
    virtual std::string signature() const;
  };

  struct FunctionInfo: SymbolInfo
  {
    std::string returnType;
    using typeName = std::pair<std::string, optional<std::string>>;
    std::vector<typeName> arguments;
    optional<typeName> restArgs;

    FunctionInfo(std::string name, posInFile pos, std::string returnTyp):
            SymbolInfo(std::move(name), std::move(pos)),
            returnType(std::move(returnTyp)),
            arguments(), restArgs(nullopt) {}

    ~FunctionInfo() override = default;

    [[nodiscard]]
    std::string signature() const override;

    [[nodiscard]]
    std::string signature(std::vector<std::string> const& scopes) const;
  };


  struct TypeDec
  {
    posInFile position;
    std::string typeName;

    TypeDec(): position(1, 1) {}
    virtual ~TypeDec() = default;

    TypeDec(posInFile pos, std::string typName):
            position(std::move(pos)), typeName(std::move(typName))
    {
    }

    TypeDec(TypeDec const& typDec) = default;
    TypeDec& operator= (TypeDec const& typDec) = default;

    TypeDec(TypeDec&& typDec) noexcept = default;
    TypeDec& operator= (TypeDec&& typDec) = default;

    [[nodiscard]]
    virtual unique_ptr<TypeDec> clone() const
    {
      return make_unique<TypeDec>(*this);
    }
  };

  struct TypedefDec : public TypeDec
  {
    std::string destName;
  };

  struct SymColorInfo
  {
    posInFile rangeBegin;
    posInFile rangeEnd;

    using RGBColor = std::tuple<double, double, double>;
    using RGBAColor = std::tuple<double, double, double, double>;
    void setLastArgPos(posInFile lastArgPos) { lastArgPosition = std::move(lastArgPos); }

    SymColorInfo() = default;
    virtual ~SymColorInfo() = default;
    SymColorInfo(SymColorInfo const& col) = default;
    SymColorInfo& operator=(SymColorInfo const& col) = default;

    SymColorInfo(SymColorInfo&& col) noexcept = default;
    SymColorInfo& operator=(SymColorInfo&& col) noexcept = default;



    [[nodiscard]]
    virtual RGBColor getRGBColor() const = 0;

    [[nodiscard]]
    virtual double getAlpha() const
    {
      return 1;
    }

    [[nodiscard]]
    RGBAColor getRGBAColor() const
    {
      RGBColor c=getRGBColor();
      auto const& red=std::get<0>(c);
      auto const& green=std::get<1>(c);
      auto const& blue=std::get<2>(c);
      return RGBAColor(red,green,blue,getAlpha());
    }

    explicit operator TextDocument::Color() const
    {
      TextDocument::Color col;
      RGBAColor c=getRGBAColor();
      col.red=std::get<0>(c);
      col.green=std::get<1>(c);
      col.blue=std::get<2>(c);
      col.alpha=std::get<3>(c);
      return col;
    }

    [[nodiscard]]
    virtual unique_ptr<SymColorInfo> clone() const = 0;

  public:
    posInFile lastArgPosition;
  };

  struct RGBSymColorInfo : SymColorInfo
  {
    double red, green, blue;

    RGBSymColorInfo(): SymColorInfo(), red(0), green(0), blue(0) {}
    RGBSymColorInfo(double redVal, double greenVal, double blueVal):
      SymColorInfo(),
      red(redVal), green(greenVal), blue(blueVal)
    {
    }

    RGBSymColorInfo(RGBSymColorInfo const& col) = default;
    RGBSymColorInfo& operator=(RGBSymColorInfo const& col) = default;

    [[nodiscard]]
    RGBColor getRGBColor() const override
    {
      return RGBColor(red, green, blue);
    }

    [[nodiscard]]
    unique_ptr<SymColorInfo> clone() const override
    {
      return unique_ptr<SymColorInfo>(new RGBSymColorInfo(*this));
    }
  };

  struct RGBASymColorInfo : RGBSymColorInfo
  {
    double alpha;

    RGBASymColorInfo(): RGBSymColorInfo(), alpha(1) {}
    RGBASymColorInfo(double redVal, double greenVal, double blueVal, double alphaVal):
            RGBSymColorInfo(redVal, greenVal, blueVal), alpha(alphaVal)
    {
    }

    [[nodiscard]]
    double getAlpha() const override
    {
      return alpha;
    }

    [[nodiscard]]
    unique_ptr<SymColorInfo> clone() const override
    {
      return unique_ptr<SymColorInfo>(new RGBASymColorInfo(*this));
    }
  };

  struct StructDecs : public TypeDec
  {
    SymbolContext* ctx;

    StructDecs(): TypeDec(), ctx(nullptr) {}
    ~StructDecs() override = default;

    StructDecs(posInFile pos, std::string typName) :
            TypeDec(std::move(pos), std::move(typName)), ctx(nullptr)
    {
    }

    StructDecs(posInFile pos, std::string typName, SymbolContext* ctx) :
            TypeDec(std::move(pos), std::move(typName)), ctx(ctx)
    {
    }

    [[nodiscard]]
    unique_ptr<TypeDec> clone() const override
    {
      return std::unique_ptr<TypeDec>(new StructDecs(*this));
    }
  };

  struct SymbolMaps
  {
    unordered_map <std::string, SymbolInfo> varDec;
    unordered_map <std::string, std::vector<FunctionInfo>> funDec;
    // can refer to other files
    unordered_map <SymbolLit, positions> varUsage;
    unordered_map <std::string, unique_ptr<TypeDec>> typeDecs;

    // python equivalent of dict[str, list[tuple(pos, sym)]]
    // filename -> list[(position, symbol)]

    std::vector<std::pair<posInFile, SymbolLit>> usageByLines;

    SymbolMaps() = default;
    ~SymbolMaps() = default;

    SymbolMaps(SymbolMaps const& symMap) :
    varDec(symMap.varDec), funDec(symMap.funDec), varUsage(symMap.varUsage), typeDecs(),
    usageByLines(symMap.usageByLines)
    {
      for(auto const& t : symMap.typeDecs)
      {
        auto const& ty=std::get<0>(t);
        auto const& tyDec=std::get<1>(t);
        typeDecs.emplace(ty, tyDec != nullptr ? tyDec->clone() : nullptr);
      }
    }

    SymbolMaps& operator=(SymbolMaps const& symMap)
    {
      varDec = symMap.varDec;
      funDec = symMap.funDec;
      varUsage = symMap.varUsage;
      usageByLines = symMap.usageByLines;

      typeDecs.clear();
      for(auto const& t : symMap.typeDecs)
      {
        auto const& ty=std::get<0>(t);
        auto const& tyDec=std::get<1>(t);
        typeDecs.emplace(ty, tyDec != nullptr ? tyDec->clone() : nullptr);
      }
      return *this;
    }

    SymbolMaps(SymbolMaps&& symMap) noexcept:
            varDec(std::move(symMap.varDec)), funDec(std::move(symMap.funDec)), varUsage(std::move(symMap.varUsage)),
            typeDecs(std::move(symMap.typeDecs)), usageByLines(std::move(symMap.usageByLines))
    {
    }

    SymbolMaps& operator=(SymbolMaps&& symMap) noexcept
    {
      varDec = std::move(symMap.varDec);
      funDec = std::move(symMap.funDec);
      varUsage = std::move(symMap.varUsage);
      usageByLines = std::move(symMap.usageByLines);
      typeDecs = std::move(symMap.typeDecs);

      return *this;
    }

    inline void clear()
    {
      varDec.clear();
      funDec.clear();
      varUsage.clear();
      usageByLines.clear();
      typeDecs.clear();
    }
    optional<fullSymPosRangeInFile> searchSymbol(posInFile const& inputPos);
    FunctionInfo& addFunDef(std::string const& funcName, posInFile const& position, std::string const& returnType);

  private:
    friend ostream& operator<<(std::ostream& os, const SymbolMaps& sym);
  };

  struct ExternalRefs
  {

    // file interactions
    // access -> (file, id)
    // unravel -> id
    // include -> file
    // import = access + unravel

    using extRefMap = std::unordered_map<std::string, SymbolContext*>;
    extRefMap extFileRefs;
    std::unordered_map<std::string, std::string> fileIdPair;
    std::unordered_set<std::string> includeVals;
    std::unordered_set<std::string> unraveledVals;
    std::unordered_set<std::string> accessVals;
    std::unordered_map<std::string, std::pair<std::string, std::string>> fromAccessVals;

    ExternalRefs() = default;
    virtual ~ExternalRefs() = default;

    ExternalRefs(ExternalRefs const& exRef) = default;
    ExternalRefs& operator=(ExternalRefs const& exRef) = default;

//  ExternalRefs(ExternalRefs&& exRef) noexcept = default;
//  ExternalRefs& operator=(ExternalRefs&& exRef) noexcept = default;

    void clear()
    {
      extFileRefs.clear();
      fileIdPair.clear();
      includeVals.clear();
      unraveledVals.clear();
      accessVals.clear();
      fromAccessVals.clear();
    }

    bool addEmptyExtRef(std::string const& fileName)
    {
      auto success=std::get<1>(extFileRefs.emplace(fileName, nullptr));
      return success;
    }

    bool addAccessVal(std::string const& symbol)
    {
      auto success=std::get<1>(accessVals.emplace(symbol));
      return success;
    }

    bool addUnravelVal(std::string const& symbol)
    {
      auto success=std::get<1>(unraveledVals.emplace(symbol));
      return success;
    }

    bool addFromAccessVal(std::string const& fileName, std::string const& symbolSrc, std::string const& symbolDest)
    {
      auto success=std::get<1>(fromAccessVals.emplace(symbolDest, make_pair(symbolSrc, fileName)));
      return success;
    }
  };


  struct SymbolContext
  {
    optional<std::string> fileLoc;
    posInFile contextLoc;
    SymbolContext* parent;
    SymbolMaps symMap;

    // file interactions
    // access -> (file, id)
    // unravel -> id
    // include -> file
    // import = access + unravel

    ExternalRefs extRefs;

    std::vector<std::unique_ptr<SymColorInfo>> colorInformation;
    std::vector<std::unique_ptr<SymbolContext>> subContexts;

    SymbolContext():
      parent(nullptr)
    {
    }

    virtual ~SymbolContext() = default;

    explicit SymbolContext(posInFile loc);
    explicit SymbolContext(posInFile loc, std::string filename);

    SymbolContext(posInFile loc, SymbolContext* contextParent):
      fileLoc(nullopt), contextLoc(std::move(loc)), parent(contextParent)
    {
    }

    template<typename T=SymbolContext, typename=std::enable_if<std::is_base_of<SymbolContext, T>::value>>
    T* newContext(posInFile const& loc)
    {
      subContexts.emplace_back(make_unique<T>(loc, this));
      return static_cast<T*>(subContexts.back().get());
    }

    template<typename T=TypeDec, typename=std::enable_if<std::is_base_of<TypeDec, T>::value>>
    T* newTypeDec(std::string const& tyName, posInFile const& loc)
    {
      auto s=symMap.typeDecs.emplace(tyName, make_unique<T>(loc, tyName));
      auto it=std::get<0>(s);
      auto succ=std::get<1>(s);
      return succ ? static_cast<T*>(it->second.get()) : static_cast<T*>(nullptr);
    }

    SymbolContext(SymbolContext const& symCtx) :
      fileLoc(symCtx.fileLoc), contextLoc(symCtx.contextLoc),
      parent(symCtx.parent), symMap(symCtx.symMap),
      extRefs(symCtx.extRefs)
    {
      for (auto& ctx : symCtx.subContexts)
      {
        subContexts.push_back(make_unique<SymbolContext>(*ctx));
      }

      for (auto& col : symCtx.colorInformation)
      {
        colorInformation.emplace_back(col != nullptr ? col->clone() : nullptr);
      }
    }

    SymbolContext& operator= (SymbolContext const& symCtx)
    {
      fileLoc = symCtx.fileLoc;
      contextLoc = symCtx.contextLoc;
      parent = symCtx.parent;
      symMap = symCtx.symMap;
      extRefs = symCtx.extRefs;

      subContexts.clear();
      for (auto& ctx : symCtx.subContexts)
      {
        subContexts.push_back(make_unique<SymbolContext>(*ctx));
      }

      colorInformation.clear();
      for (auto& col : symCtx.colorInformation)
      {
        colorInformation.emplace_back(col != nullptr ? col->clone() : nullptr);
      }

      return *this;
    }

    SymbolContext(SymbolContext&& symCtx) noexcept :
            fileLoc(std::move(symCtx.fileLoc)), contextLoc(std::move(symCtx.contextLoc)),
            parent(symCtx.parent), symMap(std::move(symCtx.symMap)),
            extRefs(std::move(symCtx.extRefs)),
            colorInformation(std::move(symCtx.colorInformation)), subContexts(std::move(symCtx.subContexts))
    {
    }

    SymbolContext& operator= (SymbolContext&& symCtx) noexcept
    {
      fileLoc = std::move(symCtx.fileLoc);
      contextLoc = std::move(symCtx.contextLoc);
      parent = symCtx.parent;
      symMap = std::move(symCtx.symMap);
      extRefs = std::move(symCtx.extRefs);
      colorInformation = std::move(symCtx.colorInformation);
      subContexts = std::move(symCtx.subContexts);
      return *this;
    }

    // [file, start, end]
    virtual std::pair<optional<fullSymPosRangeInFile>, SymbolContext*> searchSymbol(posInFile const& inputPos);



    // declarations
    optional<posRangeInFile> searchVarDecl(std::string const& symbol)
    {
      return searchVarDecl(symbol, nullopt);
    }
    virtual optional<posRangeInFile> searchVarDecl(std::string const& symbol,
                                                   optional<posInFile> const& position);
    virtual optional<posRangeInFile> searchVarDeclFull(std::string const& symbol,
                                                       optional<posInFile> const& position=nullopt);
    virtual SymbolInfo const* searchVarRaw(std::string const& symbol) const;

    std::list<posRangeInFile> searchFuncDecls(std::string const& symbol);
    virtual std::list<posRangeInFile> searchFuncDecls(
            std::string const& symbol, optional<posInFile> const& position);
    std::list<posRangeInFile> searchFuncDeclsFull(std::string const& symbol,
                                                optional<posInFile> const& position=nullopt);

    // variable signatures
    optional<std::string> searchVarSignatureFull(std::string const& symbol);
    virtual std::list<std::string> searchFuncSignature(std::string const& symbol);
    virtual std::list<std::string> searchFuncSignatureFull(std::string const& symbol);

    optional<std::string> searchLitSignature(SymbolLit const& symbol);
    std::list<std::string> searchLitFuncSignature(SymbolLit const& symbol);

    optional<posRangeInFile> searchLitPosition(
            SymbolLit const& symbol, optional<posInFile> const& position=nullopt);
    std::list<posRangeInFile> searchLitFuncPositions(
            SymbolLit const& symbol, optional<posInFile> const& position=nullopt);

    virtual std::list<ExternalRefs::extRefMap::iterator> getEmptyRefs();

    optional<std::string> getFileName() const;

    SymbolContext* getParent()
    {
      return parent == nullptr ? this : parent->getParent();
    }

    bool addEmptyExtRef(std::string const& fileName)
    {
      return extRefs.addEmptyExtRef(fileName);
    }

    void reset(std::string const& newFile)
    {
      fileLoc = newFile;
      contextLoc = std::make_pair(1,1);
      clear();
    }

    void clear()
    {
      parent = nullptr;
      symMap.clear();
      extRefs.clear();
      clearColorInformation();
      subContexts.clear();
    }

    void clearColorInformation()
    {
      colorInformation.clear();
    }


    void addRGBColor(
            std::tuple<double, double, double> const& c,
            posInFile const& posBegin,
            posInFile const& lastArgs)
    {
      auto const& red=std::get<0>(c);
      auto const& green=std::get<1>(c);
      auto const& blue=std::get<2>(c);
      colorInformation.emplace_back(make_unique<RGBSymColorInfo>(red,green,blue));
      auto const& ptr=colorInformation.back();
      ptr->rangeBegin = posBegin;
      ptr->setLastArgPos(lastArgs);
    }

    void addRGBAColor(
            std::tuple<double, double, double, double> const& c,
            posInFile const& posBegin,
            posInFile const& lastArgs)
    {
      auto const& red=std::get<0>(c);
      auto const& green=std::get<1>(c);
      auto const& blue=std::get<2>(c);
      auto const& alpha=std::get<3>(c);
      colorInformation.emplace_back(make_unique<RGBASymColorInfo>(red,green,blue,alpha));
      auto const& ptr=colorInformation.back();
      ptr->rangeBegin = posBegin;
      ptr->setLastArgPos(lastArgs);
    }

  protected:
    using SymCtxSet = std::unordered_set<SymbolContext*>;

    // search var full

    template<
            typename TArg,
            template<typename...> class TMapTraverse=std::unordered_map,
            template<typename...> class TContainerArg=std::unordered_set
    >
    using SymbolArgContainer = TMapTraverse<std::string, TContainerArg<TArg>>;

    template<
            typename TArg,
            template<typename...> class TMapTraverse=std::unordered_map,
            template<typename...> class TContainerArg=std::unordered_set,
            template<typename...> class TArgContainer=std::unordered_set
    >
    using FnCreateSymbolArgContainer =
    std::function<SymbolArgContainer<TArg, TMapTraverse, TContainerArg>(SymbolContext*, TArgContainer<TArg> const&)>;

    template<typename TArg>
    SymbolArgContainer<TArg> defaultCreateTraverse(std::unordered_set<TArg> const& searchSet)
    {
      SymbolArgContainer<TArg> retVal;
      for (auto const& traverseVal : createTraverseSet())
      {
        retVal.emplace(traverseVal, searchSet);
      }
      return retVal;
    }

    SymbolArgContainer<std::string> fromDeclCreateTraverse(std::unordered_set<std::string> const& symbols)
    {
      // base
      SymbolArgContainer<std::string> base=defaultCreateTraverse(symbols);

      for (auto const& sym : symbols)
      {
        auto aliasSearch = extRefs.fromAccessVals.find(sym);
        if (aliasSearch != extRefs.fromAccessVals.end())
        {
          // there's an alias to dest -> [src, ctx].
          auto const& src=std::get<0>(aliasSearch->second);
          auto const& fileName=std::get<1>(aliasSearch->second);

          auto baseTrav = base.find(fileName);
          if (baseTrav == base.end())
          {
            base.emplace(fileName, std::unordered_set<std::string> { src });
          }
          else
          {
            baseTrav->second.emplace(src);
          }
        }
      }

      return base;
    }

    FnCreateSymbolArgContainer<std::string> const fnFromDeclCreateTrav =
            std::mem_fn(&SymbolContext::fromDeclCreateTraverse);

    template<typename TRet, typename TArg, typename TFn>
    optional<TRet> _searchVarFull(
            TArg init, TFn const& fnLocalPredicate,
            FnCreateSymbolArgContainer<TArg> const& fnCreateTraverse=
                    std::mem_fn(&SymbolContext::defaultCreateTraverse<TArg>))
    {
      std::unordered_set<SymbolContext*> searched;
      std::unordered_set<TArg> initSet { init };
      return _searchVarFull<TRet, TArg, TFn, TFn>(
              searched, initSet, fnLocalPredicate, fnLocalPredicate, fnCreateTraverse);
    }

    template<typename TRet, typename TArg, typename TFn, typename TFn2>
    optional<TRet> _searchVarFull(
            std::unordered_set<SymbolContext*>& searched,
            std::unordered_set<TArg> const& searchArgs,
            TFn const& fnLocalPredicate, TFn2 const& fnLocalPredicateFirst,
            FnCreateSymbolArgContainer<TArg> const& fnCreateTraverse)
    {
      auto p=searched.emplace(getParent());
      auto const& notSearched=std::get<1>(p);
      if (not notSearched)
      {
        // a loop in the search path. Stop now.
        return nullopt;
      }

      // local search first
      for (TArg const& arg : searchArgs)
      {
        optional<TRet> returnVal=fnLocalPredicateFirst(this, arg);
        if (returnVal.has_value())
        {
          return returnVal;
        }
      }

      return searchVarExt<TRet, TArg, TFn>(searched, searchArgs, fnLocalPredicate, fnCreateTraverse);
    }

    template<typename TRet, typename TArg, typename TFn>
    optional<TRet> searchVarExt(
            std::unordered_set<SymbolContext*>& searched,
            std::unordered_set<TArg> const& searchArgs,
            TFn const& fnLocalPredicate,
            FnCreateSymbolArgContainer<TArg> const& fnCreateTraverse)
    {
      using travType = std::pair<std::string, std::unordered_set<TArg>>;
      for (travType const travArg : fnCreateTraverse(this, searchArgs))
      {
        std::string const traverseVal = travArg.first;
        std::unordered_set<TArg> const argSet = travArg.second;
        if (traverseVal == getFileName())
        {
          continue;
        }

        if (SymbolContext* ref=getExternalRef(traverseVal))
        {
          optional<TRet> returnValF = ref->_searchVarFull<TRet, TArg, TFn, TFn>(
                  searched, argSet,
                  fnLocalPredicate, fnLocalPredicate,
                  fnCreateTraverse);
          if (returnValF.has_value())
          {
            return returnValF;
          }
        }
      }
      return nullopt;
    }

    // search all var full

    template<typename TRet, typename TArg, typename TFn>
    std::list<TRet> _searchAllVarFull(
            TArg init,
            TFn const& fnLocalPredicate,
            FnCreateSymbolArgContainer<TArg> const& fnCreateTraverse=
                    std::mem_fn(&SymbolContext::defaultCreateTraverse<TArg>))
    {
      std::unordered_set<SymbolContext*> searched;
      std::unordered_set<TArg> initSet { init };
      return _searchAllVarFull<TRet, TArg, TFn, TFn>(
              searched, initSet, fnLocalPredicate, fnLocalPredicate, fnCreateTraverse);
    }

    template<typename TRet, typename TArg, typename TFn, typename TFn2>
    std::list<TRet> _searchAllVarFull(
            std::unordered_set<SymbolContext*>& searched,
            std::unordered_set<TArg> const& searchArgs,
            TFn const& fnLocalPredicate, TFn2 const& fnLocalPredicateFirst,
            FnCreateSymbolArgContainer<TArg> const& fnCreateTraverse)
    {
      auto p=searched.emplace(getParent());
      auto const& notSearched=std::get<1>(p);
      if (not notSearched)
      {
        // a loop in the search path. Stop now.
        return std::list<TRet>();
      }

      std::list<TRet> returnVal;
      // local search first
      for (TArg const& arg : searchArgs)
      {
        returnVal.splice(returnVal.end(), fnLocalPredicateFirst(this, arg));
      }
      returnVal.splice(returnVal.end(), searchAllVarExt<TRet, TArg, TFn>(
              searched, searchArgs, fnLocalPredicate, fnCreateTraverse));
      return returnVal;
    }

    template<typename TRet, typename TArg, typename TFn>
    std::list<TRet> searchAllVarExt(
            std::unordered_set<SymbolContext*>& searched,
            std::unordered_set<TArg> const& searchArgs,
            TFn const& fnLocalPredicate,
            FnCreateSymbolArgContainer<TArg> const& fnCreateTraverse)
    {
      using travType = std::pair<std::string, std::unordered_set<TArg>>;
      std::list<TRet> finalList;
      for (travType const travArg : fnCreateTraverse(this, searchArgs))
      {
        std::string const traverseVal = travArg.first;
        std::unordered_set<TArg> const argSet = travArg.second;
        if (traverseVal == getFileName())
        {
          continue;
        }

        if (SymbolContext* ref=getExternalRef(traverseVal))
        {
          auto returnValF=ref->_searchAllVarFull<TRet, TArg, TFn, TFn>(
                  searched, argSet,
                  fnLocalPredicate, fnLocalPredicate,
                  fnCreateTraverse);

          finalList.splice(finalList.end(), std::move(returnValF));
        }
      }
      return finalList;
    }

    virtual optional<SymbolContext*> searchStructContext(std::string const& tyVal) const;
    SymbolContext* searchStructCtxFull(std::string const&);

    optional<SymbolContext*> searchAccessDecls(std::string const&);
    virtual std::pair<SymbolContext*, bool> searchLitContext(SymbolLit const& symbol);

    virtual std::unordered_set<std::string> createTraverseSet();

    virtual SymbolContext* getExternalRef(std::string const&);
    std::list<std::string> searchFuncUnravelStruct(std::string const& symbol);

    SymbolInfo* searchVarUnravelStructRaw(std::string const& symbol);
    SymbolInfo* searchVarUnravelStructRaw(std::string const& symbol, optional<posInFile> const& position);

    void addPlainFile();
  };

  struct AddDeclContexts: SymbolContext
  {
    unordered_map <std::string, SymbolInfo> additionalDecs;
    AddDeclContexts(): SymbolContext() {}

    explicit AddDeclContexts(posInFile loc):
      SymbolContext(loc) {}

    AddDeclContexts(posInFile loc, SymbolContext* contextParent):
      SymbolContext(loc, contextParent) {}

    ~AddDeclContexts() override = default;
    optional<posRangeInFile> searchVarDecl(std::string const& symbol, optional<posInFile> const& position) override;
    SymbolInfo const* searchVarRaw(std::string const& symbol) const override;
  };
}
