/*
     This file is part of GNUnet
     Copyright (C) 2003-2013 GNUnet e.V.

     GNUnet is free software: you can redistribute it and/or modify it
     under the terms of the GNU Affero General Public License as published
     by the Free Software Foundation, either version 3 of the License,
     or (at your option) any later version.

     GNUnet 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
     Affero General Public License for more details.

     You should have received a copy of the GNU Affero General Public License
     along with this program.  If not, see <http://www.gnu.org/licenses/>.

     SPDX-License-Identifier: AGPL3.0-or-later
 */

/**
 * @file fs/fs_namespace.c
 * @brief publishing to namespaces, and tracking updateable entries
 *        for our namespaces
 * @author Christian Grothoff
 */
#include "platform.h"
#include "gnunet_constants.h"
#include "gnunet_signatures.h"
#include "gnunet_util_lib.h"

#include "gnunet_fs_service.h"
#include "fs_api.h"
#include "fs_publish_ublock.h"


/**
 * Information about an (updateable) node in the
 * namespace.
 */
struct NamespaceUpdateNode
{
  /**
   * Identifier for this node.
   */
  char *id;

  /**
   * Identifier of children of this node.
   */
  char *update;

  /**
   * Metadata for this entry.
   */
  struct GNUNET_FS_MetaData *md;

  /**
   * URI of this entry in the namespace.
   */
  struct GNUNET_FS_Uri *uri;

  /**
   * Namespace update generation ID.  Used to ensure
   * freshness of the tree_id.
   */
  unsigned int nug;

  /**
   * TREE this entry belongs to (if nug is current).
   */
  unsigned int tree_id;
};


/**
 * Handle to update information for a namespace.
 */
struct GNUNET_FS_UpdateInformationGraph
{
  /**
   * Handle to the FS service context.
   */
  struct GNUNET_FS_Handle *h;

  /**
   * Array with information about nodes in the namespace.
   */
  struct NamespaceUpdateNode **update_nodes;

  /**
   * Private key for the namespace.
   */
  struct GNUNET_CRYPTO_EcdsaPrivateKey ns;

  /**
   * Hash map mapping identifiers of update nodes
   * to the update nodes (initialized on-demand).
   */
  struct GNUNET_CONTAINER_MultiHashMap *update_map;

  /**
   * Size of the update nodes array.
   */
  unsigned int update_node_count;

  /**
   * Reference counter.
   */
  unsigned int rc;

  /**
   * Generator for unique nug numbers.
   */
  unsigned int nug_gen;
};


/**
 * Return the name of the directory in which we store
 * the update information graph for the given local namespace.
 *
 * @param h file-sharing handle
 * @param ns namespace handle
 * @return NULL on error, otherwise the name of the directory
 */
static char *
get_update_information_directory (
  struct GNUNET_FS_Handle *h,
  const struct GNUNET_CRYPTO_EcdsaPrivateKey *ns)
{
  char *dn;
  char *ret;
  struct GNUNET_CRYPTO_EcdsaPublicKey pub;
  struct GNUNET_HashCode hc;
  struct GNUNET_CRYPTO_HashAsciiEncoded enc;

  if (GNUNET_OK !=
      GNUNET_CONFIGURATION_get_value_filename (h->cfg, "FS", "UPDATE_DIR", &dn))
  {
    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, "fs", "UPDATE_DIR");
    return NULL;
  }
  GNUNET_CRYPTO_ecdsa_key_get_public (ns, &pub);
  GNUNET_CRYPTO_hash (&pub, sizeof(pub), &hc);
  GNUNET_CRYPTO_hash_to_enc (&hc, &enc);
  GNUNET_asprintf (&ret,
                   "%s%s%s",
                   dn,
                   DIR_SEPARATOR_STR,
                   (const char *) enc.encoding);
  GNUNET_free (dn);
  return ret;
}


/**
 * Release memory occupied by UIG datastructure.
 *
 * @param uig data structure to free
 */
