// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
#pragma once

#include <boost/cstdint.hpp>

namespace frantic {
namespace volumetrics {
namespace levelset {

// Forward declaration of the rle_index_spec class
class rle_index_spec;

/**
 * @todo  Recommended to refactor into the ordering x_neg, x_pos, y_neg, y_pos, z_neg, z_pos.  This will make it
 *        more consistent with some other things, and seems more intuitive to me.  -Mark Wiebe
 */
enum rae_index { rae_index_x_pos, rae_index_x_neg, rae_index_y_pos, rae_index_y_neg, rae_index_z_pos, rae_index_z_neg };

/**
 * This returns true if the neighbor index is rae_index_x_pos, rae_index_y_pos or rae_index_z_pos (assuming its within
 * bounds).
 *
 * @todo  If the ordering is refactored as recommended, this test will flip to !=.
 */
inline bool is_neighbor_index_direction_positive( int neighborIndex ) { return ( neighborIndex & 0x1 ) == 0; }

/**
 * This returns the axis (0, 1 or 2 for X, Y Z respectively) of the given neighbor index.
 */
inline int neighbor_index_axis( int neighborIndex ) { return neighborIndex >> 1; }

/**
 *  This returns the neighbor rae_index for the specified axis and sign.
 *
 * @param axis the axis of the desired neighbor index. Acceptable values
 *		are 0, 1 or 2 for X, Y Z respectively.
 * @param direction +1 to get the index in the positive direction
 *		along the axis (rae_index_x_pos, rae_index_y_pos, or rae_index_z_pos),
 *		or -1 to get the index in the negative direction instead
 *		(rae_index_x_neg, rae_index_y_neg, or rae_index_z_neg).
 * @return the frantic::volumetrics::levelset::rae_index corresponding
 *		to the specified axis and direction.
 */
inline int get_rae_neighbor_index( const int axis, const int direction ) {
    switch( axis ) {
    case 0:
        return direction > 0 ? rae_index_x_pos : rae_index_x_neg;
    case 1:
        return direction > 0 ? rae_index_y_pos : rae_index_y_neg;
    case 2:
        return direction > 0 ? rae_index_z_pos : rae_index_z_neg;
    default:
        throw( "get_rae_neighbor_index Error: axis must be 0, 1, or 2, but instead it was " +
               boost::lexical_cast<std::string>( axis ) + "." );
    }
}

/**
 *  This returns the rae_index neighbor along the specified axis
 * in the positive direction.
 *
 * @param axis the axis along which to find the rae_index neighbor
 *		in the positive direction.  Acceptable values are 0, 1, or 2
 *		for X, Y, and Z respectively.
 * @return the frantic::volumetrics::levelset::rae_index corresponding
 *		to the specified axis in the positive direction.
 */
inline int get_positive_rae_neighbor_index( const int axis ) {
    const int positiveIndices[] = { rae_index_x_pos, rae_index_y_pos, rae_index_z_pos };
    return positiveIndices[axis];
}

/**
 *  This returns the rae_index neighbor along the specified axis
 * in the negative direction.
 *
 * @param axis the axis along which to find the rae_index neighbor
 *		in the negative direction.  Acceptable values are 0, 1, or 2
 *		for X, Y, and Z respectively.
 * @return the frantic::volumetrics::levelset::rae_index corresponding
 *		to the specified axis in the negative direction.
 */
inline int get_negative_rae_neighbor_index( const int axis ) {
    const int negativeIndices[] = { rae_index_x_neg, rae_index_y_neg, rae_index_z_neg };
    return negativeIndices[axis];
}

/**
 *  Return the rae_index neighbour along the same axis, but in
 * the opposite direction.  For example, the opposite of
 * rae_index_x_pos is rae_index_x_neg.
 *
 * @param face the rae index to get the opposite of.
 * @return the rae index that is along the same axis as the
 *		specified neighborIndex, but in the opposite direction.
 */
inline int get_opposite_rae_neighbor_index( const int face ) {
    switch( face ) {
    case rae_index_x_pos:
        return rae_index_x_neg;
    case rae_index_x_neg:
        return rae_index_x_pos;
    case rae_index_y_pos:
        return rae_index_y_neg;
    case rae_index_y_neg:
        return rae_index_y_pos;
    case rae_index_z_pos:
        return rae_index_z_neg;
    case rae_index_z_neg:
        return rae_index_z_pos;
    default:
        throw std::runtime_error( "get_opposite_rae_neighbor_index Error: invalid face number" );
    }
}

/**
 * Each element specifies the data index of the voxel one unit in
 * the positive or negative direction, along the specified axis.
 *
 * @todo  Recommended to refactor into the ordering x_neg, x_pos, y_neg, y_pos, z_neg, z_pos.  This will make it
 *        more consistent with some other things, and seems more intuitive to me.  -Mark Wiebe
 */
struct ris_adj_entry {
    boost::int32_t x_pos, x_neg;
    boost::int32_t y_pos, y_neg;
    boost::int32_t z_pos, z_neg;

    /**
     * This indexing operator lets you do loops for i = 0 to 5.  The order
     * is specified as X positive/negative, Y positive/negative, Z positive/negative.
     */
    const boost::int32_t& operator[]( std::size_t i ) const { return ( &x_pos )[i]; }
};

/**
 * This class stores the adjacency graph of the defined voxels in an rle_index_spec (or possibly other
 * voxel structure).  For each defined voxel, as represented by a data index, it can give you the
 * data index of all 6 immediately adjacent voxels, providing you with the region code if the
 * adjacent voxel is not defined.
 *
 * @todo  I would suggest renaming this to voxel_adjacency_graph, because it is, indeed, providing
 *        the adjacency graph of the defined voxels.  There is nothing rle-specific about it, other
 *        than the creation code, and an ris_adjacency/voxel_adjacency_graph could just as easily be created for a dense
 *        voxel structure as well.  -Mark Wiebe
 */
class ris_adjacency {
    // This is the array of entries
    ris_adj_entry* m_adjacencyList;

    // For some minimal validation by users of this class to make sure it's the right size.
    std::size_t m_dataSize;

    // Don't implement these.  Copy construction and assignment are disabled.
    ris_adjacency( const ris_adjacency& );
    ris_adjacency& operator=( const ris_adjacency& );

  public:
    ris_adjacency() {
        m_adjacencyList = 0;
        m_dataSize = 0;
    }

    ~ris_adjacency() {
        if( m_adjacencyList ) {
            delete[] m_adjacencyList;
            m_adjacencyList = 0;
        }
    }

    const ris_adj_entry& operator[]( std::size_t dataIndex ) const { return m_adjacencyList[dataIndex]; }

    /**
     * This function computes the full adjacency information from the given rle_index_spec.
     */
    void compute( const rle_index_spec& ris );

    std::size_t data_size() const { return m_dataSize; }
};

} // namespace levelset
} // namespace volumetrics
} // namespace frantic