/*
 * This file is part of the Gopherus project.
 * Copyright (C) 2013-2022 Mateusz Viste
 */

#include <stdlib.h>  /* malloc(), NULL */
#include <string.h>  /* strcasecmp(), ... */

#include "config.h"
#include "history.h" /* include self for control and type declaration */


static void history_free_node(struct historytype *node) {
  if (node->cache != NULL) free(node->cache);
  if (node->selector != NULL) free(node->selector);
  free(node);
}


/* remove the last visited page from history */
void history_pop(struct historytype **history) {
  struct historytype *victim;
  if (*history != NULL) {
    victim = *history;
    *history = (*history)->next;
    history_free_node(victim);
  }
  if (*history == NULL) return;

  /* check if the last request was a query, and if not in cache, put a message instead to avoid reloading a query again */
  if (((*history)->itemtype == '7') && ((*history)->cache == NULL)) {
    char *msg = "3Query not in cache\ni\niThis location is not available in the local cache. Gopherus is not reissuing custom queries automatically. If you wish to force a reload, press F5.\n";
    (*history)->cachesize = strlen(msg);
    (*history)->cache = malloc((*history)->cachesize + 1);
    if ((*history)->cache == NULL) { /* oops, out of memory! */
      (*history)->cachesize = 0;
      return;
    }
    memcpy((*history)->cache, msg, (*history)->cachesize + 1);
  }
}


/* adds a new node to the history list. Returns 0 on success, non-zero otherwise. */
int history_push(struct historytype **history, unsigned char protocol, const char *host, unsigned short port, char itemtype, const char *selector) {
  struct historytype *result;

  /* shortcut - if the new node is identical to the previous page, the user is doing a 'back' action */
  if (*history != NULL) { /* do we have any history at all? */
    if ((*history)->next != NULL) { /* is there a 'previous' position? */
      if (protocol == (*history)->next->protocol) { /* same protocol */
        if (strcasecmp(host, (*history)->next->host) == 0) { /* same host */
          if (port == (*history)->next->port) { /* same port */
            if (itemtype == (*history)->next->itemtype) { /* same itemtype */
              if (strcmp(selector, (*history)->next->selector) == 0) { /* same resource */
                history_pop(history); /* do a 'back' action in the history list */
                return(0);  /* return success */
              }
            }
          }
        }
      }
    }
  }
  /* add the node */
  result = malloc(sizeof(struct historytype) + strlen(host));
  if (result == NULL) return(-1);
  strcpy(result->host, host);
  result->protocol = protocol;
  result->port = port;
  result->itemtype = itemtype;
  result->displaymemory[0] = -1;
  result->displaymemory[1] = -1;
  result->selector = strdup(selector);
  if (result->selector == NULL) {
    free(result);
    return(-1);
  }
  result->cache = NULL;
  result->cachesize = 0;
  result->next = *history;
  *history = result;
  return(0);
}


/* free cache content past latest MAXALLOWEDCACHE bytes, also drop any
 * internal (embedded) gopherus pages */
void history_cleanupcache(struct historytype *history) {
  unsigned long totalcache = 0;
  for (; history != NULL; history = history->next) {
    totalcache += history->cachesize;
    if ((totalcache > MAXALLOWEDCACHE) || (history->host[0] == '#')) {
      if (history->cache != NULL) {
        free(history->cache);
        history->cache = NULL;
        history->cachesize = 0;
      }
    }
  }
}


/* flush all history, freeing memory (sets the history ptr to NULL) */
void history_clear(struct historytype **history) {
  struct historytype *victim;
  while (*history != NULL) {
    victim = *history;
    *history = (*history)->next;
    history_free_node(victim);
  }
}