/******************************************************************************
 *
 * Copyright (C) 1997-2021 by Dimitri van Heesch.
 *
 * Permission to use, copy, modify, and distribute this software and its
 * documentation under the terms of the GNU General Public License is hereby
 * granted. No representations are made about the suitability of this software
 * for any purpose. It is provided "as is" without express or implied warranty.
 * See the GNU General Public License for more details.
 *
 * Documents produced by analyze are derivative works derived from the
 * input used in their production; they are not affected by this license.
 *
 */

#ifndef UTIL_H
#define UTIL_H

/*! \file
 *  \brief A bunch of utility functions.
 */

#include <memory>
#include <unordered_map>
#include <algorithm>
#include <functional>
#include <fstream>
#include <variant>
#include <string_view>

#include <ctype.h>
#include "types.h"
#include "docparser.h"
#include "containers.h"
#include "outputgen.h"
#include "regex.h"
#include "conceptdef.h"
#include "construct.h"

//--------------------------------------------------------------------

class ClassDef;
class FileDef;
class MemberList;
class NamespaceDef;
class FileNameLinkedMap;
class ArgumentList;
class OutputList;
class OutputDocInterface;
class MemberDef;
class GroupDef;
struct TagInfo;
class PageDef;
class SectionInfo;
class Definition;
class BufStr;
class FileInfo;
class Dir;

//--------------------------------------------------------------------

/** Abstract interface for a hyperlinked text fragment. */
class TextGeneratorIntf
{
  public:
    ABSTRACT_BASE_CLASS(TextGeneratorIntf)

    virtual void writeString(std::string_view,bool) const = 0;
    virtual void writeBreak(int indent) const = 0;
    virtual void writeLink(const OGString &extRef,const OGString &file,
                      const OGString &anchor,std::string_view text
                     ) const = 0;
};

/** Implements TextGeneratorIntf for an OutputDocInterface stream. */
class TextGeneratorOLImpl : public TextGeneratorIntf
{
  public:
    TextGeneratorOLImpl(OutputList &ol);
    void writeString(std::string_view s,bool keepSpaces) const override;
    void writeBreak(int indent) const override;
    void writeLink(const OGString &extRef,const OGString &file,
                   const OGString &anchor,std::string_view text
                  ) const override;
  private:
    OutputList &m_ol;
};

//--------------------------------------------------------------------

OGString langToString(SrcLangExt lang);
OGString getLanguageSpecificSeparator(SrcLangExt lang,bool classScope=FALSE);

//--------------------------------------------------------------------

void linkifyText(const TextGeneratorIntf &ol,
                 const Definition *scope,
                 const FileDef *fileScope,
                 const Definition *self,
                 const OGString &text,
                 bool autoBreak=FALSE,
                 bool external=TRUE,
                 bool keepSpaces=FALSE,
                 int indentLevel=0
                );

OGString fileToString(const OGString &name,bool filter=FALSE,bool isSourceCode=FALSE);

struct GetDefInput
{
  GetDefInput(const OGString &scName,const OGString &memName,const OGString &a) :
    scopeName(scName),memberName(memName),args(a) {}
  OGString scopeName;
  OGString memberName;
  OGString args;
  bool forceEmptyScope = false;
  const FileDef *currentFile = nullptr;
  bool checkCV = false;
  bool insideCode = false;
};

struct GetDefResult
{
  bool found = false;
  const MemberDef    *md=nullptr;
  const ClassDef     *cd=nullptr;
  const FileDef      *fd=nullptr;
  const NamespaceDef *nd=nullptr;
  const GroupDef     *gd=nullptr;
  const ConceptDef   *cnd=nullptr;
  const ModuleDef    *modd=nullptr;
};

GetDefResult getDefs(const GetDefInput &input);

OGString getFileFilter(const OGString &name,bool isSourceCode);

bool resolveRef(/* in */  const OGString &scName,
                /* in */  const OGString &name,
                /* in */  bool inSeeBlock,
                /* out */ const Definition **resContext,
                /* out */ const MemberDef  **resMember,
                /* in */  SrcLangExt lang,
                /* in */  bool lookForSpecializations = TRUE,
                /* in */  const FileDef *currentFile = nullptr,
                /* in */  bool checkScope = FALSE
               );

