#include "SequenceData.hpp"
#include "SysUtil.hpp"
#include "VarLenTag.hpp"
#include <cassert>

namespace moab
{

SequenceData::~SequenceData()
{
    for( int i = -numSequenceData; i <= (int)numTagData; ++i )
        free( arraySet[i] );
    free( arraySet - numSequenceData );
}

void* SequenceData::create_data( int index, int bytes_per_ent, const void* initial_value )
{
    char* array = (char*)malloc( bytes_per_ent * size() );
    if( initial_value ) SysUtil::setmem( array, initial_value, bytes_per_ent, size() );

    arraySet[index] = array;
    return array;
}

void* SequenceData::create_sequence_data( int array_num, int bytes_per_ent, const void* initial_value )
{
    const int index = -1 - array_num;
    assert( array_num < numSequenceData );
    assert( !arraySet[index] );
    return create_data( index, bytes_per_ent, initial_value );
}

void* SequenceData::create_custom_data( int array_num, size_t total_bytes )
{
    const int index = -1 - array_num;
    assert( array_num < numSequenceData );
    assert( !arraySet[index] );

    void* array     = malloc( total_bytes );
    arraySet[index] = array;
    return array;
}

SequenceData::AdjacencyDataType* SequenceData::allocate_adjacency_data()
{
    assert( !arraySet[0] );
    const size_t s = sizeof( AdjacencyDataType* ) * size();
    arraySet[0]    = malloc( s );
    memset( arraySet[0], 0, s );
    return reinterpret_cast< AdjacencyDataType* >( arraySet[0] );
}

void SequenceData::increase_tag_count( unsigned amount )
{
    void** list     = arraySet - numSequenceData;
    const size_t sz = sizeof( void* ) * ( numSequenceData + numTagData + amount + 1 );
    void** new_list = (void**)realloc( list, sz );
    if( !new_list )
    {
        fprintf( stderr, "SequenceData::increase_tag_count(): reallocation of list failed\n" );
        // Note: free(list) will be called in the destructor
        return;
    }
    else
        list = new_list;
    arraySet = list + numSequenceData;
    memset( arraySet + numTagData + 1, 0, sizeof( void* ) * amount );
    numTagData += amount;
}

void* SequenceData::allocate_tag_array( int tag_num, int bytes_per_ent, const void* default_value )
{
    if( (unsigned)tag_num >= numTagData ) increase_tag_count( tag_num - numTagData + 1 );

    assert( !arraySet[tag_num + 1] );
    return create_data( tag_num + 1, bytes_per_ent, default_value );
}

SequenceData* SequenceData::subset( EntityHandle start, EntityHandle end, const int* sequence_data_sizes ) const
{
    return new SequenceData( this, start, end, sequence_data_sizes );
}

SequenceData::SequenceData( const SequenceData* from, EntityHandle start, EntityHandle end,
                            const int* sequence_data_sizes )
    : numSequenceData( from->numSequenceData ), numTagData( from->numTagData ), startHandle( start ), endHandle( end )
{
    assert( start <= end );
    assert( from != 0 );
    assert( from->start_handle() <= start );
    assert( from->end_handle() >= end );

    void** array        = (void**)malloc( sizeof( void* ) * ( numSequenceData + numTagData + 1 ) );
    arraySet            = array + numSequenceData;
    const size_t offset = start - from->start_handle();
    const size_t count  = end - start + 1;

    for( int i = 0; i < numSequenceData; ++i )
        copy_data_subset( -1 - i, sequence_data_sizes[i], from->get_sequence_data( i ), offset, count );
    copy_data_subset( 0, sizeof( AdjacencyDataType* ), from->get_adjacency_data(), offset, count );
    for( unsigned i = 1; i <= numTagData; ++i )
        arraySet[i] = 0;
}

void SequenceData::copy_data_subset( int index, int size_per_ent, const void* source, size_t offset, size_t count )
{
    if( !source )
        arraySet[index] = 0;
    else
    {
        arraySet[index] = malloc( count * size_per_ent );
        memcpy( arraySet[index], (const char*)source + offset * size_per_ent, count * size_per_ent );
    }
}

void SequenceData::move_tag_data( SequenceData* destination, const int* tag_sizes, int num_tag_sizes )
{
    assert( destination->start_handle() >= start_handle() );
    assert( destination->end_handle() <= end_handle() );
    const size_t offset = destination->start_handle() - start_handle();
    const size_t count  = destination->size();
    if( destination->numTagData < numTagData ) destination->increase_tag_count( numTagData - destination->numTagData );

    for( unsigned i = 1; i <= numTagData; ++i )
    {
        if( !arraySet[i] ) continue;

        assert( i <= (unsigned)num_tag_sizes );
        if( num_tag_sizes ) {}  // empty line to prevent compiler warning

        const int tag_size = tag_sizes[i - 1];
        if( !destination->arraySet[i] ) destination->arraySet[i] = malloc( count * tag_size );
        memcpy( destination->arraySet[i], reinterpret_cast< char* >( arraySet[i] ) + offset * tag_size,
                count * tag_size );
    }
}

void SequenceData::release_tag_data( const int* tag_sizes, int num_tag_sizes )
{
    assert( num_tag_sizes >= (int)numTagData );
    if( num_tag_sizes ) {}  // empty line to prevent compiler warning
    for( unsigned i = 0; i < numTagData; ++i )
        release_tag_data( i, tag_sizes[i] );
}

void SequenceData::release_tag_data( int tag_num, int tag_size )
{
    if( (unsigned)tag_num < numTagData )
    {
        if( tag_size == MB_VARIABLE_LENGTH && arraySet[tag_num + 1] )
        {
            VarLenTag* iter      = reinterpret_cast< VarLenTag* >( arraySet[tag_num + 1] );
            VarLenTag* const end = iter + size();
            for( ; iter != end; ++iter )
                iter->clear();
        }
        free( arraySet[tag_num + 1] );
        arraySet[tag_num + 1] = 0;
    }
}

}  // namespace moab