static void
free_update_information_graph (struct GNUNET_FS_UpdateInformationGraph *uig)
{
  unsigned int i;
  struct NamespaceUpdateNode *nsn;

  for (i = 0; i < uig->update_node_count; i++)
  {
    nsn = uig->update_nodes[i];
    GNUNET_FS_meta_data_destroy (nsn->md);
    GNUNET_FS_uri_destroy (nsn->uri);
    GNUNET_free (nsn->id);
    GNUNET_free (nsn->update);
    GNUNET_free (nsn);
  }
  GNUNET_array_grow (uig->update_nodes, uig->update_node_count, 0);
  if (NULL != uig->update_map)
    GNUNET_CONTAINER_multihashmap_destroy (uig->update_map);
  GNUNET_free (uig);
}


/**
 * Write a namespace's update node graph to a file.
 *
 * @param uig update information graph to dump
 */
static void
write_update_information_graph (struct GNUNET_FS_UpdateInformationGraph *uig)
{
  char *fn;
  struct GNUNET_BIO_WriteHandle *wh;
  unsigned int i;
  struct NamespaceUpdateNode *n;
  char *uris;

  fn = get_update_information_directory (uig->h, &uig->ns);
  wh = GNUNET_BIO_write_open_file (fn);
  if (NULL == wh)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                _ ("Failed to open `%s' for writing: %s\n"),
                fn,
                strerror (errno));
    GNUNET_free (fn);
    return;
  }
  if (GNUNET_OK != GNUNET_BIO_write_int32 (wh,
                                           "fs-namespace-node-count",
                                           uig->update_node_count))
    goto END;
  for (i = 0; i < uig->update_node_count; i++)
  {
    n = uig->update_nodes[i];
    uris = GNUNET_FS_uri_to_string (n->uri);
    {
      struct GNUNET_BIO_WriteSpec ws[] = {
        GNUNET_BIO_write_spec_string ("fs-namespace-node-id", n->id),
        GNUNET_FS_write_spec_meta_data ("fs-namespace-node-meta", n->md),
        GNUNET_BIO_write_spec_string ("fs-namespace-node-update", n->update),
        GNUNET_BIO_write_spec_string ("fs-namespace-uris", uris),
        GNUNET_BIO_write_spec_end (),
      };
      if (GNUNET_OK != GNUNET_BIO_write_spec_commit (wh, ws))
      {
        GNUNET_free (uris);
        break;
      }
    }
    GNUNET_free (uris);
  }
END:
  if (GNUNET_OK != GNUNET_BIO_write_close (wh, NULL))
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                _ ("Failed to write `%s': %s\n"),
                fn,
                strerror (errno));
  GNUNET_free (fn);
}


/**
 * Read the namespace update node graph from a file.
 *
 * @param h FS handle to use
 * @param ns namespace to read
 * @return update graph, never NULL
 */
