/**
 * @file dep_list.c
 *
 * @copyright Copyright  (C)  2013 Moritz Hanke <hanke@dkrz.de>
 *                                 Rene Redler <rene.redler@mpimet.mpg.de>
 *
 * @version 1.0
 * @author Moritz Hanke <hanke@dkrz.de>
 *         Rene Redler <rene.redler@mpimet.mpg.de>
 */
/*
 * Keywords:
 * Maintainer: Moritz Hanke <hanke@dkrz.de>
 *             Rene Redler <rene.redler@mpimet.mpg.de>
 * URL: https://doc.redmine.dkrz.de/YAC/html/index.html
 *
 * This file is part of YAC.
 *
 * YAC is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * YAC 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with YAC.  If not, see <http://www.gnu.org/licenses/gpl.txt>.
 */

#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <stdint.h>

#include "dep_list.h"
#include "utils.h"
#include "ensure_array_size.h"
#include "communicator.h"

/** \brief dependency list
 *
 * data structure for storing and working with dependencies between unsigned integer data\n
 * possible applications of a dep_list are:
 *    - the corner ids for all cells (cell 0 -> corners 0, 1, 4, 3; cell 1 -> corners 1, 2, 5, 4; 2 -> 3, 4 , 7, 6; 3 -> 4, 5, 8, 7)
 *    - neighbour cell ids (cell 0 -> neighbours 1, 2, 3; cell 1 -> neighbours 0, 2, 3; 2 -> 0, 1, 3; 3 -> 0, 1, 2)
 *
 * The element index (in the example the cell index) goes from 0 to n-1, where n is the total number of elements in the dependency list.\n
 * The dependencies can have any valid unsigned integer number. However, one has to be careful when using \ref yac_invert_dep_list, because if the values of the dependencies are to big the resulting dependency list can be big.
 */
struct dep_list {
   size_t num_elements;           //!< total number of elements in the dependency list
   size_t * num_deps_per_element; //!< array containing the number of dependencies per element
   unsigned * dependencies;       //!< array containing all dependencies

   /**
    * \brief exclusive prefix sum for faster access of dependencies array
    *
    * this array is automatically generated and contains a exclusive prefix sum\n
    *   [0]=0, [1]=num_dep[0], [2]=num_dep[0]+num_dep[1], ...\n
    * see: http://en.wikipedia.org/wiki/Prefix_sum
    */
   size_t * prescan;
};

static void generate_prescan (struct dep_list * list) {

   size_t num_total_deps = 0;

   list->prescan = malloc(list->num_elements * sizeof(list->prescan[0]));

   for (size_t i = 0; i < list->num_elements; ++i) {

      list->prescan[i] = num_total_deps;
      num_total_deps += list->num_deps_per_element[i];
   }
}

struct dep_list * yac_dep_list_new(
  size_t num_elements, size_t * num_deps_per_element, unsigned * dependencies) {

  struct dep_list * list = malloc(1 * sizeof(*list));

  list->num_elements         = num_elements;
  list->num_deps_per_element = num_deps_per_element;
  list->dependencies         = dependencies;

  generate_prescan(list);

  return list;
}

size_t yac_get_num_elements(struct dep_list * list) {

  if (list == NULL) return 0;

  return list->num_elements;
}

void yac_add_dependencies (struct dep_list * list, unsigned element,
                           size_t num_dependencies, unsigned * dependencies) {

   if (list == NULL) return;

   if ((list->num_elements == 0) || (list->num_elements <= (size_t)element))
      yac_internal_abort_message(
         "ERROR: Wrong umber of elements in list out of range",
         __FILE__, __LINE__);

   size_t old_total_num_deps = yac_get_total_num_dependencies(list);
   size_t new_size = old_total_num_deps + num_dependencies;

   unsigned * new_dependencies = malloc(new_size * sizeof(*new_dependencies));

   size_t copy_size =
    list->prescan[element] + list->num_deps_per_element[element];

   memcpy(new_dependencies, list->dependencies,
          copy_size * sizeof(*new_dependencies));

   memcpy(new_dependencies + copy_size, dependencies,
          num_dependencies * sizeof(*new_dependencies));

   size_t offset = copy_size + num_dependencies;

   memcpy(new_dependencies + offset, list->dependencies + copy_size,
          (old_total_num_deps - copy_size) * sizeof(*new_dependencies));

   free(list->dependencies);

   list->dependencies = new_dependencies;

   list->num_deps_per_element[element] += num_dependencies;

   for (size_t i = (size_t)(element+1); i < list->num_elements; ++i)
      list->prescan[i] += num_dependencies;
}

