/******************************************************************************
 *
 * Copyright (C) 1997-2026 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 <mutex>
#include <fstream>

#include "mermaid.h"
#include "util.h"
#include "portable.h"
#include "config.h"
#include "doxygen.h"
#include "message.h"
#include "debug.h"
#include "dir.h"
#include "indexlist.h"
#include "threadpool.h"

static std::mutex g_mermaidMutex;
static int        g_mermaidIndex = 1;

MermaidManager &MermaidManager::instance()
{
  static MermaidManager theInstance;
  return theInstance;
}

MermaidManager::MermaidManager()
{
}

DString MermaidManager::imageExtension(ImageFormat format)
{
  switch (format)
  {
    case ImageFormat::PNG: return "png";
    case ImageFormat::SVG: return "svg";
    case ImageFormat::PDF: return "pdf";
  }
  return "png";
}

MermaidManager::ImageFormat MermaidManager::convertToImageFormat(OutputFormat outputFormat)
{
  switch(outputFormat)
  {
    case OutputFormat::HTML:
      // fall through
    case OutputFormat::RTF:
      // fall through
    case OutputFormat::Docbook:
      return getDotImageExtension()=="svg" ? ImageFormat::SVG : ImageFormat::PNG;
    case OutputFormat::LaTeX:
      return Config_getBool(USE_PDFLATEX) ? ImageFormat::PDF : ImageFormat::PNG;
  }
  return ImageFormat::PNG;
}

DString MermaidManager::writeMermaidSource(const DString &outDirArg, const DString &fileName,
                                            const DString &content, ImageFormat imageFormat,
                                            const DString &srcFile, int srcLine)
{
  DString outDir(outDirArg);
  DString baseName;

  // strip any trailing slashes and backslashes
  while (!outDir.empty() && (outDir.at(outDir.length()-1)=='/' || outDir.at(outDir.length()-1)=='\\'))
  {
    outDir = outDir.left(outDir.length()-1);
  }

  if (fileName.empty())
  {
    std::lock_guard<std::mutex> lock(g_mermaidMutex);
    baseName = outDir + "/inline_mermaid_" + DString().setNum(g_mermaidIndex++);
  }
  else
  {
    baseName = fileName;
    if (size_t i = baseName.rfind('.'); i!=DString::npos) baseName = baseName.left(i);
    baseName.prepend(outDir + "/");
  }

  DString mmdName = baseName + ".mmd";

  Debug::print(Debug::Mermaid, 0, "*** writeMermaidSource baseName: {}\n", baseName);
  Debug::print(Debug::Mermaid, 0, "*** writeMermaidSource mmdName: {}\n", mmdName);

  // Write the .mmd source file
  std::ofstream file = Portable::openOutputStream(mmdName);
  if (!file.is_open())
  {
    err_full(srcFile, srcLine, "Could not open file {} for writing", mmdName);
    return baseName;
  }
  file.write(content.data(), content.length());
  file.close();

  // Store for batch processing in run()
  m_diagrams.emplace_back(imageFormat,MermaidDiagramInfo(baseName, content, outDir, srcFile, srcLine));

  return baseName;
}

void MermaidManager::generateMermaidOutput(const DString &baseName, const DString &/*outDir*/,
                                           ImageFormat imageFormat, bool toIndex)
{
  if (!toIndex) return;
  DString imgName = baseName;
  if (size_t i = imgName.rfind('/'); i!=DString::npos)
  {
    imgName = imgName.mid(i + 1);
  }
  imgName += "." + imageExtension(imageFormat);
  Doxygen::indexList->addImageFile(imgName);
}