bool resolveLink(/* in */  const OGString &scName,
                 /* in */  const OGString &lr,
                 /* in */  bool inSeeBlock,
                 /* out */ const Definition **resContext,
                 /* out */ OGString &resAnchor,
                 /* in */  SrcLangExt lang,
                 /* in */  const OGString &prefix=OGString()
                );

void generateFileRef(OutputList &ol,const OGString &,
                             const OGString &linkTxt=OGString());

void writePageRef(OutputList &ol,const OGString &cn,const OGString &mn);

//OGString getCanonicalTemplateSpec(const Definition *d,const FileDef *fs,const OGString& spec);

bool matchArguments2(const Definition *srcScope,const FileDef *srcFileScope,const ArgumentList *srcAl,
                     const Definition *dstScope,const FileDef *dstFileScope,const ArgumentList *dstAl,
                     bool checkCV,SrcLangExt lang
                    );

void mergeArguments(ArgumentList &,ArgumentList &,bool forceNameOverwrite=FALSE);

bool matchTemplateArguments(const ArgumentList &srcAl,const ArgumentList &dstAl);

OGString substituteClassNames(const OGString &s);

struct SelectionBlock
{
  const char *name;
  bool enabled;
};

using SelectionBlockList = std::vector<SelectionBlock>;

struct SelectionMarkerInfo
{
  char        markerChar;
  const char *beginStr;
  size_t      beginLen;
  const char *endStr;
  size_t      endLen;
  const char *closeStr;
  size_t      closeLen;
};

OGString selectBlocks(const OGString& s,const SelectionBlockList &blockList, const SelectionMarkerInfo &markerInfo);
void checkBlocks(const OGString& s,const OGString fileName, const SelectionMarkerInfo &markerInfo);

OGString removeEmptyLines(const OGString &s);


FileDef *findFileDef(const FileNameLinkedMap *fnMap, const OGString &n, bool &ambig);
OGString findFilePath(const OGString &file, bool &ambig);

OGString showFileDefMatches(const FileNameLinkedMap *fnMap,const OGString &n);

EntryType guessSection(const OGString &name);

inline bool isId(int c)
{
  return c=='_' || c>=128 || c<0 || isalnum(c) || c=='$';
}
inline bool isIdJS(int c)
{
  return c>=128 || c<0 || isalnum(c);
}

OGString removeRedundantWhiteSpace(const OGString &s);

OGString inlineArgListToDoc(const ArgumentList &al);
OGString inlineTemplateArgListToDoc(const ArgumentList &al);

OGString argListToString(const ArgumentList &al,bool useCanonicalType=FALSE,bool showDefVals=TRUE);

OGString tempArgListToString(const ArgumentList &al,SrcLangExt lang,bool includeDefaults=true);

OGString generateMarker(int id);

void writeExamples(OutputList &ol,const ExampleList &el);

OGString stripAnonymousNamespaceScope(const OGString &s);

OGString stripFromPath(const OGString &path);

OGString stripFromIncludePath(const OGString &path);

bool rightScopeMatch(const OGString &scope, const OGString &name);

bool leftScopeMatch(const OGString &scope, const OGString &name);

struct KeywordSubstitution
{
  const char *keyword;
  using GetValue          = std::function<OGString()>;
  using GetValueWithParam = std::function<OGString(const OGString &)>;
  std::variant<GetValue,GetValueWithParam> getValueVariant;
};

using KeywordSubstitutionList = std::vector<KeywordSubstitution>;

OGString substituteKeywords(const OGString &file,const OGString &s,const KeywordSubstitutionList &keywords);

OGString substituteKeywords(const OGString &file,const OGString &s,const OGString &title,
         const OGString &projName,const OGString &projNum,const OGString &projBrief);

int getPrefixIndex(const OGString &name);

OGString removeAnonymousScopes(const OGString &s);

OGString replaceAnonymousScopes(const OGString &s,const OGString &replacement=OGString());