static struct GNUNET_FS_UpdateInformationGraph *
read_update_information_graph (struct GNUNET_FS_Handle *h,
                               const struct GNUNET_CRYPTO_EcdsaPrivateKey *ns)
{
  struct GNUNET_FS_UpdateInformationGraph *uig;
  char *fn;
  struct GNUNET_BIO_ReadHandle *rh;
  unsigned int i;
  struct NamespaceUpdateNode *n;
  char *uris;
  uint32_t count;
  char *emsg;

  uig = GNUNET_new (struct GNUNET_FS_UpdateInformationGraph);
  uig->h = h;
  uig->ns = *ns;
  fn = get_update_information_directory (h, ns);
  if (GNUNET_YES != GNUNET_DISK_file_test (fn))
  {
    GNUNET_free (fn);
    return uig;
  }
  rh = GNUNET_BIO_read_open_file (fn);
  if (NULL == rh)
  {
    GNUNET_free (fn);
    return uig;
  }
  if (GNUNET_OK != GNUNET_BIO_read_int32 (rh, "fs-namespace-count",
                                          (int32_t *) &count))
  {
    GNUNET_break (0);
    goto END;
  }
  if (count > 1024 * 1024)
  {
    GNUNET_break (0);
    goto END;
  }
  if (0 == count)
    goto END;
  uig->update_nodes =
    GNUNET_malloc (count * sizeof(struct NamespaceUpdateNode *));

  for (i = 0; i < count; i++)
  {
    n = GNUNET_new (struct NamespaceUpdateNode);
    {
      struct GNUNET_BIO_ReadSpec rs[] = {
        GNUNET_BIO_read_spec_string ("identifier", &n->id, 1024),
        GNUNET_FS_read_spec_meta_data ("meta", &n->md),
        GNUNET_BIO_read_spec_string ("update-id", &n->update, 1024),
        GNUNET_BIO_read_spec_string ("uri", &uris, 1024 * 2),
        GNUNET_BIO_read_spec_end (),
      };
      if (GNUNET_OK != GNUNET_BIO_read_spec_commit (rh, rs))
      {
        GNUNET_break (0);
        GNUNET_free (n->id);
        GNUNET_free (n->update);
        if (n->md != NULL)
          GNUNET_FS_meta_data_destroy (n->md);
        GNUNET_free (n);
        break;
      }
    }
    n->uri = GNUNET_FS_uri_parse (uris, &emsg);
    GNUNET_free (uris);
    if (n->uri == NULL)
    {
      GNUNET_break (0);
      GNUNET_free (emsg);
      GNUNET_free (n->id);
      GNUNET_free (n->update);
      GNUNET_FS_meta_data_destroy (n->md);
      GNUNET_free (n);
      break;
    }
    uig->update_nodes[i] = n;
  }
  uig->update_node_count = i;
END:
  if (GNUNET_OK != GNUNET_BIO_read_close (rh, &emsg))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                _ ("Failed to read `%s': %s\n"),
                fn,
                emsg);
    GNUNET_free (emsg);
  }
  GNUNET_free (fn);
  return uig;
}


/**
 * Context for the SKS publication.
 */
struct GNUNET_FS_PublishSksContext
{
  /**
   * URI of the new entry in the namespace.
   */
  struct GNUNET_FS_Uri *uri;

  /**
   * Namespace update node to add to namespace on success (or to be
   * deleted if publishing failed).
   */
  struct NamespaceUpdateNode *nsn;

  /**
   * Namespace we're publishing to.
   */
  struct GNUNET_CRYPTO_EcdsaPrivateKey ns;

  /**
   * Handle to the datastore.
   */
  struct GNUNET_DATASTORE_Handle *dsh;

  /**
   * Handle to FS.
   */
  struct GNUNET_FS_Handle *h;

  /**
   * Function to call once we're done.
   */
  GNUNET_FS_PublishContinuation cont;

  /**
   * Closure for cont.
   */
  void *cont_cls;

  /**
   * Handle for our UBlock operation request.
   */
  struct GNUNET_FS_PublishUblockContext *uc;
};


/**
 * Function called by the UBlock construction with
 * the result from the PUT (UBlock) request.
 *
 * @param cls closure of type "struct GNUNET_FS_PublishSksContext*"
 * @param msg error message (or NULL)
 */
static void
sks_publish_cont (void *cls, const char *msg)
{
  struct GNUNET_FS_PublishSksContext *psc = cls;
  struct GNUNET_FS_UpdateInformationGraph *uig;

  psc->uc = NULL;
  if (NULL != msg)
  {
    if (NULL != psc->cont)
      psc->cont (psc->cont_cls, NULL, msg);
    GNUNET_FS_publish_sks_cancel (psc);
    return;
  }
  if (NULL != psc->nsn)
  {
    /* FIXME: this can be done much more
     * efficiently by simply appending to the
     * file and overwriting the 4-byte header */
    uig = read_update_information_graph (psc->h, &psc->ns);
    GNUNET_array_append (uig->update_nodes, uig->update_node_count, psc->nsn);
    psc->nsn = NULL;
    write_update_information_graph (uig);
    free_update_information_graph (uig);
  }
  if (NULL != psc->cont)
    psc->cont (psc->cont_cls, psc->uri, NULL);
  GNUNET_FS_publish_sks_cancel (psc);
}


