// Copyright (c) 2019-2020 Simons Foundation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0.txt
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#pragma once
#include <utility>
#include <vector>
#include <string>
#include "./group.hpp"

// This is the generic interface for the ndarray interface
//
namespace h5::array_interface {

  // Stores the hdf5 type and the dims
  struct h5_lengths_type {
    v_t lengths;
    datatype ty;
    bool has_complex_attribute;

    //
    [[nodiscard]] int rank() const { return lengths.size(); }
  };

  // Store HDF5 hyperslab info, as in HDF5 manual
  // http://davis.lbl.gov/Manuals/HDF5-1.8.7/UG/12_Dataspaces.html
  struct hyperslab {
    v_t offset; // offset of the pointer from the start of data
    v_t stride; // stride in each dimension (in the HDF5 sense). 1 if contiguous. Always >0.
    v_t count;  // length in each dimension
    v_t block;  // block in each dimension

    // Constructor : unique to enforce the proper sizes of array
    // rank : the array will have rank + is_complex
    hyperslab(int rank, bool is_complex)
       : offset(rank + is_complex, 0), stride(rank + is_complex, 1), count(rank + is_complex, 0), block() { // block is often unused
      if (is_complex) {
        stride[rank] = 1;
        count[rank]  = 2;
      }
    }

    //
    [[nodiscard]] int rank() const { return count.size(); }
  };

  // Stores a view of an array.
  // scalar are array of rank 0, lengths, strides are empty, rank is 0, start is the scalar
  struct h5_array_view {
    datatype ty;    // HDF5 type
    void *start;    // start of data. It MUST be a pointer of T* with ty = hdf5_type<T>()
    v_t L_tot;      // lengths of the parent contiguous array
    hyperslab slab; // hyperslab
    bool is_complex;

    // Constructor : unique to enforce the proper sizes of array
    h5_array_view(datatype ty, void *start, int rank, bool is_complex)
       : ty(std::move(ty)), start(start), L_tot(rank + is_complex), slab(rank, is_complex), is_complex(is_complex) {
      if (is_complex) L_tot[rank] = 2;
    }

    [[nodiscard]] int rank() const { return slab.rank(); }
  };

  //------------------------------------------------
  // given the lengths and strides, return a L_tot. One function for all ranks (save code).
  // Assume stride_order is C.
  // use stride[rank -1]  =   strides_h5 [rank -1]
  //     stride[rank -2]  =   L[rank-1] * strides_h5 [rank -2]
  //     stride[rank -3]  =   L[rank-1] * L[rank-2] * strides_h5 [rank -3]
  //     stride[0]        =   L[rank-1] * L[rank-2] * L[1] * strides_h5 [0]
  std::pair<v_t, v_t> get_L_tot_and_strides_h5(long const *stri, int rank, long total_size);

  // Retrieve lengths and hdf5 type from a dataset g[name] or attribute obj[name]
  h5_lengths_type get_h5_lengths_type(group g, std::string const &name);

  // Write the view of the array to the group
  void write(group g, std::string const &name, h5_array_view const &a, bool compress);

  // Read into an array_view from the group
  void read(group g, std::string const &name, h5_array_view v, h5_lengths_type lt);

  // Write the view of the array to the attribute
  void write_attribute(object obj, std::string const &name, h5_array_view v);

  // Read into a contiguous array_view from the attribute
  void read_attribute(object obj, std::string const &name, h5_array_view v);

} // namespace h5::array_interface