static void runMermaid(const MermaidManager::DiagramList &diagrams)
{
  //printf("runMermaidContent for %zu images\n",contentList.size());
  if (diagrams.empty()) return;

  DString mmdc = Config_getString(MERMAID_PATH);
  if (!mmdc.empty() && mmdc.at(mmdc.length()-1) != '/' && mmdc.at(mmdc.length()-1) != '\\')
  {
    mmdc += "/";
  }
  mmdc += "mmdc";

  DString mermaidConfigFile = Config_getString(MERMAID_CONFIG_FILE);

  struct MermaidCmd
  {
    MermaidCmd(const DString &mmdc_,const DString &args_,const DString &ext_,const DString &srcFile_,int srcLine_) :
      mmdc(mmdc_), args(args_), ext(ext_), srcFile(srcFile_), srcLine(srcLine_) {}
    DString mmdc;
    DString args;
    DString ext;
    DString srcFile;
    int srcLine;
  };
  std::vector<MermaidCmd> mermaidCmds;

  for (const auto &diagram : diagrams)
  {
    //printf("content=%s\n",qPrint(mc.content));
    if (diagram.info.content.empty()) continue;

    DString ext = MermaidManager::imageExtension(diagram.imageFormat);

    DString inputFile  = diagram.info.baseName + ".mmd";
    DString outputFile = diagram.info.baseName + "." + ext;

    // Check if content has changed since last run (caching)
    FileInfo fi(outputFile.str());
    if (fi.exists())
    {
      DString cachedContent = fileToString(inputFile);
      if (cachedContent == diagram.info.content)
      {
        continue;
      }
    }

    // Build the mmdc command arguments
    DString args;
    args += "-q -i \"" + inputFile + "\" ";
    args += "-o \"" + outputFile + "\" ";

    if (!mermaidConfigFile.empty())
    {
      args += "-c \"" + mermaidConfigFile + "\" ";
    }

    mermaidCmds.emplace_back(mmdc, args,ext, diagram.info.srcFile, diagram.info.srcLine);
  }

  std::size_t numThreads = static_cast<std::size_t>(Config_getInt(DOT_NUM_THREADS));
  size_t offset=0;
  size_t total=mermaidCmds.size();
  msg("Generating {} Mermaid files using {} threads\n", total, numThreads);
  if (numThreads>1) // multi threaded version
  {
    ThreadPool threadPool(numThreads);
    std::vector< std::future<int> > results;

    // queue the work
    for (const auto &cmd : mermaidCmds)
    {
      auto processFile = [&cmd]()
      {
        Debug::print(Debug::Mermaid, 0, "*** MermaidManager::run Running: {} {}\n", cmd.mmdc, cmd.args);
        int exitCode = Portable::system(cmd.mmdc.data(), cmd.args.data(), true);
        if (exitCode != 0)
        {
          err_full(cmd.srcFile, cmd.srcLine,
              "Problems running Mermaid (mmdc). Verify that the command '{} {}' works from the command line. Exit code: {}.",
              cmd.mmdc, cmd.args, exitCode);
        }
        return exitCode;
      };
      results.emplace_back(threadPool.queue(processFile));
    }

    // wait for the results
    for (auto &f : results)
    {
      offset++;
      msg("Generating Mermaid file {}/{}\n", offset, total);
      f.get();
    }
  }
  else // single threaded version
  {
    for (const auto &cmd : mermaidCmds)
    {
      offset++;
      msg("Generating Mermaid file {}/{}\n", offset, total);
      Debug::print(Debug::Mermaid, 0, "*** MermaidManager::run Running: {} {}\n", cmd.mmdc, cmd.args);

      int exitCode = Portable::system(cmd.mmdc.data(), cmd.args.data(), true);
      if (exitCode != 0)
      {
        err_full(cmd.srcFile, cmd.srcLine,
            "Problems running Mermaid (mmdc). Verify that the command '{} {}' works from the command line. Exit code: {}.",
            cmd.mmdc, cmd.args, exitCode);
      }
    }
  }
}

void MermaidManager::setHasInlineDiagrams()
{
  // CLI mode creates images locally, other modes create inline diagram descriptions
  // in the HTML output and rely on rendering them in the browser.
  m_hasInlineDiagrams=Config_getEnum(MERMAID_RENDER_MODE)!=MERMAID_RENDER_MODE_t::CLI;
}

void MermaidManager::run()
{
  Debug::print(Debug::Mermaid, 0, "*** MermaidManager::run\n");
  runMermaid(m_diagrams);
}
