/*
 * Because we are using the kindly-provided services of
 * http://i...content-available-to-author-only...e.com , we mush together three separate
 * files using some preprocessor tricks.  The files are
 * identified individually so that you can copy and paste
 * them into their own files, if you so choose.  The
 * stuff that only matters for this http://i...content-available-to-author-only...e.com
 * paste is every line that contains the text
 * 'ALL_IN_ONE'.  This very comment section is also not
 * part of the three files
 */

#ifndef MULDIM_TEST_C_ /* ALL_IN_ONE */
#define MULDIM_TEST_C_ /* ALL_IN_ONE */



  /**** muldim_test.c - Shao Miller, April 4th, 2012 */

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

#if 0 /* ALL_IN_ONE */
  #include "muldim.h"
#else /* ALL_IN_ONE */
#include "prog.c" /* ALL_IN_ONE */
#endif /* ALL_IN_ONE */

  /*** Macros */
  #define CountOf(array) (sizeof (array) / sizeof *(array))

  int main(void) {
      int result;
      muldim_t arr;
      muldim_t end;
      int test[2][3][4];
      int i, j, k, * ip;

      /* Assume failure */
      result = EXIT_FAILURE;

      /* Same dimensions as 'test' */
      arr = muldim_zalloc(sizeof (int), 3, 2, 3, 4);
      if (!arr.ptr) {
          goto err_arr;
        }

      muldim_dmp(arr);

      end = muldim_get(arr, 3, 1, 2, 4);
      if (!end.ptr) {
          goto err_end;
        }

      muldim_dmp(end);

      printf(
          "(\n"
          "    (char *) (test[1][2] + 4) -\n"
          "    (char *) test[0][0]\n"
          "  ) == %d\n",
          (
              (char *) (test[1][2] + 4) -
              (char *) test[0][0]
            )
        );

      printf(
          "(\n"
          "    (char *) end.ptr -\n"
          "    (char *) arr.ptr\n"
          "  ) == %d\n",
          (
              (char *) end.ptr -
              (char *) arr.ptr
            )
        );

      /* Same dimensions as 'test' */
      for (i = 0; i < CountOf(test); ++i) {
          for (j = 0; j < CountOf(test[0]); ++j) {
              ip = muldim_get(arr, 2, i, j).ptr;
              for (k = 0; k < CountOf(test[0][0]); ++k) {
                  if (ip[k]) {
                      printf("muldim_zalloc failed!\n");
                      goto err_nonzero;
                    }
                  ip[k] = 42;
                  continue;
                }
              continue;
            }
          continue;
        }

      /* Success */
      printf("Yay\n");
      result = EXIT_SUCCESS;

      err_nonzero:

      err_end:

      free(arr.mem);
      err_arr:

      return result;
    }



#else /* ALL_IN_ONE */



  /**** muldim.h  - Shao Miller, April 4th, 2012 */

  #ifndef MULDIM_H_

  #include <stddef.h>

  /*** Macros */
  #define MULDIM_H_

  /*** Object types */

  typedef struct s_muldim s_muldim, muldim_t;

  /*** Struct/union definitions */

  struct s_muldim {
      void * ptr;
      size_t dims;
      size_t stride;
      size_t * dims_at;
      void * mem;
    };

  /*** Function declarations */

  extern muldim_t muldim_alloc(size_t base_sz, size_t dims, ...);
  extern muldim_t muldim_zalloc(size_t base_sz, size_t dims, ...);
  extern muldim_t muldim_get(muldim_t muldim, size_t dims, ...);
  extern void muldim_dmp(muldim_t muldim);

  #endif /* MULDIM_H_ */



  /**** muldim.c  - Shao Miller, April 4th, 2012 */

  #include <stddef.h>
  #include <stdarg.h>
  #include <stdlib.h>
  #include <stdio.h>

#if 0 /* ALL_IN_ONE */
  #include "muldim.h"
