/*=========================================================================

  Program:   Visualization Toolkit
  Module:    $RCSfile: freerange,v $

  Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
  All rights reserved.
  See Copyright.txt or http://www.kitware.com/Copyright.htm for details.

     This software is distributed WITHOUT ANY WARRANTY; without even
     the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
     PURPOSE.  See the above copyright notice for more information.

=========================================================================*/
/*----------------------------------------------------------------------------
 Copyright (c) Sandia Corporation
 See Copyright.txt or http://www.paraview.org/HTML/Copyright.html for details.
----------------------------------------------------------------------------*/
// .NAME freerange - allocates and deallocates contiguous memory efficiently
//
// .SECTION Description
//
// .SECTION Thanks
// Thanks to David Thompson for implementing this class.

#ifndef freerange_H
#define freerange_H

#include <assert.h>

#include <vtksys/stl/algorithm>
#include <vtksys/stl/deque>
#include <vtksys/stl/functional>
#include <vtksys/stl/vector>
#include <vtksys/stl/stdexcept>
#include <vtksys/ios/iostream>
#include <stdlib.h>
#include <string.h>

#if defined(__BORLANDC__)
  // Disable Borland compiler warning messages that often occur in valid code.
#  pragma warn -8027 /* functions w/ do/for/while not expanded inline */
#endif

/**\brief A free list template class in the spirit of the STL.
 *
 * A free list is a vector of items stored by value. Since items
 * are stored by value, it avoids the overhead of memory allocation
 * and deallocation as items are inserted and removed. When an item
 * is removed, it is not destroyed; a mark is made in a vector of
 * "available" positions. The next time an entry is inserted, the
 * most recently removed item's position in the vector of all items
 * is used to store the newly inserted member.
 */
template< class T, class idx_t, T empty_entry=-1 >
class freerange
{
  public:
    typedef vtksys_stl::deque<idx_t>                dead_list_entry_t;
    typedef vtksys_stl::vector< dead_list_entry_t > dead_list_t;
  protected:
    T* array ;
    idx_t array_len ; // max id + 1
    idx_t array_top ; // id of next available entry
    dead_list_t dead ;
    int dead_size;
    idx_t used;
  public:
    freerange()
    {
      array_len = 16 ;
      array_top = 0 ;
      array = new T [ array_len ];
      for ( idx_t i=array_top; i<array_len; ++i )
        array[i] = empty_entry;

      dead_size = 27;
      dead.resize( dead_size+1 ); // dead[0] is unused.

      used = 0;
    }
    ~freerange()
    {
      delete [] array ;
    }

    idx_t size() const { return used; }
    idx_t max_id() const { return array_top - 1; }
    idx_t capacity() const { return array_len; }

    // Access to the array itself.
    T* pointer(idx_t pos) { return array + pos; }

    int max_chunked_grab_size() const { return dead_size; }
    void set_max_chunked_grab_size( int c )
    {
      if ( c == dead_size )
        return;
      if ( c < 1 )
        return;
      dead_size = c;
      // FIXME: the following resize will leak any chunks larger than c!
      dead.resize( dead_size + 1 );
    }

    bool grab_internal( idx_t& v, int num ) 
    {
      if ( (num <= dead_size) && (! dead[num].empty()) ) {
        v = dead[num].front() ;
        dead[num].pop_front() ;
        used += num;
        return (v != 0);
      }
      if ( array_top + num > array_len ) {
        // OK, time to resize the array
        do {
          array_len <<= 1 ;
        } while ( array_top + num > array_len );

        T* array_tmp = new T [ array_len ];
        if ( ! array_tmp )
          throw vtksys_stl::runtime_error( "freerange memory allocation failed" );
        for ( idx_t i=0; i<array_top; i++ )
          array_tmp[i] = array[i];
        delete [] array;
        array = array_tmp;
      }
      v = array_top;
      array_top += num;
      used += num;
      return (v != 0) ;
    }

    idx_t grab( int num=1 )
    {
      if ( num <= 0 )
        return -1;
      idx_t v;
      if ( grab_internal( v, num ) )
        for (idx_t i=0; i<num; ++i )
          array[ v + i ] = empty_entry;
      return v;
    }

    idx_t grabAndAssign( const T& src )
    { idx_t entry; grab_internal(entry,1) ; array[entry] = src ; return entry ; }

    void mark_as_freed( idx_t i, idx_t num )
    {
      for ( idx_t stop=i+num; i < stop; i++ )
        array[ i ] = empty_entry;
    }

    void free( idx_t i, idx_t num=1 )
    {
      if ( num <= 0 )
        return;

      if ( i > array_top )
        { // trying to fee past end of storage. continue quietly
        return;
        }
      else if ( i + num > array_top )
        { // trying to fee past end of storage. truncate and continue quietly
        array_top = i;
        used -= array_top - i;
        return;
        }
      else if ( i == array_top - num )
        {
        array_top -= num ;
        used -= num;
        return ;
        }
      mark_as_freed( i, num );
      // break up large freed blocks into random sizes that all fit in our dead pool
      while ( num >= dead_size ) {
        // Not all OS's have lrand48(), try rand() instead.
        //register int deadpool = lrand48() % dead_size + 1;
        register int deadpool = rand() % dead_size + 1;
        dead[deadpool].push_front( i );
        i += deadpool;
        num -= deadpool;
        used -= deadpool;
      }
      if ( num ) {
        dead[num].push_front( i );
        used -= num;
      }
    }

