/* See LICENSE file for license and copyright information */

#include <string.h>
#include <stdlib.h>
#include <glib/gi18n.h>

#include "commands.h"
#include "shortcuts.h"
#include "bookmarks.h"
#include "database.h"
#include "document.h"
#include "zathura.h"
#include "print.h"
#include "document.h"
#include "utils.h"
#include "page-widget.h"
#include "page.h"
#include "plugin.h"
#include "internal.h"
#include "render.h"
#include "adjustment.h"

#include <girara/session.h>
#include <girara/settings.h>
#include <girara/commands.h>
#include <girara/datastructures.h>
#include <girara/utils.h>

bool
cmd_bookmark_create(girara_session_t* session, girara_list_t* argument_list)
{
  g_return_val_if_fail(session != NULL, false);
  g_return_val_if_fail(session->global.data != NULL, false);
  zathura_t* zathura = session->global.data;
  if (zathura->document == NULL) {
    girara_notify(session, GIRARA_ERROR, _("No document opened."));
    return false;
  }

  const unsigned int argc = girara_list_size(argument_list);
  if (argc != 1) {
    girara_notify(session, GIRARA_ERROR, _("Invalid number of arguments given."));
    return false;
  }

  const char* bookmark_name = girara_list_nth(argument_list, 0);
  zathura_bookmark_t* bookmark = zathura_bookmark_get(zathura, bookmark_name);
  bool update = bookmark != NULL ? true : false;

  bookmark = zathura_bookmark_add(zathura, bookmark_name, zathura_document_get_current_page_number(zathura->document) + 1);
  if (bookmark == NULL) {
    if (update == true) {
      girara_notify(session, GIRARA_ERROR, _("Could not update bookmark: %s"), bookmark_name);
    } else {
      girara_notify(session, GIRARA_ERROR, _("Could not create bookmark: %s"), bookmark_name);
    }
    return false;
  } else {
    if (update == true) {
      girara_notify(session, GIRARA_INFO, _("Bookmark successfully updated: %s"), bookmark_name);
    } else {
      girara_notify(session, GIRARA_INFO, _("Bookmark successfully created: %s"), bookmark_name);
    }
  }

  return true;
}

bool
cmd_bookmark_delete(girara_session_t* session, girara_list_t* argument_list)
{
  g_return_val_if_fail(session != NULL, false);
  g_return_val_if_fail(session->global.data != NULL, false);
  zathura_t* zathura = session->global.data;
  if (zathura->document == NULL) {
    girara_notify(session, GIRARA_ERROR, _("No document opened."));
    return false;
  }

  const unsigned int argc = girara_list_size(argument_list);
  if (argc != 1) {
    girara_notify(session, GIRARA_ERROR, _("Invalid number of arguments given."));
    return false;
  }

  const char* bookmark = girara_list_nth(argument_list, 0);
  if (zathura_bookmark_remove(zathura, bookmark)) {
    girara_notify(session, GIRARA_INFO, _("Removed bookmark: %s"), bookmark);
  } else {
    girara_notify(session, GIRARA_ERROR, _("Failed to remove bookmark: %s"), bookmark);
  }

  return true;
}

bool
cmd_bookmark_open(girara_session_t* session, girara_list_t* argument_list)
{
  g_return_val_if_fail(session != NULL, false);
  g_return_val_if_fail(session->global.data != NULL, false);
  zathura_t* zathura = session->global.data;
  if (zathura->document == NULL) {
    girara_notify(session, GIRARA_ERROR, _("No document opened."));
    return false;
  }

  const unsigned int argc = girara_list_size(argument_list);
  if (argc != 1) {
    girara_notify(session, GIRARA_ERROR, _("Invalid number of arguments given."));
    return false;
  }

  const char* bookmark_name = girara_list_nth(argument_list, 0);
  zathura_bookmark_t* bookmark = zathura_bookmark_get(zathura, bookmark_name);
  if (bookmark == NULL) {
    girara_notify(session, GIRARA_ERROR, _("No such bookmark: %s"), bookmark_name);
    return false;
  }

  zathura_jumplist_add(zathura);
  page_set(zathura, bookmark->page - 1);
  if (bookmark->x != DBL_MIN && bookmark->y != DBL_MIN) {
    position_set(zathura, bookmark->x, bookmark->y);
  }
  zathura_jumplist_add(zathura);

  return true;
}

bool
cmd_close(girara_session_t* session, girara_list_t* UNUSED(argument_list))
{
  g_return_val_if_fail(session != NULL, false);
  g_return_val_if_fail(session->global.data != NULL, false);
  zathura_t* zathura = session->global.data;
  if (zathura->document == NULL) {
    return true;
  }

  document_close(zathura, false);

  return true;
}

