/* SPDX-License-Identifier: Zlib */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <glib.h>
#include <glib/gstdio.h>
#include <girara/datastructures.h>
#include <archive.h>
#include <archive_entry.h>

#include "plugin.h"
#include "internal.h"
#include "utils.h"

static bool read_archive(cb_document_t* cb_document, const char* archive, girara_list_t* supported_extensions);
static char* get_extension(const char* path);
static bool read_dir(cb_document_t* cb_document, const char* directory, girara_list_t* supported_extensions);

static void cb_document_page_meta_free(void* data) {
  if (data != NULL) {
    cb_document_page_meta_t* meta = data;
    g_free(meta->file);
    g_free(meta);
  }
}

static int compare_pages(const void* p1, const void* p2) {
  const cb_document_page_meta_t* page1 = p1;
  const cb_document_page_meta_t* page2 = p2;

  return compare_path(page1->file, page2->file);
}

zathura_error_t cb_document_open(zathura_document_t* document) {
  if (document == NULL) {
    return ZATHURA_ERROR_INVALID_ARGUMENTS;
  }

  /* archive path */
  const char* path = zathura_document_get_path(document);

  /* create list of supported formats */
  g_autoptr(girara_list_t) supported_extensions = girara_list_new_with_free(g_free);
  if (supported_extensions == NULL) {
    return ZATHURA_ERROR_UNKNOWN;
  }

  g_autoptr(GSList) formats = gdk_pixbuf_get_formats();
  for (GSList* list = formats; list != NULL; list = list->next) {
    GdkPixbufFormat* format = (GdkPixbufFormat*)list->data;
    char** extensions       = gdk_pixbuf_format_get_extensions(format);

    for (unsigned int i = 0; extensions[i] != NULL; i++) {
      girara_list_append(supported_extensions, g_strdup(extensions[i]));
    }

    g_strfreev(extensions);
  }

  cb_document_t* cb_document = g_malloc0(sizeof(cb_document_t));

  /* create list of supported files (pages) */
  cb_document->pages = girara_sorted_list_new_with_free(compare_pages, cb_document_page_meta_free);
  if (cb_document->pages == NULL) {
    goto error_free;
  }

  /* read files recursively */
  if (g_file_test(path, G_FILE_TEST_IS_DIR)) {
    if (read_dir(cb_document, path, supported_extensions) == false) {
      goto error_free;
    }
  } else {
    if (read_archive(cb_document, path, supported_extensions) == false) {
      goto error_free;
    }
  }

  /* set document information */
  zathura_document_set_number_of_pages(document, girara_list_size(cb_document->pages));
  zathura_document_set_data(document, cb_document);

  return ZATHURA_ERROR_OK;

error_free:

  cb_document_free(document, cb_document);

  return ZATHURA_ERROR_UNKNOWN;
}

zathura_error_t cb_document_free(zathura_document_t* UNUSED(document), void* data) {
  cb_document_t* cb_document = data;
  if (cb_document == NULL) {
    return ZATHURA_ERROR_INVALID_ARGUMENTS;
  }

  /* remove page list */
  if (cb_document->pages != NULL) {
    girara_list_free(cb_document->pages);
  }

  g_free(cb_document);

  return ZATHURA_ERROR_OK;
}

static void get_pixbuf_size(GdkPixbufLoader* loader, int width, int height, gpointer data) {
  cb_document_page_meta_t* meta = (cb_document_page_meta_t*)data;

  meta->width  = width;
  meta->height = height;

  gdk_pixbuf_loader_set_size(loader, 0, 0);
}

static bool read_archive(cb_document_t* cb_document, const char* archive, girara_list_t* supported_extensions) {
  g_autoptr(archive_t) a = archive_read_new();
  if (a == NULL) {
    return false;
  }

  archive_read_support_filter_all(a);
  archive_read_support_format_all(a);
  int r = archive_read_open_filename(a, archive, (size_t)LIBARCHIVE_BUFFER_SIZE);
  if (r != ARCHIVE_OK) {
    return false;
  }

  struct archive_entry* entry = NULL;
  while ((r = archive_read_next_header(a, &entry)) != ARCHIVE_EOF) {
    if (r < ARCHIVE_WARN) {
      // let's ignore warnings ... they are non-fatal errors
      return false;
    } else if (r == ARCHIVE_RETRY) {
      continue;
    }

    if (archive_entry_filetype(entry) != AE_IFREG) {
      // we only care about regular files
      continue;
    }

    const char* path           = archive_entry_pathname(entry);
    g_autofree char* extension = get_extension(path);

    if (extension == NULL) {
      continue;
    }

    for (size_t index = 0; index < girara_list_size(supported_extensions); ++index) {
      const char* ext = girara_list_nth(supported_extensions, index);
      if (g_strcmp0(extension, ext) == 0) {
        cb_document_page_meta_t* meta = g_malloc0(sizeof(cb_document_page_meta_t));
        meta->file                    = g_strdup(path);

        g_autoptr(GdkPixbufLoader) loader = gdk_pixbuf_loader_new();
        g_signal_connect(loader, "size-prepared", G_CALLBACK(get_pixbuf_size), meta);

        uint8_t buf[LIBARCHIVE_BUFFER_SIZE];
        la_ssize_t bytes_read;
        while ((bytes_read = archive_read_data(a, buf, sizeof(buf))) != 0) {
          if (bytes_read == ARCHIVE_RETRY) {
            continue;
          } else if (bytes_read < 0) {
            break;
          }

          if (gdk_pixbuf_loader_write(loader, buf, bytes_read, NULL) == false) {
            break;
          }

          if (meta->width > 0 && meta->height > 0) {
            break;
          }
        }

        gdk_pixbuf_loader_close(loader, NULL);

        if (meta->width > 0 && meta->height > 0) {
          girara_list_append(cb_document->pages, meta);
        } else {
          cb_document_page_meta_free(meta);
        }

        break;
      }
    }
  }

  return true;
}

static bool read_dir(cb_document_t* cb_document, const char* directory, girara_list_t* supported_extensions) {
  g_autoptr(GDir) dir = g_dir_open(directory, 0, NULL);
  if (dir == NULL) {
    return false;
  }
  const char* entrypath = NULL;
  while ((entrypath = g_dir_read_name(dir))) {
    g_autofree char* fullpath  = g_strdup_printf("%s/%s", directory, entrypath);
    g_autofree char* extension = get_extension(fullpath);
    if (extension == NULL) {
      continue;
    }

    for (size_t index = 0; index < girara_list_size(supported_extensions); ++index) {
      const char* ext = girara_list_nth(supported_extensions, index);
      if (g_strcmp0(ext, extension) == 0) {
        cb_document_page_meta_t* meta = g_malloc(sizeof(cb_document_page_meta_t));
        meta->file                    = g_strdup(fullpath);

        g_autoptr(GdkPixbuf) data = gdk_pixbuf_new_from_file(meta->file, NULL);
        meta->width               = gdk_pixbuf_get_width(data);
        meta->height              = gdk_pixbuf_get_height(data);

        if (meta->width > 0 && meta->height > 0) {
          girara_list_append(cb_document->pages, meta);
        } else {
          cb_document_page_meta_free(meta);
        }

        break;
      }
    }
  }
  return true;
}

static char* get_extension(const char* path) {
  if (path == NULL) {
    return NULL;
  }

  const char* res = strrchr(path, '.');
  if (res == NULL) {
    return NULL;
  }

  return g_ascii_strdown(res + 1, -1);
}
