/* This file is part of Zutty.
 * Copyright (C) 2020 Tom Szilagyi
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * See the file LICENSE for the full license.
 */

#include "fontpack.h"
#include "log.h"

#include <ftw.h>
//#include <stdio.h> // DEBUG
#include <string.h>
#include <strings.h>

namespace
{
   struct SearchState
   {
      // input
      const char* fontname = nullptr;
      size_t fontnamelen = 0;

      // output
      int level = 0;
      std::string ext;
      std::string regular;
      std::string bold;
      std::string italic;
      std::string boldItalic;
   };
   SearchState sstate;

   int
   saveCandidate (const char* fpath, const char* ext, int level,
                  const char* variant, std::string& dest)
   {
      logT << variant << ": " << fpath << std::endl;
      if (sstate.ext != "" && sstate.ext != ext)
      {
         logT << "Rejecting candidate because its extension: '" << ext
              << "' does not match the other(s): '" << sstate.ext << "'"
              << std::endl;
         return 1;
      }
      dest = fpath;
      sstate.ext = ext;
      sstate.level = level;
      return 0;
   }

   int
   fontFileFilter (const char* fpath, const struct stat* sb,
                   int tflag, struct FTW* ftwbuf)
   {
      // If we have just emerged from a directory where fonts were found,
      // time to call it a day.
      if (tflag == FTW_D &&
          sstate.level > 0 && ftwbuf->level == sstate.level - 1 &&
          sstate.regular.size () > 0)
         return 1;

      // In a less ideal case, where some candidates were found in this dir,
      // but no regular variant, clear partial results and proceed.
      if (tflag == FTW_D && sstate.level > 0)
      {
         logT << "Some candidates found but no regular variant; continuing"
              << std::endl;
         sstate.level = 0;
         sstate.ext = "";
         sstate.regular = "";
         sstate.bold = "";
         sstate.italic = "";
         sstate.boldItalic = "";
         return 0;
      }

      // Filter by file type - only regular files and symlinks
      if (tflag != FTW_F && tflag != FTW_SL)
         return 0;

      // Filter by extension
      const char* fname = fpath + ftwbuf->base;
      const char* ext = strrchr (fname, '.');
      if (!ext)
         return 0;
      if (strcasecmp (ext, ".gz") == 0 && ext > fname)
         do
            --ext;
         while (ext > fname && ext [0] != '.');

      if (strcasecmp (ext, ".ttc") != 0 &&
          strcasecmp (ext, ".ttf") != 0 &&
          strcasecmp (ext, ".otf") != 0 &&
          strcasecmp (ext, ".pcf") != 0 &&
          strcasecmp (ext, ".pcf.gz") != 0)
         return 0;

      // Filter by font name
      if (strncasecmp (fname, sstate.fontname, sstate.fontnamelen) != 0)
         return 0;

      // At this point we only need to consider the mid part between font name
      // and extension. This is either matched to one of the face variants,
      // or we reject the file.
      const char* mid = fname + sstate.fontnamelen;
      size_t midlen = ext - mid;

      // Discard optional hyphen, underscore or space between name and midpart
      if (midlen > 0 &&
          (mid [0] == '-' || mid [0] == '_' || mid [0] == ' '))
      {
         ++mid;
         --midlen;
      }

      // Apply some simple heuristics to identify font variants
      if (midlen == 0 ||
          strncasecmp (mid, "R", midlen) == 0 ||
          strncasecmp (mid, "Regular", midlen) == 0)
      {
         if (saveCandidate (fpath, ext, ftwbuf->level,
                            "Regular", sstate.regular))
            return 0;
      }
      else if (strncasecmp (mid, "B", midlen) == 0 ||
               strncasecmp (mid, "Bold", midlen) == 0)
      {
         if (saveCandidate (fpath, ext, ftwbuf->level,
                            "Bold", sstate.bold))
            return 0;
      }
      else if (strncasecmp (mid, "I", midlen) == 0 ||
               strncasecmp (mid, "It", midlen) == 0 ||
               strncasecmp (mid, "Italic", midlen) == 0 ||
               strncasecmp (mid, "O", midlen) == 0 ||
               strncasecmp (mid, "Ob", midlen) == 0 ||
               strncasecmp (mid, "Oblique", midlen) == 0)
      {
         if (saveCandidate (fpath, ext, ftwbuf->level,
                            "Italic", sstate.italic))
            return 0;
      }
      else if (strncasecmp (mid, "BI", midlen) == 0 ||
               strncasecmp (mid, "BoldIt", midlen) == 0 ||
               strncasecmp (mid, "BoldItalic", midlen) == 0)
      {
         if (saveCandidate (fpath, ext, ftwbuf->level,
                            "BoldItalic", sstate.boldItalic))
            return 0;
      }

   #if 0
      // TODO remove the below printouts along with the include of stdio.h
      printf("%-3s %2d ",
             (tflag == FTW_D) ?   "d"   : (tflag == FTW_DNR) ? "dnr" :
             (tflag == FTW_DP) ?  "dp"  : (tflag == FTW_F) ?   "f" :
             (tflag == FTW_NS) ?  "ns"  : (tflag == FTW_SL) ?  "sl" :
             (tflag == FTW_SLN) ? "sln" : "???",
             ftwbuf->level);

      if (tflag == FTW_NS)
         printf("-------");
      else
         printf("%7jd", (intmax_t) sb->st_size);

      printf("   %-40s %d %s\n",
             fpath, ftwbuf->base, fpath + ftwbuf->base);
   #endif
      return 0;
   }

} // namespace

