/******************************************************************************
 *
 * Purpose:  Implementation of the VecSegIndex class.
 *
 * This class is used to manage a vector segment data block index.  There
 * will be two instances created, one for the record data (sec_record) and
 * one for the vertices (sec_vert).  This class is exclusively a private
 * helper class for VecSegHeader.
 *
 ******************************************************************************
 * Copyright (c) 2010
 * PCI Geomatics, 90 Allstate Parkway, Markham, Ontario, Canada.
 *
 * SPDX-License-Identifier: MIT
 ****************************************************************************/

#include "pcidsk.h"
#include "core/pcidsk_utils.h"
#include "segment/cpcidskvectorsegment.h"
#include <cassert>
#include <cstring>
#include <cstdio>
#include <limits>

using namespace PCIDSK;

/* -------------------------------------------------------------------- */
/*      Size of a block in the record/vertex block tables.  This is    */
/*      determined by the PCIDSK format and may not be changed.         */
/* -------------------------------------------------------------------- */
static const int block_page_size = 8192;

/************************************************************************/
/*                          VecSegDataIndex()                           */
/************************************************************************/

VecSegDataIndex::VecSegDataIndex()

{
    block_initialized = false;
    vs = nullptr;
    dirty = false;
    section = 0;
    offset_on_disk_within_section = 0;
    size_on_disk = 0;
    block_count = 0;
    bytes = 0;
}

/************************************************************************/
/*                          ~VecSegDataIndex()                          */
/************************************************************************/

VecSegDataIndex::~VecSegDataIndex()

{
}

/************************************************************************/
/*                             Initialize()                             */
/************************************************************************/

void VecSegDataIndex::Initialize( CPCIDSKVectorSegment *vsIn, int sectionIn )

{
    this->section = sectionIn;
    this->vs = vsIn;

    if( section == sec_vert )
        offset_on_disk_within_section = 0;
    else
        offset_on_disk_within_section = vs->di[sec_vert].SerializedSize();

    uint32 offset = offset_on_disk_within_section
        + vs->vh.section_offsets[hsec_shape];

    memcpy( &block_count, vs->GetData(sec_raw,offset,nullptr,4), 4);
    memcpy( &bytes, vs->GetData(sec_raw,offset+4,nullptr,4), 4);

    bool needs_swap = !BigEndianSystem();

    if( needs_swap )
    {
        SwapData( &block_count, 4, 1 );
        SwapData( &bytes, 4, 1 );
    }

    if( block_count > (std::numeric_limits<uint32>::max() - 8) /4 )
    {
        throw PCIDSKException("Invalid block_count: %u", block_count);
    }

    size_on_disk = block_count * 4 + 8;
}

/************************************************************************/
/*                           SerializedSize()                           */
/************************************************************************/

uint32 VecSegDataIndex::SerializedSize()

{
    return 8 + 4 * block_count;
}

/************************************************************************/
/*                           GetBlockIndex()                            */
/************************************************************************/

const std::vector<uint32> *VecSegDataIndex::GetIndex()

{
/* -------------------------------------------------------------------- */
/*      Load block map if needed.                                       */
/* -------------------------------------------------------------------- */
    if( !block_initialized )
    {
        bool needs_swap = !BigEndianSystem();

        try
        {
            block_index.resize( block_count );
        }
        catch( const std::exception& ex )
        {
            throw PCIDSKException("Out of memory allocating block_index(%u): %s",
                                  block_count, ex.what());
        }
        if( block_count > 0 )
        {
            vs->ReadFromFile( &(block_index[0]),
                              offset_on_disk_within_section
                              + vs->vh.section_offsets[hsec_shape] + 8,
                              4 * block_count );

            if( needs_swap )
                SwapData( &(block_index[0]), 4, block_count );
        }

        block_initialized = true;
    }

    return &block_index;
}

/************************************************************************/
/*                               Flush()                                */
/************************************************************************/

void VecSegDataIndex::Flush()

