#pragma once

#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
#include <stdio.h>


#include "defs.h"
#include "DynamicArray.h"

/**
 * @file
 * @defgroup SMatrix SMatrix
 * @addtogroup SMatrix
 * @{
 * A SMatrix is a 2d, rectangular array.
 */

S_BEGIN_DECLS

/**
 * An SMatrix is an opaque data structure representing a simple rectangular
 * matrix.
 *
 * An SMatrix Stores pointers to data, and not the data itself.
 * So when using functions like s_matrix_realloc, it only copies the pointers
 * over from the old SMatrix to the new.
 */
typedef struct SMatrix SMatrix;

#define S_MATRIX(k) ((SMatrix *) (k))

/**
 * convenience structure to store type into and name of a tuple.
 * Example:
 @code{.c}
typedef enum {
  MY_TUPLE_ENUM_NAME,
  MY_TUPLE_ENUM_AGE,
  MY_TUPLE_ENUM_HEIGHT,
  MY_TUPLE_ENUM_END_OF_TUPLE
} MyRowInfoEnum;

const SMatrixRowInformation my_row_info[] = {
  {"Name", S_TYPE_STRING, NULL, NULL, NULL},
  {"Age", S_TYPE_UINT, NULL, NULL, NULL},
  {"Height", S_TYPE_UINT, NULL, NULL, NULL}
  {NULL, NULL, NULL, NULL, NULL}
};
 @endcode
 * The information can then be used like this:
 @code{.c}
for (int i = 0, i <= s_matrix_get_last_tuple_n (my_matrix), i++) {
  spointer tmp_tuple = s_matrix_get_tuple (my_matrix, i);
  for (int j = 0, j <= MY_TUPLE_ENUM_END_OF_TUPLE, j++) {
    s_print ("%s: %s", my_row_info[j], tmp_tuple[j]);
  }
  s_print ("\n");
  free (tmp_tuple);
}
 @endcode
 *
 */
typedef struct SMatrixRowInformation {
  schar *       name; /**< Name of the row. */
  FreeFunc      free_func; /**< Free function to be used with complex objects.*/
  FuncPointer   to_json; /**< Used to get some sort of JSON information out of
                              a complex object. (Can be NULL). */
  FuncPointer   from_json; /**< When constructing an object of this type from
                            * some JSON representation of the complex object.
                            * This functons should be used. (Can be NULL)*/
  SType         type; /**< Type of the row. */
} SMatrixRowInformation;

/**
 * Create a new SMatrix.
 *
 * @param width The width of the matrix. This can be seen as the number of
 *              columns each tuple has.
 * @param height The initial height of the matrix. The initial number of rows
 *               the matrix has.
 */
S_EXPORTED
SMatrix *
s_matrix_new (size_t width,
              size_t height,
              SMatrixRowInformation * row_information);

/**
 * Free a SMatrix.
 *
 * @param self The SMatrix to free.
 * @param free_data Should the data also be freed with the data structure?
 */
S_EXPORTED
void
s_matrix_free (SMatrix * self, sboolean free_data);

/**
 * Reallocate a SMatrix from one tuple width to an other.
 *
 * @warning The new tuple size must be larger then the last, or it will be
 * turnicated, but not freed.
 *
 * @note You need to free the old SMatrix yourself
 * <em>(but not the data stored)</em>, this is to avoid memory leaks.
 */
S_EXPORTED
SMatrix *
s_matrix_realloc (SMatrix * self,
                  size_t width,
                  SMatrixRowInformation * new_row_information);

/**
 * Get element y in tuple x.
 *
 * Equivalent to matrix[x][y] would be in static 2d arrays.
 */
S_EXPORTED
spointer
s_matrix_get (SMatrix * self,
              size_t x,
              size_t y);

/**
 * Returns the number representing the last tuple.
 * IE: it the last tuple has the x position of 10, this will return 10.
 *
 * @param self The SMatrix to get the x coordinate of the last tuple.
 *
 * @return The x coordinate of the last tuple.
 *
 * @note The value way be wrong.
 */
S_EXPORTED
size_t
s_matrix_get_last_tuple_n (SMatrix self);

/**
 * @brief Set data at a point [x,y] in the array.
 *
 * @param self The SMatrix to operate on.
 * @param x The x coordinates of where to put the data.
 *          (What tuple to put it in).
 * @param y The y coordinates to put the data in.
 *          (What position in the tuple to put it in).
 * @param data The data to be placed in the SMatrix.
 */
S_EXPORTED
void
s_matrix_set (SMatrix * self,
              size_t x,
              size_t y,
              spointer data);

/**
 * Gets tuple x in SMatrix self.
 * 
 * @param self the matrix to perform the operation on.
 * @param x the tuple to get.
 *
 * @return a pointer to an array congaing painters to the data in the tuple.
 *
 * @see s_matrix_get_tuple_as_dynamic_array()
 */
S_EXPORTED
spointer *
s_matrix_get_tuple (SMatrix * self,
                    size_t x);

/**
 * Append a tuple to a SMatrix.
 *
 * @param self The SMatrix to perform the operation on.
 * @param tuple The <em>allocated</em> data tuple to be copied into the SMatrix.
 *
 * @note This function will consume (free) allocated data for the tuple.
 *
 * @note The function assumes that the tuple has the same width as specified
 *       during construction or reallocation. Any dangling pointers will
 *       <em>not</em> be freed.
 */
S_EXPORTED
void
s_matrix_append (SMatrix * self,
                 spointer * tuple);

/**
 * Get tuple x as a SDynamicArray.
 *
 * @param self the SMatrix to perform the operation on.
 * @param x the tuple to get.
 *
 * @return An SDynamicArray containing pointers to the data in the tuple.
 *
 * @see s_matrix_get_tuple
 */
S_EXPORTED
SDynamicArray *
s_matrix_get_tuple_as_dynamic_array (SMatrix * self,
                                     size_t x);

/**
 * This iterates over each tuple. giving the tuple as the item in the
 * callback function.
 */
S_EXPORTED
void
s_matrix_for_each (SMatrix * self,
                  ForEachFunc callback,
                  spointer data);


/**
 * @TODO
 * @warning NOT IMPLIED
 *
 * Get the matrix as JSON.
 * @param self The SMatrix to get the JSON from.
 *
 * @return a null-terminated JSON string representing the matrix.
 *
 * @note This only works on primative types. complex types (IE: structs and
 * such) can not be gotten any information from.
 *
 *The outputted JSON will have the format:
 @code{.js}
{
  { // Tuple
    { // Item
      name: "Name"
      type: "STRING",
      data: "John Smith"
    },
    { // Item
      name: "Age",
      type: "UINT",
      data: 32,
    },
    { // Item
      name: "Height",
      type: "UINT",
      data: 189
    }
  },
  { //tuple
    // ...
  }
}
 @endcode
 */
S_EXPORTED
char *
s_matrix_serialize_json (SMatrix * self);

/**
 * @TODO
 * @warning NOT IMPLIED
 *
 * Deselialize JSON into an SMatrix.
 *
 * @param self The SMatrix to write to.
 * @param data the JSON data to be deselialised.
 *
 * @note The SMatrix must be empty, but initialised.
 *
 * @note As with s_matrix_serialize_json(), only primative types work. Complex
 * types will not work.
 */
S_EXPORTED
void
s_matrix_deserialize_json (SMatrix * self,
                           const schar * data);

/** @} */

S_END_DECLS