struct GNUNET_FS_PublishSksContext *
GNUNET_FS_publish_sks (struct GNUNET_FS_Handle *h,
                       const struct GNUNET_CRYPTO_EcdsaPrivateKey *ns,
                       const char *identifier,
                       const char *update,
                       const struct GNUNET_FS_MetaData *meta,
                       const struct GNUNET_FS_Uri *uri,
                       const struct GNUNET_FS_BlockOptions *bo,
                       enum GNUNET_FS_PublishOptions options,
                       GNUNET_FS_PublishContinuation cont,
                       void *cont_cls)
{
  struct GNUNET_FS_PublishSksContext *psc;
  struct GNUNET_FS_Uri *sks_uri;

  sks_uri = GNUNET_new (struct GNUNET_FS_Uri);
  sks_uri->type = GNUNET_FS_URI_SKS;
  sks_uri->data.sks.identifier = GNUNET_strdup (identifier);
  GNUNET_CRYPTO_ecdsa_key_get_public (ns, &sks_uri->data.sks.ns);

  psc = GNUNET_new (struct GNUNET_FS_PublishSksContext);
  psc->h = h;
  psc->uri = sks_uri;
  psc->cont = cont;
  psc->cont_cls = cont_cls;
  psc->ns = *ns;
  if (0 == (options & GNUNET_FS_PUBLISH_OPTION_SIMULATE_ONLY))
  {
    psc->dsh = GNUNET_DATASTORE_connect (h->cfg);
    if (NULL == psc->dsh)
    {
      sks_publish_cont (psc, _ ("Failed to connect to datastore."));
      return NULL;
    }
  }
  if (NULL != update)
  {
    psc->nsn = GNUNET_new (struct NamespaceUpdateNode);
    psc->nsn->id = GNUNET_strdup (identifier);
    psc->nsn->update = GNUNET_strdup (update);
    psc->nsn->md = GNUNET_FS_meta_data_duplicate (meta);
    psc->nsn->uri = GNUNET_FS_uri_dup (uri);
  }
  psc->uc = GNUNET_FS_publish_ublock_ (h,
                                       psc->dsh,
                                       identifier,
                                       update,
                                       ns,
                                       meta,
                                       uri,
                                       bo,
                                       options,
                                       &sks_publish_cont,
                                       psc);
  return psc;
}


/**
 * Abort the SKS publishing operation.
 *
 * @param psc context of the operation to abort.
 */
void
GNUNET_FS_publish_sks_cancel (struct GNUNET_FS_PublishSksContext *psc)
{
  if (NULL != psc->uc)
  {
    GNUNET_FS_publish_ublock_cancel_ (psc->uc);
    psc->uc = NULL;
  }
  if (NULL != psc->dsh)
  {
    GNUNET_DATASTORE_disconnect (psc->dsh, GNUNET_NO);
    psc->dsh = NULL;
  }
  GNUNET_FS_uri_destroy (psc->uri);
  if (NULL != psc->nsn)
  {
    GNUNET_FS_meta_data_destroy (psc->nsn->md);
    GNUNET_FS_uri_destroy (psc->nsn->uri);
    GNUNET_free (psc->nsn->id);
    GNUNET_free (psc->nsn->update);
    GNUNET_free (psc->nsn);
  }
  GNUNET_free (psc);
}


/**
 * Closure for 'process_update_node'.
 */
struct ProcessUpdateClosure
{
  /**
   * Function to call for each node.
   */
  GNUNET_FS_IdentifierProcessor ip;

  /**
   * Closure for 'ip'.
   */
  void *ip_cls;
};


/**
 * Call the iterator in the closure for each node.
 *
 * @param cls closure (of type 'struct ProcessUpdateClosure *')
 * @param key current key code
 * @param value value in the hash map (of type 'struct NamespaceUpdateNode *')
 * @return GNUNET_YES if we should continue to
 *         iterate,
 *         GNUNET_NO if not.
 */
static int
process_update_node (void *cls, const struct GNUNET_HashCode *key, void *value)
{
  struct ProcessUpdateClosure *pc = cls;
  struct NamespaceUpdateNode *nsn = value;

  pc->ip (pc->ip_cls, nsn->id, nsn->uri, nsn->md, nsn->update);
  return GNUNET_YES;
}


