/*
 * (C) Copyright 1996-2013 ECMWF.
 *
 * This software is licensed under the terms of the Apache Licence Version 2.0
 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
 * In applying this licence, ECMWF does not waive the privileges and immunities
 * granted to it by virtue of its status as an intergovernmental organisation nor
 * does it submit to any jurisdiction.
 */

// File HyperCube.h
// Baudouin Raoult - ECMWF Oct 96

#ifndef eckit_HyperCube_h
#define eckit_HyperCube_h

#include <vector>
#include <functional>
#include <numeric>
#include <ostream>

//-----------------------------------------------------------------------------


//-----------------------------------------------------------------------------

// Helper class to handle multi-dimension objects
// The first dimension should be the one most likely to change

class HyperCube {
public:


    typedef std::vector<size_t> Dimensions;
    typedef std::vector<size_t> Coordinates;
    typedef std::vector<size_t> Remapping;

    // -- Contructors

    HyperCube(const Dimensions &d) : dimensions_(d) {}

    // -- Methods

    // Translate coordinates into an index to a 1 dimension array.

    size_t index(const Coordinates &) const;

    // Return the number of elemets

    size_t count() const;

    // Translate index to coordinates

    void coordinates(size_t index, Coordinates &) const;

    // Accessors

    const Dimensions &dimensions() const    {
        return dimensions_;
    }
    Dimensions &dimensions()                {
        return dimensions_;
    }
    size_t     dimensions(size_t n) const {
        return dimensions_[n];
    }
    size_t     size() const                {
        return dimensions_.size();
    }

    // Return the 'remapping' std::vector needing to add 'count' labels
    // for the dimension 'which' at position 'where'

    HyperCube addToDimension(size_t which, size_t where, size_t count, Remapping &) const;

    // Combine two 'remapping' vectors

    static void combine(Remapping &,  Remapping &);

private:

    // -- Members

    Dimensions dimensions_;

};

inline  // For speed
size_t HyperCube::count() const
{
    return std::accumulate(dimensions_.begin(), dimensions_.end(), 1, std::multiplies<size_t>());
}


inline // For speed
size_t HyperCube::index(const Coordinates &coord) const
{
    size_t n = 1;
    size_t a = 0;

    // ASSERT(coord.size() == dimensions_.size());

    // The fact that this is in reverse is important for addToDimension

    for (int i = coord.size() - 1; i >= 0; i--)
    {
        // ASSERT(coord[i] >= 0 && coord[i] < dimensions_[i]);
        a += coord[i] * n;
        n *= dimensions_[i];
    }

    return a;
}


inline // For speed
void HyperCube::combine(Remapping &map1, Remapping &map2)
{
    if (map1.size() == 0)
    {
        std::swap(map1, map2);
    }
    else {
        for (Remapping::iterator i = map1.begin() ; i != map1.end() ; ++i)
        {
            // ASSERT(*i < map2.size());
            *i = map2[*i];
        }
    }
}

std::ostream &operator<<(std::ostream &out, const HyperCube &cube);

//-----------------------------------------------------------------------------


#endif