bool
cmd_info(girara_session_t* session, girara_list_t* UNUSED(argument_list))
{
  g_return_val_if_fail(session != NULL, false);
  g_return_val_if_fail(session->global.data != NULL, false);
  zathura_t* zathura = session->global.data;
  if (zathura->document == NULL) {
    girara_notify(session, GIRARA_ERROR, _("No document opened."));
    return false;
  }

  struct meta_field {
    char* name;
    zathura_document_information_type_t field;
  };

  const struct meta_field meta_fields[] = {
    { _("Title"),             ZATHURA_DOCUMENT_INFORMATION_TITLE },
    { _("Author"),            ZATHURA_DOCUMENT_INFORMATION_AUTHOR },
    { _("Subject"),           ZATHURA_DOCUMENT_INFORMATION_SUBJECT },
    { _("Keywords"),          ZATHURA_DOCUMENT_INFORMATION_KEYWORDS },
    { _("Creator"),           ZATHURA_DOCUMENT_INFORMATION_CREATOR },
    { _("Producer"),          ZATHURA_DOCUMENT_INFORMATION_PRODUCER },
    { _("Creation date"),     ZATHURA_DOCUMENT_INFORMATION_CREATION_DATE },
    { _("Modification date"), ZATHURA_DOCUMENT_INFORMATION_MODIFICATION_DATE }
  };

  girara_list_t* information = zathura_document_get_information(zathura->document, NULL);
  if (information == NULL) {
    girara_notify(session, GIRARA_INFO, _("No information available."));
    return false;
  }

  GString* string = g_string_new(NULL);

  GIRARA_LIST_FOREACH(information, zathura_document_information_entry_t*, iter, entry)
  if (entry != NULL) {
    for (unsigned int i = 0; i < LENGTH(meta_fields); i++) {
      if (meta_fields[i].field == entry->type) {
        char* text = g_strdup_printf("<b>%s:</b> %s\n", meta_fields[i].name, entry->value);
        g_string_append(string, text);
        g_free(text);
      }
    }
  }
  GIRARA_LIST_FOREACH_END(information, zathura_document_information_entry_t*, iter, entry);

  if (strlen(string->str) > 0) {
    g_string_erase(string, strlen(string->str) - 1, 1);
    girara_notify(session, GIRARA_INFO, "%s", string->str);
  } else {
    girara_notify(session, GIRARA_INFO, _("No information available."));
  }

  g_string_free(string, TRUE);

  return false;
}

bool
cmd_help(girara_session_t* UNUSED(session), girara_list_t*
         UNUSED(argument_list))
{
  return true;
}

bool
cmd_hlsearch(girara_session_t* session, girara_list_t* UNUSED(argument_list))
{
  g_return_val_if_fail(session != NULL, false);
  g_return_val_if_fail(session->global.data != NULL, false);
  zathura_t* zathura = session->global.data;

  document_draw_search_results(zathura, true);
  render_all(zathura);

  return true;
}


bool
cmd_open(girara_session_t* session, girara_list_t* argument_list)
{
  g_return_val_if_fail(session != NULL, false);
  g_return_val_if_fail(session->global.data != NULL, false);
  zathura_t* zathura = session->global.data;

  const int argc = girara_list_size(argument_list);
  if (argc > 2) {
    girara_notify(session, GIRARA_ERROR, _("Too many arguments."));
    return false;
  } else if (argc >= 1) {
    if (zathura->document != NULL) {
      document_close(zathura, false);
    }

    document_open(zathura, girara_list_nth(argument_list, 0),
                  (argc == 2) ? girara_list_nth(argument_list, 1) : NULL,
                  ZATHURA_PAGE_NUMBER_UNSPECIFIED);
  } else {
    girara_notify(session, GIRARA_ERROR, _("No arguments given."));
    return false;
  }

  return true;
}

bool
cmd_quit(girara_session_t* session, girara_list_t* UNUSED(argument_list))
{
  sc_quit(session, NULL, NULL, 0);

  return true;
}

bool
cmd_print(girara_session_t* session, girara_list_t* UNUSED(argument_list))
{
  g_return_val_if_fail(session != NULL, false);
  g_return_val_if_fail(session->global.data != NULL, false);
  zathura_t* zathura = session->global.data;

  if (zathura->document == NULL) {
    girara_notify(session, GIRARA_ERROR, _("No document opened."));
    return false;
  }

  print(zathura);

  return true;
}

bool
cmd_nohlsearch(girara_session_t* session, girara_list_t* UNUSED(argument_list))
{
  g_return_val_if_fail(session != NULL, false);
  g_return_val_if_fail(session->global.data != NULL, false);
  zathura_t* zathura = session->global.data;

  document_draw_search_results(zathura, false);
  render_all(zathura);

  return true;
}

