/***************************************************************************
 *   Copyright (C) 2018 by David S. Register                               *
 *                                                                         *
 *   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 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, see <https://www.gnu.org/licenses/   *
 ***************************************************************************/

/**
 * \file
 *
 * Implement svg_utils.h -- SVG Utility functions
 */

#ifdef __ANDROID__
#include "androidUTIL.h"
#include "qdebug.h"
#endif

#include <wx/filename.h>
#include <wx/dir.h>

#ifdef ocpnUSE_SVG
#ifndef ocpnUSE_wxBitmapBundle
#include "wxSVG/svg.h"
#else
#include <wx/bmpbndl.h>
#endif
#endif  // ocpnUSE_SVG

#include "model/svg_utils.h"

#include "model/base_platform.h"
#include "model/routeman.h"
#include "pugixml.hpp"

#define SVG_IN_TO_PT 72
#define SVG_IN_TO_PX 96
#define SVG_PT_TO_IN 1 / 72
#define SVG_PX_TO_IN 1 / 96
#define SVG_PT_TO_PX 96 / 72
#define SVG_MM_TO_PX 3.7795275591
#define SVG_PX_TO_MM 0.2645833333
#define SVG_MM_TO_PT 2.8346456693
#define SVG_PT_TO_MM 0.3527777778
#define SVG_CM_TO_PX 37.795275591
#define SVG_CM_TO_PT 28.346456693
#define SVG_MM_TO_IN 25.4

wxBitmap LoadSVG(const wxString filename, const unsigned int width,
                 const unsigned int height, wxBitmap* default_bitmap,
                 bool use_cache) {
#ifdef ocpnUSE_SVG
#ifndef ocpnUSE_wxBitmapBundle
#ifdef __ANDROID__
  return loadAndroidSVG(filename, width, height);
#else
  wxSVGDocument svgDoc;
  if (svgDoc.Load(filename))
    return wxBitmap(svgDoc.Render(width, height, NULL, true, true));
  else
    return wxBitmap(width, height);
#endif
#else
#ifdef __ANDROID__
  return loadAndroidSVG(filename, width, height);
#else
  wxSize s(width, height);
  if (wxFileExists(filename)) {
    wxBitmap bmp;
    std::string key;
    if (use_cache && SVGBitmapCache::GetInstance().HasKey(
                         key = SVGBitmapCache::GetInstance().MakeKey(
                             filename, width, height))) {
      bmp = SVGBitmapCache::GetInstance().Get(key);
    } else {
      bmp = wxBitmapBundle::FromSVGFile(filename, s).GetBitmap(s);
      if (use_cache) {
        SVGBitmapCache::GetInstance().Add(key, bmp);
      }
    }
    if (bmp.IsOk()) {
      return bmp;
    }
  }
  if (default_bitmap) {
    return *default_bitmap;
  } else {
    return wxNullBitmap;  // Or wxBitmap(width, height);?
  }
#endif
#endif
#else
  return wxBitmap(width, height);
#endif  // ocpnUSE_SVG
}

/*  returns 1 if str ends with suffix  */
int str_ends_with(const char* str, const char* suffix) {
  if (str == NULL || suffix == NULL) return 0;

  size_t str_len = strlen(str);
  size_t suffix_len = strlen(suffix);

  if (suffix_len > str_len) return 0;

  return 0 == strncmp(str + str_len - suffix_len, suffix, suffix_len);
}

// Convert the provided value to the standard 96 DPI pixels
// if such a conversion is doable. If not doable or not necessary (px, em, ex,
// %), numerical part of the value is returned In case of an error, 0 is
// returned
unsigned int get_px_length(const char* val) {
  int num;
  try {
    num = std::stoi(val);
  } catch (std::invalid_argument&) {
    return 0;
  } catch (std::out_of_range&) {
    return 0;
  }
  if (num < 0) {
    return 0;
  }

  if (str_ends_with(val, "mm")) {
    return (unsigned int)((float)num * SVG_MM_TO_PX);
  } else if (str_ends_with(val, "cm")) {
    return (unsigned int)((float)num * SVG_CM_TO_PX);
  } else if (str_ends_with(val, "in")) {
    return (unsigned int)((float)num * SVG_CM_TO_PX);
  } else if (str_ends_with(val, "pt")) {
    return (unsigned int)((float)num * SVG_PT_TO_PX);
  }
  return num;
}

