/*******************************************************************************
 * This file is part of SWIFT.
 * Copyright (c) 2021 John Helly (j.c.helly@durham.ac.uk)
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published
 * by the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 ******************************************************************************/

#ifndef SWIFT_LIGHTCONE_SHELL_H
#define SWIFT_LIGHTCONE_SHELL_H

/* Standard headers */
#include <stdio.h>

/* Config parameters. */
#include <config.h>

/* Local headers */
#include "cosmology.h"
#include "error.h"
#include "lightcone/lightcone_map.h"
#include "lightcone/lightcone_map_types.h"
#include "lightcone/pixel_index.h"
#include "lightcone/projected_kernel.h"
#include "particle_buffer.h"

enum lightcone_shell_state {
  shell_uninitialized,
  shell_current,
  shell_complete,
};

union lightcone_map_buffer_entry {
  int i;
  float f;
};

/**
 * @brief Encode an angle in the range 0 to 2pi as an int
 *
 * @param angle the angle to encode
 */
__attribute__((always_inline)) INLINE static int angle_to_int(
    const double angle) {

  if (angle < 0.0 || angle > 2 * M_PI) error("angle is out of range!");
  const double fac = ((1 << 30) - 1) / M_PI;
  return (int)(angle * fac);
}

/**
 * @brief Convert an encoded angle back to a double
 *
 * @param i the int containing the angle
 */
__attribute__((always_inline)) INLINE static double int_to_angle(const int i) {

  const double fac = M_PI / ((1 << 30) - 1);
  return i * fac;
}

/**
 * @brief Information about a particle type contributing to the lightcone
 *
 * For each Swift particle type we store how many lightcone maps that type
 * contributes to and their indexes in the array of lightcone_maps structs
 * associated with each lightcone_shell.
 *
 * We also record the number of bytes needed to store one update: updates
 * consist of the angular coordinates of the particle, its angular smoothing
 * radius, and the quantities contributed to the lightcone maps.
 *
 */
struct lightcone_particle_type {

  /*! Number of lightcone maps this particle type contributes to */
  int nr_maps;

  /*! Number of smoothed this particle type contributes to */
  int nr_smoothed_maps;

  /*! Number of un-smoothed this particle type contributes to */
  int nr_unsmoothed_maps;

  /*! Indices of the lightcone maps this particle type contributes to.
    Smoothed maps will be stored first in the array. */
  int *map_index;

  /*! Amount of data to store per particle: theta, phi, radius and the value to
   * add to each healpix map */
  size_t buffer_element_size;
};

/**
 * @brief Information about each lightcone shell
 *
 * Each shell contains one lightcone_map for each healpix map
 * we're making. This is where the pixel data is stored while
 * the current simulation timestep overlaps the shell's redshift
 * range.
 *
 * Each shell also contains one particle_buffer per particle type,
 * which stores the updates to be applied to the pixel data.
 * Updates are accumulated in the buffers during each time step
 * and applied at the end of the step.
 *
 */
struct lightcone_shell {

  /*! State of this shell */
  enum lightcone_shell_state state;

  /*! Inner radius of shell */
  double rmin;

  /*! Outer radius of shell */
  double rmax;

  /*! Minimum expansion factor for this shell */
  double amin;

  /*! Maximum expansion factor for this shell */
  double amax;

  /*! Number of maps associated with this shell */
  int nr_maps;

  /*! Array of lightcone maps for this shell */
  struct lightcone_map *map;

  /*! Buffers to store the map updates for each particle type */
  struct particle_buffer buffer[swift_type_count];

  /*! Healpix nside parameter */
  int nside;

  /*! Total pixels in the maps */
  pixel_index_t total_nr_pix;

  /*! Number of pixels per map stored on this node */
  pixel_index_t local_nr_pix;

  /*! Offset of the first pixel stored on this rank */
  pixel_index_t local_pix_offset;

  /*! Number of pixels per rank (last node has any extra) */
  pixel_index_t pix_per_rank;
};

struct lightcone_shell *lightcone_shell_array_init(
    const struct cosmology *cosmo, const char *radius_file, int nr_maps,
    struct lightcone_map_type *map_type, int nside, pixel_index_t total_nr_pix,
    struct lightcone_particle_type *part_type, size_t elements_per_block,
    int *nr_shells_out);

void lightcone_shell_array_free(struct lightcone_shell *shell, int nr_shells);

void lightcone_shell_array_dump(const struct lightcone_shell *shell,
                                int nr_shells, FILE *stream);

struct lightcone_shell *lightcone_shell_array_restore(
    FILE *stream, int nr_shells, struct lightcone_particle_type *part_type,
    size_t elements_per_block);

void lightcone_shell_flush_map_updates(
    struct lightcone_shell *shell, struct threadpool *tp,
    struct lightcone_particle_type *part_type,
    const double max_map_update_send_size_mb,
    struct projected_kernel_table *kernel_table, int verbose);

#endif /* SWIFT_LIGHTCONE_SHELL_H */