struct dep_list * yac_invert_dep_list(struct dep_list * list) {

   if ((list == NULL) || (list->num_elements == 0)) return NULL;

   // compute the total number of dependencies in list
   size_t num_total_deps = 0;
   for (size_t i = 0; i < list->num_elements; ++i)
      num_total_deps += list->num_deps_per_element[i];

   if (num_total_deps == 0) return NULL;

   struct dep_list * inv_list = malloc(1 * sizeof(*list));

   // determine the maximal index in the dependency list of list
   unsigned max_index = list->dependencies[0];
   for (size_t i = 1; i < num_total_deps; ++i)
      if (list->dependencies[i] > max_index) max_index = list->dependencies[i];

   inv_list->num_elements = (size_t)(max_index+1);
   inv_list->num_deps_per_element =
      calloc (max_index+1, sizeof (inv_list->num_deps_per_element[0]));

   // compute the number of dependencies per element in dep_inv
   unsigned const * curr_element_deps = list->dependencies;
   num_total_deps = 0;
   for (size_t i = 0; i < list->num_elements; ++i) {

      for (size_t j = 0; j < list->num_deps_per_element[i]; ++j) {

         ++inv_list->num_deps_per_element[curr_element_deps[j]];
      }
      num_total_deps += list->num_deps_per_element[i];
      curr_element_deps += list->num_deps_per_element[i];
   }

   // generate prescan data
   generate_prescan(inv_list);

   // set the dependencies
   inv_list->dependencies =
      malloc (num_total_deps * sizeof (inv_list->dependencies[0]));
   size_t * curr_num_deps_per_element =
      calloc (inv_list->num_elements, sizeof(*curr_num_deps_per_element));
   curr_element_deps = list->dependencies;
   for (size_t i = 0; i < list->num_elements; ++i) {

      for (size_t j = 0; j < list->num_deps_per_element[i]; ++j) {

         inv_list->dependencies[
            inv_list->prescan[curr_element_deps[j]] + 
            curr_num_deps_per_element[curr_element_deps[j]]] = (unsigned)i;

         ++curr_num_deps_per_element[curr_element_deps[j]];
      }
      curr_element_deps += list->num_deps_per_element[i];
   }

   free (curr_num_deps_per_element);

   return inv_list;
}

size_t yac_get_num_dependencies_of_element(
  struct dep_list * list, size_t index) {

   if (list == NULL) return 0;

  return list->num_deps_per_element[index];
}

unsigned const * yac_get_dependencies_of_element(
   struct dep_list * list, size_t index) {

   assert(list != NULL);

   return list->dependencies + list->prescan[index];
}

size_t yac_get_total_num_dependencies(struct dep_list * list) {

   if ((list == NULL) || (list->num_elements == 0))
      return 0;
   else
      return
         list->prescan[list->num_elements-1] +
         list->num_deps_per_element[list->num_elements-1];
}

size_t yac_get_dependency_index(
   struct dep_list * list, size_t index, unsigned dependency) {

   if ((list == NULL) || (index >= list->num_elements)) return -1;
   
   for (size_t i = 0; i < list->num_deps_per_element[index]; ++i)
      if (list->dependencies[list->prescan[index] + i] == dependency)
         return list->prescan[index] + i;

   return (size_t)-1;
}

size_t yac_get_dependency_offset(struct dep_list * list, size_t index) {

   assert(list != NULL);

   return list->prescan[index];
}

