/******************************************************************************
 *
 * Copyright (C) 1997-2020 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 Doxygen are derivative works derived from the
 * input used in their production; they are not affected by this license.
 *
 */

#include <map>
#include <memory>
#include <unordered_map>
#include <unordered_set>
#include <string>
#include <mutex>

#include "tooltip.h"
#include "definition.h"
#include "outputlist.h"
#include "util.h"
#include "filedef.h"
#include "doxygen.h"
#include "config.h"

static std::mutex                                                g_tooltipsFileMutex;
static std::mutex                                                g_tooltipsTipMutex;
static std::unordered_map<int, std::unordered_set<std::string> > g_tooltipsWrittenPerFile;

class TooltipManager::Private
{
  public:
    std::map<std::string,const Definition*> tooltipInfo;
};

TooltipManager::TooltipManager() : p(std::make_unique<Private>())
{
}

TooltipManager::~TooltipManager()
{
}

static DString escapeId(const DString &s)
{
  DString res=s;
  for (size_t i=0;i<res.length();i++) if (!isId(res[i])) res[i]='_';
  return res;
}

void TooltipManager::addTooltip(const Definition *d)
{
  bool sourceTooltips = Config_getBool(SOURCE_TOOLTIPS);
  if (!sourceTooltips) return;

  DString id = d->getOutputFileBase();
  if (size_t i=id.rfind('/'); i!=DString::npos)
  {
    id = id.mid(i+1); // strip path (for CREATE_SUBDIRS=YES)
  }
  // In case an extension is present translate this extension to something understood by the tooltip handler
  // otherwise extend t with a translated htmlFileExtension.
  DString currentExtension = getFileNameExtension(id);
  if (currentExtension.empty())
  {
    id += escapeId(Doxygen::htmlFileExtension);
  }
  else
  {
    id = stripExtensionGeneral(id,currentExtension) + escapeId(currentExtension);
  }

  DString anc = d->anchor();
  if (!anc.empty())
  {
    id+="_"+anc;
  }
  id = "a" + id;
  p->tooltipInfo.emplace(id.str(),d);
  //printf("%p: addTooltip(%s)\n",this,id.data());
}

void TooltipManager::writeTooltips(OutputCodeList &ol)
{
  std::unordered_map<int, std::unordered_set<std::string> >::iterator it;
  // critical section
  {
    std::lock_guard<std::mutex> lock(g_tooltipsFileMutex);

    int id = ol.id();
    it = g_tooltipsWrittenPerFile.find(id);
    if (it==g_tooltipsWrittenPerFile.end()) // new file
    {
      it = g_tooltipsWrittenPerFile.emplace(id,std::unordered_set<std::string>()).first;
    }
  }

  for (const auto &[name,d] : p->tooltipInfo)
  {
    bool written = false;

    // critical section
    {
      std::lock_guard<std::mutex> lock(g_tooltipsTipMutex);
      written = it->second.find(name)!=it->second.end();
      if (!written) // only write tooltips once
      {
        it->second.insert(name); // remember we wrote this tooltip for the given file id
      }
    }

    if (!written)
    {
      //printf("%p: writeTooltips(%s) ol=%d\n",this,qPrint(name),ol.id());
      DocLinkInfo docInfo;
      docInfo.name   = d->qualifiedName();
      docInfo.ref    = d->getReference();
      docInfo.url    = d->getOutputFileBase();
      docInfo.anchor = d->anchor();
      SourceLinkInfo defInfo;
      if (d->getBodyDef() && d->getStartBodyLine()!=-1)
      {
        defInfo.file    = d->getBodyDef()->name();
        defInfo.line    = d->getStartBodyLine();
        defInfo.url     = d->getSourceFileBase();
        defInfo.anchor  = d->getSourceAnchor();
      }
      SourceLinkInfo declInfo; // TODO: fill in...
      DString decl;
      if (d->definitionType()==Definition::TypeMember)
      {
        const MemberDef *md = toMemberDef(d);
        if (!md->isAnonymous())
        {
          decl = md->declaration();
        }
      }
      ol.writeTooltip(name,                // id
          docInfo,                         // symName
          decl,                            // decl
          d->briefDescriptionAsTooltip(),  // desc
          defInfo,
          declInfo
          );
    }
  }
}