bool
cmd_save(girara_session_t* session, girara_list_t* argument_list)
{
  g_return_val_if_fail(session != NULL, false);
  g_return_val_if_fail(session->global.data != NULL, false);
  zathura_t* zathura = session->global.data;

  if (zathura->document == NULL) {
    girara_notify(session, GIRARA_ERROR, _("No document opened."));
    return false;
  }

  if (girara_list_size(argument_list) == 1) {
    if (document_save(zathura, girara_list_nth(argument_list, 0), false) == true) {
      girara_notify(session, GIRARA_INFO, _("Document saved."));
    } else {
      girara_notify(session, GIRARA_INFO, _("Failed to save document."));
    }
  } else {
    girara_notify(session, GIRARA_ERROR, _("Invalid number of arguments."));
    return false;
  }

  return true;
}

bool
cmd_savef(girara_session_t* session, girara_list_t* argument_list)
{
  g_return_val_if_fail(session != NULL, false);
  g_return_val_if_fail(session->global.data != NULL, false);
  zathura_t* zathura = session->global.data;

  if (zathura->document == NULL) {
    girara_notify(session, GIRARA_ERROR, _("No document opened."));
    return false;
  }

  if (girara_list_size(argument_list) == 1) {
    if (document_save(zathura, girara_list_nth(argument_list, 0), true) == true) {
      girara_notify(session, GIRARA_INFO, _("Document saved."));
    } else {
      girara_notify(session, GIRARA_INFO, _("Failed to save document."));
    }
  } else {
    girara_notify(session, GIRARA_ERROR, _("Invalid number of arguments."));
    return false;
  }

  return true;
}

bool
cmd_search(girara_session_t* session, const char* input, girara_argument_t* argument)
{
  g_return_val_if_fail(session != NULL, false);
  g_return_val_if_fail(input != NULL, false);
  g_return_val_if_fail(argument != NULL, false);
  g_return_val_if_fail(session->global.data != NULL, false);
  zathura_t* zathura = session->global.data;

  if (zathura->document == NULL || strlen(input) == 0) {
    return false;
  }

  zathura_error_t error = ZATHURA_ERROR_OK;

  /* set search direction */
  zathura->global.search_direction = argument->n;

  unsigned int number_of_pages     = zathura_document_get_number_of_pages(zathura->document);
  unsigned int current_page_number = zathura_document_get_current_page_number(zathura->document);

  /* reset search highlighting */
  bool nohlsearch = false;
  girara_setting_get(session, "nohlsearch", &nohlsearch);

  /* search pages */
  for (unsigned int page_id = 0; page_id < number_of_pages; ++page_id) {
    unsigned int index = (page_id + current_page_number) % number_of_pages;
    zathura_page_t* page = zathura_document_get_page(zathura->document, index);
    if (page == NULL) {
      continue;
    }

    GtkWidget* page_widget = zathura_page_get_widget(zathura, page);
    g_object_set(page_widget, "draw-links", FALSE, NULL);

    zathura_renderer_lock(zathura->sync.render_thread);
    girara_list_t* result = zathura_page_search_text(page, input, &error);
    zathura_renderer_unlock(zathura->sync.render_thread);

    if (result == NULL || girara_list_size(result) == 0) {
      girara_list_free(result);
      g_object_set(page_widget, "search-results", NULL, NULL);

      if (error == ZATHURA_ERROR_NOT_IMPLEMENTED) {
        break;
      } else {
        continue;
      }
    }

    g_object_set(page_widget, "search-results", result, NULL);

    if (argument->n == BACKWARD) {
      /* start at bottom hit in page */
      g_object_set(page_widget, "search-current", girara_list_size(result) - 1, NULL);
    } else {
      g_object_set(page_widget, "search-current", 0, NULL);
    }
  }

  girara_argument_t* arg = g_try_malloc0(sizeof(girara_argument_t));
  if (arg == NULL) {
    return false;
  }

  arg->n = FORWARD;
  sc_search(session, arg, NULL, 0);
  g_free(arg);

  return true;
}