    T& operator[] ( idx_t i )
    {
      return array[i] ;
    }

    const T& operator[] ( idx_t i ) const
    {
      return array[i] ;
    }

    void resize( idx_t new_size )
    {
      idx_t i = 0;

      // note that resizing doesn't guarantee the number of contiguous free
      // or even free items in the list... it only guarantees storage for
      // some total number of items assuming that all the holes get filled.
      if ( new_size == array_len ) {
        return;
      } else if ( new_size < array_len ) {
        // time to remove elements AND possibly dead entries
        // until we're trimmed down to size
        array_len = new_size;
        T* array_tmp = new T [ array_len ];
        if ( ! array_tmp )
          throw vtksys_stl::runtime_error( "freerange memory allocation failed" );
        if ( array_top > array_len ) {
          used -= array_top - array_len;
          array_top = array_len;
        }
        for ( i=0; i<array_top; i++ )
          array_tmp[i] = array[i];
        delete [] array;
        for ( i=array_top; i<new_size; ++i )
          array_tmp[i] = empty_entry;
        array = array_tmp;
        for ( i=1; i<dead_size; i++ ) { // dead[0] is unused
          typename dead_list_entry_t::iterator it = vtksys_stl::remove_if( dead[i].begin(), dead[i].end(), vtksys_stl::bind2nd( vtksys_stl::greater_equal<idx_t>(), new_size ) );
          dead[i].erase( it, dead[i].end() );
        }
      } else { // new_size > array_len
        // allocate more memory
        array_len = new_size;
        T* array_tmp = new T [ array_len ];
        if ( ! array_tmp )
          throw vtksys_stl::runtime_error( "freerange memory allocation failed" );
        for ( i=0; i<array_top; i++ )
          array_tmp[i] = array[i];
        for ( i=array_top; i<new_size; ++i )
          array_tmp[i] = empty_entry;
        delete [] array;
        array = array_tmp;
      }
    }

    // dead[0] is unused!
    typename dead_list_t::iterator dead_begin() { return dead.begin()+1; }
    typename dead_list_t::iterator dead_end() { return dead.end(); }

    // dead[0] is unused!
    typename dead_list_t::const_iterator dead_begin() const { return dead.begin()+1; }
    typename dead_list_t::const_iterator dead_end() const { return dead.end(); }

    void clear()
    {
      array_top = 0;
      used = 0;
      for ( typename dead_list_t::iterator it = dead_begin(); it != dead_end(); ++it )
        it->clear();
    }

    T empty_entry_value() { return empty_entry; }

    class iterator ;
    friend class iterator ;

    iterator begin() ;
    iterator end() ;

    class iterator {
      protected:
        idx_t posn ;
        freerange* fl ;

        void check( idx_t p )
        {
          if ( ! fl ) {
            posn = 0 ;
            return ;
          }

          while ( ((*fl)[p] == empty_entry) && (p < fl->array_top) ) 
            p++ ;

          if ( p >= fl->array_top ) {
            fl = 0 ;
            posn = 0 ;
          } else {
            posn = p ;
          }
        }

      public:
        typedef vtksys_stl::bidirectional_iterator_tag iterator_category;

        iterator()
        { fl = 0 ; posn = 0 ; }

        iterator( freerange<T,idx_t,empty_entry>* list, idx_t p )
        { fl = list ; check(p) ; }

        iterator( const iterator& i )
        { fl = i.fl ; posn = i.posn ; }

        ~iterator() { } ;

        iterator& operator = ( idx_t p )
        { check( p ) ; }

        idx_t position() const { return posn ; }

        bool operator == ( const iterator& i ) const
        { return ((i.fl == fl) && (i.posn == posn)) ; }

        bool operator != ( const iterator& i ) const
        { return ( (fl != i.fl) || (posn != i.posn) ) ; }

        iterator& operator ++ ()
        { check( ++posn ) ; return *this ; }

        const T& operator * () const
        { assert(fl) ; return fl->array[posn] ; }

        //const T* operator -> () const
        //{ assert(fl) ; return &fl->array[posn] ; }

        T& operator * ()
        { assert(fl) ; return fl->array[posn] ; }

        //T* operator -> ()
        //{ assert(fl) ; return &fl->array[posn] ; }
    } ;

} ;


template< class T, class idx_t, T empty_entry >
typename freerange<T,idx_t,empty_entry>::iterator freerange<T,idx_t,empty_entry>::begin()
{
  return iterator( this, 0 ) ;
}

template< class T, class idx_t, T empty_entry >
typename freerange<T,idx_t,empty_entry>::iterator freerange<T,idx_t,empty_entry>::end() 
{
  return iterator( 0, 0 ) ;
}

#endif // freerange_H