#endif /* ALL_IN_ONE */

  /*** Function declarations */

  static size_t sz_add(size_t x, size_t y);
  static size_t sz_mul(size_t x, size_t y);

  /*** Struct/union definitions */

  struct s_muldim_wrapper {
      muldim_t muldim;
      size_t dims_hdr[1];
    };

  /*** Object definitions */
  static const muldim_t new_muldim;

  /*** Function definitions */

  muldim_t muldim_alloc(size_t base_sz, size_t dims, ...) {
      register const size_t dims_offset = offsetof(
          struct s_muldim_wrapper,
          dims_hdr
        );
      muldim_t result;
      size_t dims_sz;
      size_t hdr_sz;
      va_list vargs;
      size_t array_sz;
      size_t i;
      int dim;
      size_t total_sz;
      char * ptr;
      size_t * dims_at;

      result = new_muldim;

      if (!base_sz) {
          goto err_base_sz;
        }

      dims_sz = sz_mul(dims, sizeof result.dims_at[0]);
      if (!dims_sz) {
          goto err_dims_sz;
        }

      hdr_sz = sz_add(dims_offset, dims_sz);
      if (!hdr_sz) {
          goto err_hdr_sz;
        }
      hdr_sz = sz_add(base_sz, hdr_sz);
      if (!hdr_sz) {
          goto err_hdr_sz;
        }
      hdr_sz = hdr_sz - 1;
      hdr_sz = hdr_sz / base_sz;
      hdr_sz = hdr_sz * base_sz;

      va_start(vargs, dims);
      array_sz = base_sz;
      for (i = dims; i; --i) {
          dim = va_arg(vargs, int);
          if (dim < 1) {
              goto err_dim;
            }

          array_sz = sz_mul(array_sz, (size_t) dim);
          if (!array_sz) {
              goto err_array_sz;
            }

          continue;
        }
      va_end(vargs);

      total_sz = sz_add(hdr_sz, array_sz);
      if (!total_sz) {
          goto err_total_sz;
        }

      ptr = malloc(total_sz);
      if (!ptr) {
          goto err_ptr;
        }

      /* Success */

      dims_at = (void *) (ptr + dims_offset);

      va_start(vargs, dims);
      for (i = 0; i < dims; ++i) {
          dim = va_arg(vargs, int);
          dims_at[i] = (size_t) dim;

          continue;
        }
      va_end(vargs);

      result.ptr = ptr + hdr_sz;
      result.dims = dims;
      result.stride = array_sz;
      result.dims_at = dims_at;
      result.mem = ptr;

      *(muldim_t *) ptr = result;
      return result;

      free(ptr);
      err_ptr:

      err_total_sz:

      err_array_sz:

      err_dim:

      err_hdr_sz:

      err_dims_sz:

      err_base_sz:

      return result;
    }

  muldim_t muldim_zalloc(size_t base_sz, size_t dims, ...) {
      register const size_t dims_offset = offsetof(
          struct s_muldim_wrapper,
          dims_hdr
        );
      muldim_t result;
      size_t dims_sz;
      size_t hdr_sz;
      va_list vargs;
      size_t array_sz;
      size_t i;
      int dim;
      size_t total_sz;
      char * ptr;
      size_t * dims_at;

      result = new_muldim;

      if (!base_sz) {
          goto err_base_sz;
        }

      dims_sz = sz_mul(dims, sizeof result.dims_at[0]);
      if (!dims_sz) {
          goto err_dims_sz;
        }

      hdr_sz = sz_add(dims_offset, dims_sz);
      if (!hdr_sz) {
          goto err_hdr_sz;
        }
      hdr_sz = sz_add(base_sz, hdr_sz);
      if (!hdr_sz) {
          goto err_hdr_sz;
        }
      hdr_sz = hdr_sz - 1;
      hdr_sz = hdr_sz / base_sz;
      hdr_sz = hdr_sz * base_sz;

      va_start(vargs, dims);
      array_sz = base_sz;
      for (i = dims; i; --i) {
          dim = va_arg(vargs, int);
          if (dim < 1) {
              goto err_dim;
            }

          array_sz = sz_mul(array_sz, (size_t) dim);
          if (!array_sz) {
              goto err_array_sz;
            }

          continue;
        }
      va_end(vargs);

      total_sz = sz_add(hdr_sz, array_sz);
      if (!total_sz) {
          goto err_total_sz;
        }

      total_sz = total_sz / base_sz;
      ptr = calloc(total_sz, base_sz);
      if (!ptr) {
          goto err_ptr;
        }

      /* Success */

      dims_at = (void *) (ptr + dims_offset);

      va_start(vargs, dims);
      for (i = 0; i < dims; ++i) {
          dim = va_arg(vargs, int);
          dims_at[i] = (size_t) dim;

          continue;
        }
      va_end(vargs);

      result.ptr = ptr + hdr_sz;
      result.dims = dims;
      result.stride = array_sz;
      result.dims_at = dims_at;
      result.mem = ptr;

      *(muldim_t *) ptr = result;
      return result;

      free(ptr);
      err_ptr:

      err_total_sz:

      err_array_sz:

      err_dim:

      err_hdr_sz:

      err_dims_sz:

      err_base_sz:

      return result;
    }

  muldim_t muldim_get(muldim_t muldim, size_t dims, ...) {
      char * ptr;
      va_list vargs;
      int dim;

      if (!dims || dims > muldim.dims) {
          goto err_dims;
        }

      ptr = muldim.ptr;
      va_start(vargs, dims);
      while (dims--) {
          dim = va_arg(vargs, int);
          if (dim < 0 || dim > muldim.dims_at[0]) {
              goto err_dim;
            }

          muldim.stride = muldim.stride / muldim.dims_at[0];
          ptr = ptr + muldim.stride * dim;
          --muldim.dims;
          ++muldim.dims_at;

          continue;
        }
      va_end(vargs);

      /* Success */

      muldim.ptr = ptr;
      return muldim;

      err_dim:

      err_dims:

      return new_muldim;
    }

  void muldim_dmp(muldim_t muldim) {
      printf(
          "ptr == %p\n"
          "dims == %u\n"
          "stride == %u\n"
          "dims_at == %p\n"
          "mem == %p\n",
          muldim.ptr,
          (unsigned int) muldim.dims,
          (unsigned int) muldim.stride,
          (void *) muldim.dims_at,
          muldim.mem
        );
      printf("dimensions: { ");
      while (muldim.dims--) {
          printf("%u, ", (unsigned int) muldim.dims_at++[0]);
          continue;
        }
      printf("}\n");
      return;
    }

  static size_t sz_add(size_t x, size_t y) {
      size_t result;

      result = x + y;
      if (result <= x || result <= y) {
          /* Wrap-around */
          return 0;
        }
      return result;
    }

  static size_t sz_mul(size_t x, size_t y) {
      size_t result;

      result = x * y;
      if (result <= x || result <= y) {
          /* Wrap-around */
          return 0;
        }
      return result;
    }



#endif /* ALL_IN_ONE */