#!/usr/bin/env python3
import os
import re
import shutil
import subprocess
import sys
from pathlib import Path
from glob import glob

if not os.getenv("MESON_BUILD_ROOT"):
  # Let's prevent contributors from creating broken bundles
  print("\033[31m(ERROR)\033[0m: Script called standalone. Please build GIMP targeting installer or msix creation.")
  sys.exit(1)


# Bundle deps and GIMP files
GIMP_SOURCE = Path(os.getenv("MESON_SOURCE_ROOT")).as_posix()  

## System prefix: it is MSYSTEM_PREFIX
with open("meson-logs/meson-log.txt") as f:
  meson_log = f.read()
match = re.search(r"Main binary: (.*)\\bin\\python\.exe", meson_log)
MSYSTEM_PREFIX = match.group(1).replace("\\", "/")
## GIMP prefix: as set at meson configure time
GIMP_PREFIX = Path(os.getenv("MESON_INSTALL_DESTDIR_PREFIX")).as_posix()

## Bundle dir: we make a "perfect" bundle separated from GIMP_PREFIX
#NOTE: The bundling script need to set $MSYSTEM_PREFIX to our dist scripts
#fallback code be able to identify what arch they are distributing
GIMP_DISTRIB = Path(GIMP_SOURCE) / f"gimp-{Path(MSYSTEM_PREFIX).name}"

def bundle(src_root, pattern):
  ## Search for targets in search path
  src_root = Path(src_root)
  for src_path in src_root.glob(pattern):
    if not src_path.exists():
      continue
    ## Copy found targets to bundle path
    dest_path = GIMP_DISTRIB / src_path.relative_to(src_root)
    dest_path.parent.mkdir(parents=True, exist_ok=True)
    print(f"Bundling {src_path} to {dest_path.parent}")
    if src_path.is_dir():
      shutil.copytree(src_path, dest_path, dirs_exist_ok=True)
    else:
      shutil.copy2(src_path, dest_path)

def clean(base_path, pattern):
  base_path = Path(base_path)
  first_found = False
  for parent_path in base_path.glob(os.path.dirname(pattern)):
    for path in parent_path.rglob(os.path.basename(pattern)):
      if path.exists():
        if not first_found:
           print(f"Cleaning {base_path}/{pattern}")
           first_found = True
        if path.is_dir():
          shutil.rmtree(path)
        else:
          path.unlink()


## Prepare bundle
GIMP_DISTRIB.mkdir(parents=True, exist_ok=True)
### Prevent Git going crazy
(GIMP_DISTRIB / ".gitignore").write_text("*\n")
### Add a wrapper at tree root, less messy than having to look for the
### binary inside bin/, in the middle of all the DLLs.
with open("config.h") as file:
  for line in file:
    match = re.match(r'^#define\s+(\S+)\s+(.*)', line)
    if match:
      key, value = match.groups()
      os.environ[key] = value.strip().strip('"').strip("'")
if os.getenv('GIMP_UNSTABLE'):
  gimp_mutex_version = os.getenv('GIMP_APP_VERSION')
else:
  gimp_mutex_version = re.search(r'\d+', os.getenv('GIMP_APP_VERSION')).group()
(GIMP_DISTRIB / "gimp.cmd").write_text(f"bin\\gimp-{gimp_mutex_version}.exe\n")


## Settings.
bundle(GIMP_PREFIX, "etc/gimp")
### Needed for fontconfig
bundle(MSYSTEM_PREFIX, "etc/fonts")
#### Avoid other programs breaking the cache. See: https://gitlab.gnome.org/GNOME/gimp/-/issues/1366
fonts_conf = GIMP_DISTRIB / "etc/fonts/fonts.conf"
text = fonts_conf.read_text()
new_text = text.replace(
  "LOCAL_APPDATA_FONTCONFIG_CACHE",
  f"~/AppData/Local/GIMP/{os.getenv('GIMP_APP_VERSION')}/fontconfig/cache"
)
fonts_conf.write_text(new_text)