/**
 * Closure for 'find_trees'.
 */
struct FindTreeClosure
{
  /**
   * UIG we are operating on.
   */
  struct GNUNET_FS_UpdateInformationGraph *uig;

  /**
   * Array with 'head's of TREEs.
   */
  struct NamespaceUpdateNode **tree_array;

  /**
   * Size of 'tree_array'
   */
  unsigned int tree_array_size;

  /**
   * Current generational ID used.
   */
  unsigned int nug;

  /**
   * Identifier for the current TREE, or UINT_MAX for none yet.
   */
  unsigned int id;
};


/**
 * Find all nodes reachable from the current node (including the
 * current node itself).  If they are in no tree, add them to the
 * current one.   If they are the head of another tree, merge the
 * trees.  If they are in the middle of another tree, let them be.
 * We can tell that a node is already in an tree by checking if
 * its 'nug' field is set to the current 'nug' value.  It is the
 * head of an tree if it is in the 'tree_array' under its respective
 * 'tree_id'.
 *
 * In short, we're trying to find the smallest number of tree to
 * cover a directed graph.
 *
 * @param cls closure (of type 'struct FindTreeClosure')
 * @param key current key code
 * @param value value in the hash map
 * @return GNUNET_YES if we should continue to
 *         iterate,
 *         GNUNET_NO if not.
 */
static int
find_trees (void *cls, const struct GNUNET_HashCode *key, void *value)
{
  struct FindTreeClosure *fc = cls;
  struct NamespaceUpdateNode *nsn = value;
  struct GNUNET_HashCode hc;

  if (nsn->nug == fc->nug)
  {
    if (UINT_MAX == nsn->tree_id)
      return GNUNET_YES;   /* circular */
    GNUNET_assert (nsn->tree_id < fc->tree_array_size);
    if (fc->tree_array[nsn->tree_id] != nsn)
      return GNUNET_YES;   /* part of "another" (directed) TREE,
                            * and not root of it, end trace */
    if (nsn->tree_id == fc->id)
      return GNUNET_YES;   /* that's our own root (can this be?) */
    /* merge existing TREE, we have a root for both */
    fc->tree_array[nsn->tree_id] = NULL;
    if (UINT_MAX == fc->id)
      fc->id = nsn->tree_id;   /* take over ID */
  }
  else
  {
    nsn->nug = fc->nug;
    nsn->tree_id = UINT_MAX;   /* mark as undef */
    /* trace */
    GNUNET_CRYPTO_hash (nsn->update, strlen (nsn->update), &hc);
    GNUNET_CONTAINER_multihashmap_get_multiple (fc->uig->update_map,
                                                &hc,
                                                &find_trees,
                                                fc);
  }
  return GNUNET_YES;
}


/**
 * List all of the identifiers in the namespace for which we could
 * produce an update.  Namespace updates form a graph where each node
 * has a name.  Each node can have any number of URI/meta-data entries
 * which can each be linked to other nodes.  Cycles are possible.
 *
 * Calling this function with "next_id" NULL will cause the library to
 * call "ip" with a root for each strongly connected component of the
 * graph (a root being a node from which all other nodes in the Tree
 * are reachable).
 *
 * Calling this function with "next_id" being the name of a node will
 * cause the library to call "ip" with all children of the node.  Note
 * that cycles within the final tree are possible (including self-loops).
 * I know, odd definition of a tree, but the GUI will display an actual
 * tree (GtkTreeView), so that's what counts for the term here.
 *
 * @param h fs handle to use
 * @param ns namespace to inspect for updateable content
 * @param next_id ID to look for; use NULL to look for tree roots
 * @param ip function to call on each updateable identifier
 * @param ip_cls closure for ip
 */