int yac_list_contains_dependency(
   struct dep_list * list, unsigned dependency) {

   if (list == NULL) return 1 == 0;

   size_t num_dependencies = yac_get_total_num_dependencies(list);

   for (size_t i = 0; i < num_dependencies; ++i)
      if (list->dependencies[i] == dependency)
         return 1 == 1;

   return 1 == 0;
}

void yac_get_dependency(
   struct dep_list * list, size_t dep_index, size_t * index,
   unsigned * dependency) {

   size_t i;

   if ((list == NULL) ||(dep_index >= yac_get_total_num_dependencies(list))) {
      *index = (size_t)-1;
      *dependency = -1;
      return;
   }
   
   for (i = 1; i < list->num_elements; ++i) {

      if (list->prescan[i] > dep_index) break;
   }

   *index = i-1;
   *dependency = list->dependencies[dep_index];
}

struct dep_list * yac_copy_dep_list(struct dep_list * list) {

   if (list == NULL) return NULL;

   if (list->num_elements == 0)
      return yac_dep_list_new(0, NULL, NULL);

   size_t num_total_deps =
      list->prescan[list->num_elements-1] +
      list->num_deps_per_element[list->num_elements-1];

   size_t * num_deps_per_element =
      malloc(list->num_elements * sizeof(*num_deps_per_element));
   unsigned * dependencies = malloc(num_total_deps * sizeof(*dependencies));

   memcpy(num_deps_per_element, list->num_deps_per_element,
          list->num_elements * sizeof(*num_deps_per_element));
   memcpy(dependencies, list->dependencies, 
          num_total_deps * sizeof(*dependencies));

   return yac_dep_list_new(
      list->num_elements, num_deps_per_element, dependencies);
}

static void yac_pack_dep_list_uint64(
  uint64_t num_elements, uint64_t * num_deps_per_element,
  unsigned * dependencies, int total_num_dependencies, void * buffer,
  int buffer_size, int * position, struct communicator * comm) {

  yac_comm_pack(&num_elements, 1, COMM_UINT64_T,
                buffer, buffer_size, position, comm);
  yac_comm_pack(num_deps_per_element, num_elements, COMM_UINT64_T,
                buffer, buffer_size, position, comm);
  yac_comm_pack(dependencies, total_num_dependencies, COMM_UINT,
                buffer, buffer_size, position, comm);
}

void yac_pack_dep_list(struct dep_list * list, void * buffer, int buffer_size,
                       int * position, struct communicator * comm) {

  if ((list == NULL) || (list->num_elements == 0)) {

    uint64_t num_elements = 0;
    yac_comm_pack(&num_elements, 1, COMM_UINT64_T,
                  buffer, buffer_size, position, comm);

  } else if (sizeof(size_t) == sizeof(uint64_t)) {

    yac_pack_dep_list_uint64(
      (uint64_t)list->num_elements, (uint64_t*)list->num_deps_per_element,
      list->dependencies, (int)yac_get_total_num_dependencies(list),
      buffer, buffer_size, position, comm);

  } else {

    uint64_t * num_deps_per_element_uint64_t =
      malloc(list->num_elements * sizeof(*num_deps_per_element_uint64_t));
    for (size_t i = 0; i < list->num_elements; ++i)
      num_deps_per_element_uint64_t[i] =
        (uint64_t)list->num_deps_per_element[i];

    yac_pack_dep_list_uint64(
      (uint64_t)list->num_elements, num_deps_per_element_uint64_t,
      list->dependencies, (int)yac_get_total_num_dependencies(list),
      buffer, buffer_size, position, comm);

    free(num_deps_per_element_uint64_t);
  }
}