## Library data.
bundle(GIMP_PREFIX, "lib/gimp")
bundle(GIMP_PREFIX, "lib/babl-*")
bundle(GIMP_PREFIX, "lib/gegl-*")
### Needed to open remote files
bundle(MSYSTEM_PREFIX, "lib/gio")
### Needed to loading icons in GUI
bundle(MSYSTEM_PREFIX, "lib/gdk-pixbuf-*/*/loaders.cache")
bundle(MSYSTEM_PREFIX, "lib/gdk-pixbuf-*/*/loaders/libpixbufloader-png.dll")
bundle(MSYSTEM_PREFIX, "lib/gdk-pixbuf-*/*/loaders/pixbufloader_svg.dll")
### Support for legacy Win clipboard images: https://gitlab.gnome.org/GNOME/gimp/-/issues/4802
bundle(MSYSTEM_PREFIX, "lib/gdk-pixbuf-*/*/loaders/libpixbufloader-bmp.dll")
### Support for non .PAT patterns: https://gitlab.gnome.org/GNOME/gimp/-/issues/12351
bundle(MSYSTEM_PREFIX, "lib/gdk-pixbuf-*/*/loaders/libpixbufloader-jpeg.dll")
bundle(MSYSTEM_PREFIX, "lib/gdk-pixbuf-*/*/loaders/libpixbufloader-gif.dll")
bundle(MSYSTEM_PREFIX, "lib/gdk-pixbuf-*/*/loaders/libpixbufloader-tiff.dll")


## Resources.
bundle(GIMP_PREFIX, "share/gimp")
### Needed for file dialogs
bundle(MSYSTEM_PREFIX, "share/glib-*/schemas/gschemas.compiled")
### Needed to not crash UI. See: https://gitlab.gnome.org/GNOME/gimp/-/issues/6165
bundle(MSYSTEM_PREFIX, "share/icons/Adwaita")
### Needed by GTK to use icon themes. See: https://gitlab.gnome.org/GNOME/gimp/-/issues/5080
bundle(GIMP_PREFIX, "share/icons/hicolor")
### Needed for 'th' word breaking in Text tool etc
bundle(MSYSTEM_PREFIX, "share/libthai")
### Needed for file-wmf work
bundle(MSYSTEM_PREFIX, "share/libwmf")
### Only copy from langs supported in GIMP.
lang_array = [Path(f).stem for f in glob(str(Path(GIMP_SOURCE)/"po/*.po"))]
for lang in lang_array:
  bundle(GIMP_PREFIX, f"share/locale/{lang}/LC_MESSAGES/*.mo")
  if (Path(MSYSTEM_PREFIX)/f"share/locale/{lang}/LC_MESSAGES").exists():
    # Needed for eventually used widgets, GTK inspector etc
    bundle(MSYSTEM_PREFIX, f"share/locale/{lang}/LC_MESSAGES/gtk*.mo")
    # For language list in text tool options
    bundle(MSYSTEM_PREFIX, f"share/locale/{lang}/LC_MESSAGES/iso_639_3.mo")
### Needed for welcome page
bundle(GIMP_PREFIX, "share/metainfo/org.gimp*.xml")
### mypaint brushes
bundle(MSYSTEM_PREFIX, "share/mypaint-data")
### Needed for full CJK and Cyrillic support in file-pdf
bundle(MSYSTEM_PREFIX, "share/poppler")


## Executables and DLLs.
### Minimal (and some additional) executables for the 'bin' folder
bundle(GIMP_PREFIX, "bin/gimp*.exe")
bundle(GIMP_PREFIX, "bin/libgimp*.dll")
### Bundled just to promote GEGL. See: https://gitlab.gnome.org/GNOME/gimp/-/issues/10580
bundle(GIMP_PREFIX, "bin/gegl.exe")
# Note: we want the same test as around the global variable
# show_debug_menu in app/main.c
if (os.getenv("GIMP_UNSTABLE") or not os.getenv("GIMP_RELEASE")) and "32" not in MSYSTEM_PREFIX:
  ### Needed for 'Show image graph'.
  #### See: https://gitlab.gnome.org/GNOME/gimp/-/issues/6045
  bundle(MSYSTEM_PREFIX, "bin/dot.exe")
  #### See: https://gitlab.gnome.org/GNOME/gimp/-/issues/12119
  bundle(MSYSTEM_PREFIX, "bin/libgvplugin_dot*.dll")
  bundle(MSYSTEM_PREFIX, "bin/libgvplugin_pango*.dll")
  bundle(MSYSTEM_PREFIX, "bin/config6")  