void
GNUNET_FS_namespace_list_updateable (
  struct GNUNET_FS_Handle *h,
  const struct GNUNET_CRYPTO_EcdsaPrivateKey *ns,
  const char *next_id,
  GNUNET_FS_IdentifierProcessor ip,
  void *ip_cls)
{
  unsigned int i;
  unsigned int nug;
  struct GNUNET_HashCode hc;
  struct NamespaceUpdateNode *nsn;
  struct ProcessUpdateClosure pc;
  struct FindTreeClosure fc;
  struct GNUNET_FS_UpdateInformationGraph *uig;

  uig = read_update_information_graph (h, ns);
  if (NULL == uig->update_nodes)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
                "No updateable nodes found for ID `%s'\n",
                next_id);
    free_update_information_graph (uig);
    return;   /* no nodes */
  }
  uig->update_map =
    GNUNET_CONTAINER_multihashmap_create (2 + 3 * uig->update_node_count / 4,
                                          GNUNET_NO);
  for (i = 0; i < uig->update_node_count; i++)
  {
    nsn = uig->update_nodes[i];
    GNUNET_CRYPTO_hash (nsn->id, strlen (nsn->id), &hc);
    GNUNET_CONTAINER_multihashmap_put (
      uig->update_map,
      &hc,
      nsn,
      GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
  }
  if (NULL != next_id)
  {
    GNUNET_CRYPTO_hash (next_id, strlen (next_id), &hc);
    pc.ip = ip;
    pc.ip_cls = ip_cls;
    GNUNET_CONTAINER_multihashmap_get_multiple (uig->update_map,
                                                &hc,
                                                &process_update_node,
                                                &pc);
    free_update_information_graph (uig);
    return;
  }
  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
              "Calculating TREEs to find roots of update trees\n");
  /* Find heads of TREEs in update graph */
  nug = ++uig->nug_gen;
  fc.tree_array = NULL;
  fc.tree_array_size = 0;

  for (i = 0; i < uig->update_node_count; i++)
  {
    nsn = uig->update_nodes[i];
    if (nsn->nug == nug)
    {
      GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
                  "TREE of node `%s' is %u\n",
                  nsn->id,
                  nsn->nug);
      continue;     /* already placed in TREE */
    }
    GNUNET_CRYPTO_hash (nsn->update, strlen (nsn->update), &hc);
    nsn->nug = nug;
    nsn->tree_id = UINT_MAX;
    fc.id = UINT_MAX;
    fc.nug = nug;
    fc.uig = uig;
    GNUNET_CONTAINER_multihashmap_get_multiple (uig->update_map,
                                                &hc,
                                                &find_trees,
                                                &fc);
    if (UINT_MAX == fc.id)
    {
      /* start new TREE */
      for (fc.id = 0; fc.id < fc.tree_array_size; fc.id++)
      {
        if (NULL == fc.tree_array[fc.id])
        {
          fc.tree_array[fc.id] = nsn;
          nsn->tree_id = fc.id;
          break;
        }
      }
      if (fc.id == fc.tree_array_size)
      {
        GNUNET_array_append (fc.tree_array, fc.tree_array_size, nsn);
        nsn->tree_id = fc.id;
      }
      GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
                  "Starting new TREE %u with node `%s'\n",
                  nsn->tree_id,
                  nsn->id);
      /* put all nodes with same identifier into this TREE */
      GNUNET_CRYPTO_hash (nsn->id, strlen (nsn->id), &hc);
      fc.id = nsn->tree_id;
      fc.nug = nug;
      fc.uig = uig;
      GNUNET_CONTAINER_multihashmap_get_multiple (uig->update_map,
                                                  &hc,
                                                  &find_trees,
                                                  &fc);
    }
    else
    {
      /* make head of TREE "id" */
      fc.tree_array[fc.id] = nsn;
      nsn->tree_id = fc.id;
    }
    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
                "TREE of node `%s' is %u\n",
                nsn->id,
                fc.id);
  }
  for (i = 0; i < fc.tree_array_size; i++)
  {
    nsn = fc.tree_array[i];
    if (NULL != nsn)
    {
      GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
                  "Root of TREE %u is node `%s'\n",
                  i,
                  nsn->id);
      ip (ip_cls, nsn->id, nsn->uri, nsn->md, nsn->update);
    }
  }
  GNUNET_array_grow (fc.tree_array, fc.tree_array_size, 0);
  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Done processing TREEs\n");
  free_update_information_graph (uig);
}


/* end of fs_namespace.c */