struct dep_list * yac_unpack_dep_list(
  void * buffer, int buffer_size, int * position, struct communicator * comm) {

  uint64_t num_elements;
  yac_comm_unpack(buffer, buffer_size, position, &num_elements,
                  1, COMM_UINT64_T, comm);

  if (num_elements == 0) return yac_dep_list_new(0, NULL, NULL);

  uint64_t * num_deps_per_element =
    malloc(num_elements * sizeof(*num_deps_per_element));
  yac_comm_unpack(buffer, buffer_size, position, num_deps_per_element,
                  (int)num_elements, COMM_UINT64_T, comm);

  uint64_t total_num_dependencies = 0;
  for (uint64_t i = 0; i < num_elements; ++i)
    total_num_dependencies += num_deps_per_element[i];

  unsigned * dependencies =
    malloc(total_num_dependencies * sizeof(*dependencies));

  yac_comm_unpack(buffer, buffer_size, position, dependencies,
                  (int)total_num_dependencies, COMM_UINT, comm);

  if (sizeof(uint64_t) == sizeof(size_t)) {

    return
      yac_dep_list_new(
        (size_t)num_elements, (size_t*)num_deps_per_element, dependencies);

  } else {

    size_t * num_deps_per_element_size_t =
      malloc(num_elements * sizeof(*num_deps_per_element_size_t));

    for (uint64_t i = 0; i < num_elements; ++i)
      num_deps_per_element_size_t[i] = (size_t)num_deps_per_element[i];

    struct dep_list * list = 
      yac_dep_list_new(
        (size_t)num_elements, num_deps_per_element_size_t, dependencies);

    free(num_deps_per_element);

    return list;
  }
}

int yac_dep_list_pack_size(struct dep_list * list, struct communicator * comm) {

   if (list == NULL) return yac_comm_pack_size(1, COMM_UINT64_T, comm);

  int num_elements = (int)list->num_elements;
  int total_num_dependencies = (int)yac_get_total_num_dependencies(list);

  int pack_size = yac_comm_pack_size(num_elements + 1, COMM_UINT64_T, comm);
  pack_size    += yac_comm_pack_size(total_num_dependencies, COMM_UINT, comm);

  return pack_size;
}

void yac_dep_list_delete(struct dep_list * list) {

  if (list == NULL) return;

  free(list->num_deps_per_element);
  free(list->dependencies);
  free(list->prescan);
  free(list);
}

void yac_remove_dependencies_of_elements(
   struct dep_list * dep, size_t * element_indices, size_t num_elements) {

   if (dep == NULL) return;

   size_t * old_prescan = dep->prescan;

   for (size_t i = 0; i < num_elements; ++ i) {

      if (element_indices[i] == (size_t)-1) continue;

      dep->num_deps_per_element[element_indices[i]] = 0;
   }

   generate_prescan(dep);

   unsigned * curr_dep;

   curr_dep = dep->dependencies;

   for (size_t i = 0; i < dep->num_elements; ++i) {

      for (size_t j = 0; j < dep->num_deps_per_element[i]; ++j) {

         *curr_dep = dep->dependencies[old_prescan[i]+j];
         ++curr_dep;
      }
   }

   dep->dependencies =
      realloc(dep->dependencies,
              yac_get_total_num_dependencies(dep) *
              sizeof(dep->dependencies[0]));

   free(old_prescan);
}

void yac_remove_dependencies(
   struct dep_list * dep, unsigned * dependencies, size_t num_dependencies) {

   if (dep == NULL) return;

   size_t num_total_deps = yac_get_total_num_dependencies(dep);

   for (size_t j = 0, l = 0; j < dep->num_elements; ++j) {
      size_t curr_num_deps_per_element = dep->num_deps_per_element[j];
      for (size_t k = 0; k < curr_num_deps_per_element; ++k, ++l) {

         for (size_t i = 0; i < num_dependencies; ++i) {

            if (dependencies[i] == (unsigned)-1)
               continue;

            if (dep->dependencies[l] == dependencies[i]) {

               dep->num_deps_per_element[j]--;
               dep->dependencies[l] = (unsigned)-1;
            }
         }
      }
   }

   unsigned * curr_dep;

   curr_dep = dep->dependencies;

   for (size_t i = 0; i < num_total_deps; ++i) {

      if (dep->dependencies[i] != (unsigned)-1) {

         *curr_dep = dep->dependencies[i];
         ++curr_dep;
      }
   }

   free (dep->prescan);
   generate_prescan(dep);

   dep->dependencies =
      realloc(dep->dependencies,
              yac_get_total_num_dependencies(dep) *
              sizeof(dep->dependencies[0]));
}