### Needed to not pollute output. See: https://gitlab.gnome.org/GNOME/gimp/-/issues/8877
bundle(MSYSTEM_PREFIX, "bin/gdbus.exe")
### Needed for hyperlink support etc... See: https://gitlab.gnome.org/GNOME/gimp/-/issues/12288
#...when running from `gimp*.exe --verbose`
bundle(MSYSTEM_PREFIX, "bin/gspawn*-console.exe")
if not os.getenv("GIMP_UNSTABLE") and os.getenv("GIMP_RELEASE"):
  #...when running from `gimp*.exe`
  bundle(MSYSTEM_PREFIX, "bin/gspawn*-helper.exe")
  
## Binaries for GObject Introspection support. See: https://gitlab.gnome.org/GNOME/gimp/-/issues/13170
bundle(GIMP_PREFIX, "lib/girepository-*")
bundle(MSYSTEM_PREFIX, "lib/girepository-*")
bundle(MSYSTEM_PREFIX, "bin/libgirepository-*.dll")
### FIXME: luajit crashes at startup: See: https://gitlab.gnome.org/GNOME/gimp/-/issues/11597
#bundle(MSYSTEM_PREFIX, "bin/luajit.exe")
#bundle(MSYSTEM_PREFIX, "lib/lua")
#bundle(MSYSTEM_PREFIX, "share/lua")
### Python support
#python.exe is needed for plug-ins output in `gimp-console*.exe`
bundle(MSYSTEM_PREFIX, "bin/python.exe")
if not os.getenv("GIMP_UNSTABLE") and os.getenv("GIMP_RELEASE"):
  #pythonw.exe is needed to run plug-ins silently in `gimp*.exe`
  bundle(MSYSTEM_PREFIX, "bin/pythonw.exe")
bundle(MSYSTEM_PREFIX, "lib/python*")
clean(GIMP_DISTRIB, "lib/python*/*.pyc")

### We save the list of already copied DLLs to keep a state between 2_bundle-gimp-uni_dep runs.
done_dll = Path(f"{os.getenv('MESON_BUILD_ROOT')}/done-dll.list")
done_dll.unlink(missing_ok=True)
### Deps (DLLs) of the binaries in 'bin' and 'lib' dirs
for dir in ["bin", "lib"]:
  search_dir = GIMP_DISTRIB / dir
  print(f"Searching for dependencies of {search_dir} in {MSYSTEM_PREFIX} and {GIMP_PREFIX}")
  for ext in ("*.dll", "*.exe"):
    for dep in search_dir.rglob(ext):
      subprocess.run([
        sys.executable, f"{GIMP_SOURCE}/build/windows/2_bundle-gimp-uni_dep.py",
        str(dep), f"{MSYSTEM_PREFIX}/", f"{GIMP_PREFIX}/",
        str(GIMP_DISTRIB), "--output-dll-list", done_dll.as_posix()
      ], check=True)

### .pdb (CodeView) debug symbols from babl, gegl and GIMP binaries
bundle(GIMP_PREFIX, "bin/*.pdb")
### Remove .pdb without corresponding binaries (depends on what was choosen to be bundled above)
files = os.listdir(GIMP_DISTRIB / "bin")
binaries = {os.path.splitext(file)[0] for file in files if file.endswith(('.exe', '.dll'))}
for file in files:
  if file.endswith('.pdb'):
    if os.path.splitext(file)[0] not in binaries:
      print(f"(INFO): removing orphan {file}")
      os.remove(os.path.join(GIMP_DISTRIB / "bin", file))
### Remove ancient COFF symbol table (not used for debugging) from MSYS2 binaries
for bin_path in GIMP_DISTRIB.rglob("*"):
  if bin_path.suffix.lower() in (".dll", ".exe", ".pyd"):
    with open(bin_path, "rb") as f:
      if b"RSDS" not in f.read():
        print(f"(INFO): stripping COFF symbols from {bin_path.name}")
        try:
          subprocess.run(["strip", str(bin_path)], check=True)
        except:
          continue


## Development files (to build GEGL filters and GIMP plug-ins).
clean(GIMP_DISTRIB, "lib/*.a")
bundle(GIMP_PREFIX, "include/gimp-*")
bundle(GIMP_PREFIX, "include/babl-*")
bundle(GIMP_PREFIX, "include/gegl-*")
bundle(GIMP_PREFIX, "lib/libgimp*.a")
bundle(GIMP_PREFIX, "lib/libbabl*.a")
bundle(GIMP_PREFIX, "lib/libgegl*.a")
bundle(GIMP_PREFIX, "lib/pkgconfig/gimp*")
bundle(GIMP_PREFIX, "lib/pkgconfig/babl*")
bundle(GIMP_PREFIX, "lib/pkgconfig/gegl*")