namespace zutty
{
   Fontpack::Fontpack (const std::string& fontpath,
                       const std::string& fontname,
                       const std::string& dwfontname)
   {
      logT << "Fontpack: fontpath=" << fontpath
           << "; fontname=" << fontname
           << "; dwfontname=" << dwfontname
           << std::endl;

      // Look for & initialize the regular font (with variants)

      sstate.fontname = fontname.data ();
      sstate.fontnamelen = fontname.size ();

      size_t pos = 0;
      size_t nextpos = 0;
      do
      {
         nextpos = fontpath.find (':', pos);
         size_t len = (nextpos == std::string::npos)
                    ? std::string::npos
                    : nextpos - pos;

         std::string fontpath1 = fontpath.substr (pos, len);
         logT << "Looking for candidates under " << fontpath1 << std::endl;
         pos = nextpos + 1;

         int flags = FTW_DEPTH;
         if (nftw (fontpath1.c_str (), fontFileFilter, 32, flags) == -1)
         {
            SYS_WARN ("Cannot walk file tree at ", fontpath1);
         }

      } while (!sstate.regular.size () && nextpos != std::string::npos);

      if (! sstate.regular.size ())
      {
         logE << "No Regular variant of the requested font '" << fontname
              << "' could be identified." << std::endl;
         throw std::runtime_error (std::string ("No suitable files for '") +
                                   fontname + "' found!");
      }

      fontRegular = std::make_unique <Font> (sstate.regular);
      px = fontRegular->getPx ();
      py = fontRegular->getPy ();

      try
      {
         if (sstate.bold.size ())
            fontBold = std::make_unique <Font> (
               sstate.bold, * fontRegular.get (), Font::Overlay);
      }
      catch (const std::runtime_error& e)
      {
         fontBold = nullptr;
         logW << "Failed to load bold variant: " << e.what () << std::endl;
      }

      try
      {
         if (sstate.italic.size ())
            fontItalic = std::make_unique <Font> (
               sstate.italic, * fontRegular.get (), Font::Overlay);
      }
      catch (const std::runtime_error& e)
      {
         fontItalic = nullptr;
         logW << "Failed to load italic variant: " << e.what () << std::endl;
      }

      try
      {
         if (sstate.boldItalic.size ())
            fontBoldItalic = std::make_unique <Font> (
               sstate.boldItalic, * fontRegular.get (), Font::Overlay);
      }
      catch (const std::runtime_error& e)
      {
         fontBoldItalic = nullptr;
         logW << "Failed to load boldItalic variant: " << e.what () << std::endl;
      }

      // Initialize the underline metrics

      ulMetrics [0] = getRegular().getUlTop ();
      ulMetrics [1] = getRegular().getUlThick ();
      ulMetrics [2] = getBold().getUlTop ();
      ulMetrics [3] = getBold().getUlThick ();
      ulMetrics [4] = getItalic().getUlTop ();
      ulMetrics [5] = getItalic().getUlThick ();
      ulMetrics [6] = getBoldItalic().getUlTop ();
      ulMetrics [7] = getBoldItalic().getUlThick ();

      // Look for & initialize the double-width font

      sstate.level = 0;
      sstate.ext = "";
      sstate.regular = "";
      sstate.bold = "";
      sstate.italic = "";
      sstate.boldItalic = "";

      sstate.fontname = dwfontname.data ();
      sstate.fontnamelen = dwfontname.size ();

      pos = 0;
      nextpos = 0;
      do
      {
         nextpos = fontpath.find (':', pos);
         size_t len = (nextpos == std::string::npos)
                    ? std::string::npos
                    : nextpos - pos;

         std::string fontpath1 = fontpath.substr (pos, len);
         logT << "Looking for double-width candidates under " << fontpath1
              << std::endl;
         pos = nextpos + 1;

         int flags = FTW_DEPTH;
         if (nftw (fontpath1.c_str (), fontFileFilter, 32, flags) == -1)
         {
            SYS_WARN ("Cannot walk file tree at ", fontpath1);
         }

      } while (!sstate.regular.size () && nextpos != std::string::npos);

      try
      {
         if (sstate.regular.size ())
            fontDoubleWidth = std::make_unique <Font> (
               sstate.regular, * fontRegular.get (), Font::DoubleWidth);
         else if (dwfontname != "")
         {
            logW << "Failed to locate requested double-width font: "
                 << dwfontname << std::endl;
         }
      }
      catch (const std::runtime_error& e)
      {
         fontDoubleWidth = nullptr;
         logW << "Failed to load double-width font: " << e.what () << std::endl;
      }
   }

} // namespace zutty