OGString convertNameToFile(const OGString &name,bool allowDots=FALSE,bool allowUnderscore=FALSE);

OGString generateAnonymousAnchor(const OGString &fileName,int count);

void extractNamespaceName(const OGString &scopeName,
                          OGString &className,OGString &namespaceName,
                          bool allowEmptyClass=FALSE);

OGString insertTemplateSpecifierInScope(const OGString &scope,const OGString &templ);

OGString stripScope(const OGString &name);

OGString convertToId(const OGString &s);
OGString correctId(const OGString &s);

OGString convertToHtml(const OGString &s,bool keepEntities=true);

OGString convertToXML(const OGString &s, bool keepEntities=false);

OGString convertToJSString(const OGString &s,bool keepEntities=false);

OGString getOverloadDocs();

void addMembersToMemberGroup(/* in,out */ MemberList *ml,
                             /* in,out */ MemberGroupList *pMemberGroups,
                             /* in */     const Definition *context);

int extractClassNameFromType(const OGString &type,int &pos,
                              OGString &name,OGString &templSpec,SrcLangExt=SrcLangExt::Unknown);

OGString normalizeNonTemplateArgumentsInString(
       const OGString &name,
       const Definition *context,
       const ArgumentList &formalArgs);

OGString substituteTemplateArgumentsInString(
       const OGString &name,
       const ArgumentList &formalArgs,
       const ArgumentList *actualArgs);

OGString stripTemplateSpecifiersFromScope(const OGString &fullName,
                                          bool parentOnly=TRUE,
                                          OGString *lastScopeStripped=nullptr,
                                          OGString scopeName=OGString(),
                                          bool allowArtificial=true);

OGString resolveTypeDef(const Definition *d,const OGString &name,
                        const Definition **typedefContext=nullptr);

OGString mergeScopes(const OGString &leftScope,const OGString &rightScope);

int getScopeFragment(const OGString &s,int p,int *l);

void addRefItem(const RefItemVector &sli,
                const OGString &key,
                const OGString &prefix,
                const OGString &name,
                const OGString &title,
                const OGString &args,
                const Definition *scope);

PageDef *addRelatedPage(const OGString &name,
                        const OGString &ptitle,
                        const OGString &doc,
                        const OGString &fileName,
                        int docLine,
                        int startLine,
                        const RefItemVector &sli = RefItemVector(),
                        GroupDef *gd=nullptr,
                        const TagInfo *tagInfo=nullptr,
                        bool xref=FALSE,
                        SrcLangExt lang=SrcLangExt::Unknown
                       );

bool getCaseSenseNames();

OGString escapeCharsInString(const OGString &name,bool allowDots,bool allowUnderscore=FALSE);
OGString unescapeCharsInString(const OGString &s);

void addGroupListToTitle(OutputList &ol,const Definition *d);

OGString linkToText(SrcLangExt lang,const OGString &link,bool isFileName);

bool checkExtension(const OGString &fName, const OGString &ext);

void addHtmlExtensionIfMissing(OGString &fName);

OGString stripExtensionGeneral(const OGString &fName, const OGString &ext);

OGString stripExtension(const OGString &fName);

//void replaceNamespaceAliases(OGString &scope,size_t i);

int computeQualifiedIndex(const OGString &name);

void addDirPrefix(OGString &fileName);

OGString relativePathToRoot(const OGString &name);
OGString determineAbsoluteIncludeName(const OGString &curFile,const OGString &incFileName);

void createSubDirs(const Dir &d);
void clearSubDirs(const Dir &d);

OGString removeLongPathMarker(OGString path);
OGString stripPath(const OGString &s);

bool containsWord(const OGString &s,const char *word);

bool findAndRemoveWord(OGString &s,const char *word);

OGString stripLeadingAndTrailingEmptyLines(const OGString &s,int &docLine);

bool updateLanguageMapping(const OGString &extension,const OGString &parser);
SrcLangExt getLanguageFromFileName(const OGString& fileName, SrcLangExt defLang=SrcLangExt::Cpp);
SrcLangExt getLanguageFromCodeLang(OGString &fileName);
OGString getFileNameExtension(const OGString &fn);
void initDefaultExtensionMapping();
void addCodeOnlyMappings();

