#ifndef LIBTENSOR_ABS_INDEX_IMPL_H
#define LIBTENSOR_ABS_INDEX_IMPL_H

#include <libtensor/core/out_of_bounds.h>
#include "magic_dimensions_impl.h"
#include "../abs_index.h"

namespace libtensor {


template<size_t N>
const char *abs_index<N>::k_clazz = "abs_index<N>";


template<size_t N>
abs_index<N>::abs_index(const dimensions<N> &dims) :

    m_dims(dims), m_aidx(0) {

}


template<size_t N>
abs_index<N>::abs_index(const index<N> &idx, const dimensions<N> &dims) :

    m_dims(dims), m_idx(idx) {

    m_aidx = get_abs_index(m_idx, m_dims);
}


template<size_t N>
abs_index<N>::abs_index(size_t aidx, const dimensions<N> &dims) :

    m_dims(dims), m_aidx(aidx) {

    get_index(m_aidx, m_dims, m_idx);
}


template<size_t N>
abs_index<N>::abs_index(const abs_index<N> &other) :

    m_dims(other.m_dims), m_idx(other.m_idx), m_aidx(other.m_aidx) {

}


template<size_t N>
bool abs_index<N>::inc() {

    if(is_last()) return false;

    size_t n = N - 1;
    bool done = false, ok = false;
    do {
        if(m_idx[n] < m_dims[n] - 1) {
            m_idx[n]++;
            for(register size_t i = n + 1; i != N; i++) m_idx[i] = 0;
            done = true;
            ok = true;
        } else {
            if(n == 0) done = true;
            else n--;
        }
    } while(!done);
    if(ok) m_aidx++;
    return ok;
}


template<size_t N>
size_t abs_index<N>::get_abs_index(const index<N> &idx,
    const dimensions<N> &dims) {

#ifdef LIBTENSOR_DEBUG
    static const char *method =
        "get_abs_index(const index<N>&, const dimensions<N>&)";

    for(size_t i = 0; i != N; i++) {
        if(idx[i] >= dims[i]) {
            throw out_of_bounds(g_ns, k_clazz, method, __FILE__, __LINE__,
                "idx");
        }
    }
#endif // LIBTENSOR_DEBUG

    size_t aidx = 0;
    for(register size_t i = 0; i != N; i++) {
        aidx += dims.get_increment(i) * idx[i];
    }

    return aidx;
}


template<size_t N>
void abs_index<N>::get_index(size_t aidx, const dimensions<N> &dims,
    index<N> &idx) {

#ifdef LIBTENSOR_DEBUG
    static const char *method =
        "get_index(size_t, const dimensions<N>&, index<N>&)";

    if(aidx >= dims.get_size()) {
        throw out_of_bounds(g_ns, k_clazz, method, __FILE__, __LINE__, "aidx");
    }
#endif // LIBTENSOR_DEBUG

    size_t a = aidx;
    size_t imax = N - 1;
    for(register size_t i = 0; i < imax; i++) {
        idx[i] = a / dims.get_increment(i);
        a %= dims.get_increment(i);
    }
    idx[N - 1] = a;
}


template<size_t N>
void abs_index<N>::get_index(size_t aidx, const magic_dimensions<N> &mdims,
    index<N> &idx) {

#ifdef LIBTENSOR_DEBUG
    static const char *method =
        "get_index(size_t, const dimensions<N>&, index<N>&)";

    if(aidx >= mdims.get_dims().get_size()) {
        throw out_of_bounds(g_ns, k_clazz, method, __FILE__, __LINE__, "aidx");
    }
#endif // LIBTENSOR_DEBUG

    uint64_t a = aidx;
    for(register size_t i = 0; i != N - 1; i++) {
        idx[i] = mdims.divide(a, i);
        a -= idx[i] * mdims.get_dims().get_increment(i);
    }
    idx[N - 1] = a;
}


} // namespace libtensor

#endif // LIBTENSOR_ABS_INDEX_IMPL_H