bool SVGDocumentPixelSize(const wxString filename, unsigned int& width,
                          unsigned int& height) {
  width = 0;
  height = 0;
  float viewBoxWidth = 0, viewBoxHeight = 0;
  pugi::xml_document svgDoc;
  if (svgDoc.load_file(filename.fn_str())) {
    pugi::xml_node svgNode = svgDoc.child("svg");

    // Check for viewBox attribute.
    if (const char* viewBox = svgNode.attribute("viewBox").value()) {
      std::istringstream iss(viewBox);
      float minX, minY;
      iss >> minX >> minY >> viewBoxWidth >> viewBoxHeight;
    }

    // Check width/height attributes
    for (pugi::xml_attribute attr = svgNode.first_attribute(); attr;
         attr = attr.next_attribute()) {
      const char* pca = attr.name();
      if (!strcmp(pca, "width")) {
        width = get_px_length(attr.as_string());
      } else if (!strcmp(pca, "height")) {
        height = get_px_length(attr.as_string());
      }
    }
    // SVG sizing rules: https://svgwg.org/specs/integration/#svg-css-sizing
    if (width == 0 && height == 0) {
      if (viewBoxWidth > 0 && viewBoxHeight > 0) {
        // Use viewBox dimensions
        width = viewBoxWidth;
        height = viewBoxHeight;
      } else {
        // Default sizes per spec
        width = 300;
        height = 150;
      }
    } else if (width == 0) {
      if (viewBoxWidth > 0 && viewBoxHeight > 0) {
        width = height * (viewBoxWidth / viewBoxHeight);
      } else {
        width = 300;
      }
    } else if (height == 0) {
      if (viewBoxWidth > 0 && viewBoxHeight > 0) {
        height = width * (viewBoxHeight / viewBoxWidth);
      } else {
        height = 150;
      }
    }
  }
  return false;
}

unsigned int SVGPixelsToDisplay(unsigned int svg_px) {
  return g_BasePlatform->GetDisplayDPmm() * SVG_MM_TO_IN / SVG_IN_TO_PX *
         svg_px * g_ChartScaleFactorExp;
}

wxBitmap LoadSvgStdIcon(const std::string& svg_file, const wxWindow* w,
                        bool touch) {
  auto path = fs::path(g_BasePlatform->GetSharedDataDir().ToStdString()) /
              "uidata" / "MUI_flat" / svg_file;
  int size = g_BasePlatform->GetSvgStdIconSize(w, touch);
  return LoadSVG(path.string(), size, size);
}

SVGBitmapCache::SVGBitmapCache() {
  wxFileName iconcachedir;
  iconcachedir.SetName("iconCacheSVG");
  iconcachedir.SetPath(g_BasePlatform->GetPrivateDataDir());
  //  Create the cache dir here if necessary
  if (!wxDir::Exists(iconcachedir.GetFullPath())) {
    wxFileName::Mkdir(iconcachedir.GetFullPath());
  }
  cache_directory = iconcachedir.GetFullPath();
}

std::string SVGBitmapCache::MakeKey(wxString file_path, const int width,
                                    const int height) {
  std::replace(file_path.begin(), file_path.end(), ':', '_');
  std::replace(file_path.begin(), file_path.end(), '/', '_');
  std::replace(file_path.begin(), file_path.end(), '\\', '_');
  std::replace(file_path.begin(), file_path.end(), '>', '_');
  std::replace(file_path.begin(), file_path.end(), '<', '_');
  std::replace(file_path.begin(), file_path.end(), '"', '_');
  std::replace(file_path.begin(), file_path.end(), '|', '_');
  std::replace(file_path.begin(), file_path.end(), '?', '_');
  std::replace(file_path.begin(), file_path.end(), '*', '_');

  std::ostringstream ss;
  ss << file_path << "_" << width << "x" << height;
  return ss.str();
}

void SVGBitmapCache::Add(const wxString key, const wxBitmap bmp) {
  if (!bmp.IsOk()) {
    return;
  }
  sync.lock();
  items.emplace(key, bmp);
  wxFileName fn;
  fn.SetName(key);
  fn.SetPath(cache_directory);
  bmp.SaveFile(fn.GetFullPath(), wxBITMAP_TYPE_PNG);
  sync.unlock();
}

wxBitmap SVGBitmapCache::Get(const wxString key) {
  wxBitmap bmp = wxNullBitmap;
  sync.lock();
  std::unordered_map<std::string, wxBitmap>::const_iterator i =
      items.find(key.ToStdString());
  if (i != items.end()) {
    bmp = i->second;
  } else {
    wxFileName fn;
    fn.SetName(key);
    fn.SetPath(cache_directory);
    if (fn.FileExists()) {
      bmp.LoadFile(fn.GetFullPath(), wxBITMAP_TYPE_PNG);
      if (bmp.IsOk()) {
        items.emplace(key, bmp);
      } else {
        bmp = wxNullBitmap;
      }
    }
  }
  sync.unlock();
  return bmp;
}

bool SVGBitmapCache::HasKey(const wxString key) {
  bool res = false;
  sync.lock();
  if (items.find(key.ToStdString()) != items.end()) {
    res = true;
  } else {
    wxFileName fn;
    fn.SetName(key);
    fn.SetPath(cache_directory);
    if (fn.FileExists()) {
      // We proactively also load it here if it exists
      wxBitmap bmp;
      bmp.LoadFile(fn.GetFullPath(), wxBITMAP_TYPE_PNG);
      if (bmp.IsOk()) {
        items.emplace(key, bmp);
        res = true;
      }
    }
  }
  sync.unlock();
  return res;
}