{
    if( !dirty )
        return;

    GetIndex(); // force loading if not already loaded!

    PCIDSKBuffer wbuf( SerializedSize() );

    memcpy( wbuf.buffer + 0, &block_count, 4 );
    memcpy( wbuf.buffer + 4, &bytes, 4 );
    memcpy( wbuf.buffer + 8, &(block_index[0]), 4*block_count );

    bool needs_swap = !BigEndianSystem();

    if( needs_swap )
        SwapData( wbuf.buffer, 4, block_count+2 );

    // Make sure this section of the header is large enough.
    int32 shift = (int32) wbuf.buffer_size - (int32) size_on_disk;

    if( shift != 0 )
    {
        uint32 old_section_size = vs->vh.section_sizes[hsec_shape];

//        fprintf( stderr, "Shifting section %d by %d bytes.\n",
//                 section, shift );

        vs->vh.GrowSection( hsec_shape, old_section_size + shift );

        if( section == sec_vert )
        {
            // move record block index and shape index.
            vs->MoveData( vs->vh.section_offsets[hsec_shape]
                          + vs->di[sec_vert].size_on_disk,
                          vs->vh.section_offsets[hsec_shape]
                          + vs->di[sec_vert].size_on_disk + shift,
                          old_section_size - size_on_disk );
        }
        else
        {
            // only move shape index.
            vs->MoveData( vs->vh.section_offsets[hsec_shape]
                          + vs->di[sec_vert].size_on_disk
                          + vs->di[sec_record].size_on_disk,
                          vs->vh.section_offsets[hsec_shape]
                          + vs->di[sec_vert].size_on_disk
                          + vs->di[sec_record].size_on_disk
                          + shift,
                          old_section_size
                          - vs->di[sec_vert].size_on_disk
                          - vs->di[sec_record].size_on_disk );
        }

        if( section == sec_vert )
            vs->di[sec_record].offset_on_disk_within_section += shift;
    }

    // Actually write to disk.
    vs->WriteToFile( wbuf.buffer,
                     offset_on_disk_within_section
                     + vs->vh.section_offsets[hsec_shape],
                     wbuf.buffer_size );

    size_on_disk = wbuf.buffer_size;
    dirty = false;
}

/************************************************************************/
/*                           GetSectionEnd()                            */
/************************************************************************/

uint32 VecSegDataIndex::GetSectionEnd()

{
    return bytes;
}

/************************************************************************/
/*                           SetSectionEnd()                            */
/************************************************************************/

void VecSegDataIndex::SetSectionEnd( uint32 new_end )

{
    // should we keep track of the need to write this back to disk?
    bytes = new_end;
}

/************************************************************************/
/*                          AddBlockToIndex()                           */
/************************************************************************/

void VecSegDataIndex::AddBlockToIndex( uint32 block )

{
    GetIndex(); // force loading.

    block_index.push_back( block );
    block_count++;
    dirty = true;
}

/************************************************************************/
/*                              SetDirty()                              */
/*                                                                      */
/*      This method is primarily used to mark the need to write the     */
/*      index when the location changes.                                */
/************************************************************************/

void VecSegDataIndex::SetDirty()

{
    dirty = true;
}

/************************************************************************/
/*                          VacateBlockRange()                          */
/*                                                                      */
/*      Move any blocks in the indicated block range to the end of      */
/*      the segment to make space for a growing header.                 */
/************************************************************************/

void VecSegDataIndex::VacateBlockRange( uint32 start, uint32 count )

{
    GetIndex(); // make sure loaded.

    unsigned int i;
    uint32  next_block = (uint32) (vs->GetContentSize() / block_page_size);

    for( i = 0; i < block_count; i++ )
    {
        if( block_index[i] >= start && block_index[i] < start+count )
        {
            vs->MoveData( block_index[i] * block_page_size,
                          next_block * block_page_size,
                          block_page_size );
            block_index[i] = next_block;
            dirty = true;
            next_block++;
        }
    }
}