bool checkIfTypedef(const Definition *scope,const FileDef *fileScope,const OGString &n);

OGString parseCommentAsText(const Definition *scope,const MemberDef *member,const OGString &doc,const OGString &fileName,int lineNr);
OGString parseCommentAsHtml(const Definition *scope,const MemberDef *member,const OGString &doc,const OGString &fileName,int lineNr);

bool transcodeCharacterStringToUTF8(std::string &input,const char *inputEncoding);

OGString recodeString(const OGString &str,const char *fromEncoding,const char *toEncoding);

void writeTypeConstraints(OutputList &ol,const Definition *d,const ArgumentList &al);

OGString convertCharEntitiesToUTF8(const OGString &s);

void stackTrace();

bool readInputFile(const OGString &fileName,std::string &contents,
                   bool filter=TRUE,bool isSourceCode=FALSE);
OGString filterTitle(const OGString &title);

bool patternMatch(const FileInfo &fi,const StringVector &patList);

OGString externalLinkTarget(const bool parent = false);
OGString createHtmlUrl(const OGString &relPath,
                       const OGString &ref,
                       bool href,
                       bool islocalFile,
                       const OGString &targetFileName,
                       const OGString &anchor);
OGString externalRef(const OGString &relPath,const OGString &ref,bool href);
int nextUtf8CharPosition(const OGString &utf8Str,uint32_t len,uint32_t startPos);

void writeMarkerList(OutputList &ol,const std::string &markerText,size_t numMarkers,
                     std::function<void(size_t)> replaceFunc);

/** Data associated with a HSV colored image. */
struct ColoredImgDataItem
{
  const char *name;
  unsigned short width;
  unsigned short height;
  const unsigned char *content;
  const unsigned char *alpha;
};

void writeColoredImgData(const OGString &dir,ColoredImgDataItem data[]);
OGString replaceColorMarkers(const OGString &str);

bool copyFile(const OGString &src,const OGString &dest);

int lineBlock(const OGString &text,const OGString &marker);

bool isURL(const OGString &url);

OGString correctURL(const OGString &url,const OGString &relPath);

OGString processMarkup(const OGString &s);

bool protectionLevelVisible(Protection prot);

OGString stripIndentation(const OGString &s,bool skipFirstLine=false);
void stripIndentationVerbatim(OGString &doc,const int indentationLevel);

OGString getDotImageExtension();

bool fileVisibleInIndex(const FileDef *fd,bool &genSourceFile);

OGString extractDirection(OGString &docs);

void convertProtectionLevel(
                   MemberListType inListType,
                   Protection inProt,
                   MemberListType *outListType1,
                   MemberListType *outListType2
                  );

bool mainPageHasTitle();
bool openOutputFile(const OGString &outFile,std::ofstream &f);

StringVector split(const std::string &s,const std::string &delimiter);
StringVector split(const std::string &s,const reg::Ex &delimiter);
int findIndex(const StringVector &sv,const std::string &s);
int findIndex(const std::string &s,const reg::Ex &re);
std::string join(const StringVector &s,const std::string &delimiter);

bool recognizeFixedForm(const OGString &contents, FortranFormat format);
FortranFormat convertFileNameFortranParserCode(OGString fn);

OGString integerToAlpha(int n, bool upper=true);
OGString integerToRoman(int n, bool upper=true);

OGString getEncoding(const FileInfo &fi);

inline OGString fixSpaces(const OGString &s) { return substitute(s," ","&#160;"); }

OGString detab(const OGString &s,size_t &refIndent);

OGString getProjectId();
OGString projectLogoFile();

void mergeMemberOverrideOptions(MemberDefMutable *md1,MemberDefMutable *md2);

size_t updateColumnCount(const char *s,size_t col);

OGString mangleCSharpGenericName(const OGString &name);
OGString demangleCSharpGenericName(const OGString &name,const OGString &templArgs);

#endif