bool
cmd_export(girara_session_t* session, girara_list_t* argument_list)
{
  g_return_val_if_fail(session != NULL, false);
  g_return_val_if_fail(session->global.data != NULL, false);
  zathura_t* zathura = session->global.data;
  if (zathura->document == NULL) {
    girara_notify(session, GIRARA_ERROR, _("No document opened."));
    return false;
  }

  if (girara_list_size(argument_list) != 2) {
    girara_notify(session, GIRARA_ERROR, _("Invalid number of arguments given."));
    return false;
  }

  const char* file_identifier = girara_list_nth(argument_list, 0);
  const char* file_name       = girara_list_nth(argument_list, 1);

  if (file_name == NULL || file_identifier == NULL) {
    return false;
  }

  char* export_path = girara_fix_path(file_name);
  if (export_path == NULL) {
    return false;
  }

  /* attachment */
  if (strncmp(file_identifier, "attachment-", strlen("attachment-")) == 0) {
    if (zathura_document_attachment_save(zathura->document, file_identifier + strlen("attachment-"), export_path) == false) {
      girara_notify(session, GIRARA_ERROR, _("Couldn't write attachment '%s' to '%s'."), file_identifier, file_name);
    } else {
      girara_notify(session, GIRARA_INFO, _("Wrote attachment '%s' to '%s'."), file_identifier, export_path);
    }
    /* image */
  } else if (strncmp(file_identifier, "image-p", strlen("image-p")) == 0 && strlen(file_identifier) >= 10) {
    /* parse page id */
    const char* input = file_identifier + strlen("image-p");
    int page_id = atoi(input);
    if (page_id == 0) {
      goto image_error;
    }

    /* parse image id */
    input = strstr(input, "-");
    if (input == NULL) {
      goto image_error;
    }

    int image_id = atoi(input + 1);
    if (image_id == 0) {
      goto image_error;
    }

    /* get image */
    zathura_page_t* page = zathura_document_get_page(zathura->document, page_id - 1);
    if (page == NULL) {
      goto image_error;
    }

    girara_list_t* images = zathura_page_images_get(page, NULL);
    if (images == NULL) {
      goto image_error;
    }

    zathura_image_t* image = girara_list_nth(images, image_id - 1);
    if (image == NULL) {
      goto image_error;
    }

    cairo_surface_t* surface = zathura_page_image_get_cairo(page, image, NULL);
    if (surface == NULL) {
      goto image_error;
    }

    if (cairo_surface_write_to_png(surface, export_path) == CAIRO_STATUS_SUCCESS) {
      girara_notify(session, GIRARA_INFO, _("Wrote image '%s' to '%s'."), file_identifier, export_path);
    } else {
      girara_notify(session, GIRARA_ERROR, _("Couldn't write image '%s' to '%s'."), file_identifier, file_name);
    }

    goto error_ret;

image_error:

    girara_notify(session, GIRARA_ERROR, _("Unknown image '%s'."), file_identifier);
    goto error_ret;
    /* unknown */
  } else {
    girara_notify(session, GIRARA_ERROR, _("Unknown attachment or image '%s'."), file_identifier);
  }

error_ret:

  g_free(export_path);

  return true;
}

bool
cmd_exec(girara_session_t* session, girara_list_t* argument_list)
{
  g_return_val_if_fail(session != NULL, false);
  g_return_val_if_fail(session->global.data != NULL, false);
  zathura_t* zathura = session->global.data;

  if (zathura->document != NULL) {
    const char* path = zathura_document_get_path(zathura->document);

    GIRARA_LIST_FOREACH(argument_list, char*, iter, value)
      char* r = girara_replace_substring(value, "$FILE", path);

      if (r != NULL) {
        char* s = girara_replace_substring(r, "%", path);
        g_free(r);

        if (s != NULL) {
          girara_list_iterator_set(iter, s);
        }
      }
    GIRARA_LIST_FOREACH_END(argument_list, char*, iter, value);
  }

  return girara_exec_with_argument_list(session, argument_list);
}

bool
cmd_offset(girara_session_t* session, girara_list_t* argument_list)
{
  g_return_val_if_fail(session != NULL, false);
  g_return_val_if_fail(session->global.data != NULL, false);
  zathura_t* zathura = session->global.data;
  if (zathura->document == NULL) {
    girara_notify(session, GIRARA_ERROR, _("No document opened."));
    return false;
  }

  /* no argument: take current page as offset */
  int page_offset = zathura_document_get_current_page_number(zathura->document);

  /* retrieve offset from argument */
  if (girara_list_size(argument_list) == 1) {
    const char* value = girara_list_nth(argument_list, 0);
    if (value != NULL) {
      page_offset = atoi(value);
      if (page_offset == 0 && strcmp(value, "0") != 0) {
        girara_notify(session, GIRARA_WARNING, _("Argument must be a number."));
        return false;
      }
    }
  }

  zathura_document_set_page_offset(zathura->document, page_offset);

  return true;
}

bool
cmd_version(girara_session_t* session, girara_list_t* UNUSED(argument_list))
{
  g_return_val_if_fail(session != NULL, false);
  g_return_val_if_fail(session->global.data != NULL, false);
  zathura_t* zathura = session->global.data;

  char* string = zathura_get_version_string(zathura, true);
  if (string == NULL) {
    return false;
  }

  /* display information */
  girara_notify(session, GIRARA_INFO, "%s", string);

  g_free(string);

  return true;
}