// This file is part of the ustl library, an STL implementation.
//
// Copyright (C) 2005 by Mike Sharov <msharov@users.sourceforge.net>
// This file is free software, distributed under the MIT License.
//
// memblock.cc
//
//	Allocated memory block.
//

#include "fstream.h"
#include "mistream.h"
#include "memblock.h"
#include "ualgo.h"
#include "uassert.h"
#include "umemory.h"

#include <errno.h>

namespace ustl {

/// Allocates 0 bytes for the internal block.
memblock::memblock (void)
: memlink (),
  m_Capacity (0)
{
}

/// Allocates \p n bytes for the internal block.
memblock::memblock (size_type n)
: memlink (),
  m_Capacity (0)
{
    resize (n);
}

/// links to \p p, \p n. Data can not be modified and will not be freed.
memblock::memblock (const void* p, size_type n)
: memlink (),
  m_Capacity (0)
{
    assign (p, n);
}

/// Links to what \p b is linked to.
memblock::memblock (const cmemlink& b)
: memlink (),
  m_Capacity (0)
{
    assign (b);
}

/// Links to what \p b is linked to.
memblock::memblock (const memlink& b)
: memlink (),
  m_Capacity (0)
{
    assign (b);
}

/// Links to what \p b is linked to.
memblock::memblock (const memblock& b)
: memlink (),
  m_Capacity (0)
{
    assign (b);
}

/// Frees internal data, if appropriate
/// Only if the block was allocated using resize, or linked to using Manage,
/// will it be freed. Also, Derived classes should call DestructBlock from
/// their destructor, because upstream virtual functions are unavailable at
/// this point and will not be called automatically.
///
memblock::~memblock (void)
{
    if (!is_linked())
	deallocate();
}

/// resizes the block to \p newSize bytes, reallocating if necessary.
void memblock::resize (size_type newSize, bool bExact)
{
    if (m_Capacity < newSize + minimumFreeCapacity())
	reserve (newSize, bExact);
    memlink::resize (newSize);
}

/// Frees internal data.
void memblock::deallocate (void) throw()
{
    if (m_Capacity) {
	assert (cdata() && "Internal error: space allocated, but the pointer is NULL");
	assert (data() && "Internal error: read-only block is marked as allocated space");
	free (data());
    }
    unlink();
}

/// Assumes control of the memory block \p p of size \p n.
/// The block assigned using this function will be freed in the destructor.
void memblock::manage (void* p, size_type n)
{
    assert (p || !n);
    assert (!m_Capacity && "Already managing something. deallocate or unlink first.");
    link (p, n);
    m_Capacity = n;
}

/// "Instantiate" a linked block by allocating and copying the linked data.
void memblock::copy_link (void)
{
    const cmemlink l (*this);
    if (is_linked())
	unlink();
    assign (l);
}
 
/// Copies data from \p p, \p n.
void memblock::assign (const void* p, size_type n)
{
    assert ((p != (const void*) cdata() || size() == n) && "Self-assignment can not resize");
    resize (n);
    copy (p, n);
}

/// \brief Reallocates internal block to hold at least \p newSize bytes.
///
/// Additional memory may be allocated, but for efficiency it is a very
/// good idea to call reserve before doing byte-by-byte edit operations.
/// The block size as returned by size() is not altered. reserve will not
/// reduce allocated memory. If you think you are wasting space, call
/// deallocate and start over. To avoid wasting space, use the block for
/// only one purpose, and try to get that purpose to use similar amounts
/// of memory on each iteration.
///
void memblock::reserve (size_type newSize, bool bExact)
{
    if ((newSize += minimumFreeCapacity()) <= m_Capacity)
	return;
    void* oldBlock (is_linked() ? NULL : data());
    if (!bExact)
	newSize = Align (newSize, c_PageSize);
    pointer newBlock = (pointer) realloc (oldBlock, newSize);
    if (!newBlock)
#if PLATFORM_ANDROID
        printf("bad_alloc\n");
#else
	throw bad_alloc (newSize);
#endif
    if (!oldBlock && cdata())
	copy_n (cdata(), min (size() + 1, newSize), newBlock);
    link (newBlock, size());
    m_Capacity = newSize;
}

/// Swaps the contents with \p l
void memblock::swap (memblock& l)
{
    memlink::swap (l);
    ::ustl::swap (m_Capacity, l.m_Capacity);
}

/// Shifts the data in the linked block from \p start to \p start + \p n.
memblock::iterator memblock::insert (iterator start, size_type n)
{
    const uoff_t ip = start - begin();
    assert (ip <= size());
    resize (size() + n, false);
    memlink::insert (iat(ip), n);
    return (iat (ip));
}

/// Shifts the data in the linked block from \p start + \p n to \p start.
memblock::iterator memblock::erase (iterator start, size_type n)
{
    const uoff_t ep = start - begin();
    assert (ep + n <= size());
    memlink::erase (start, n);
    memlink::resize (size() - n);
    return (iat (ep));
}

/// Unlinks object.
void memblock::unlink (void)
{
    memlink::unlink();
    m_Capacity = 0;
}

/// Reads the object from stream \p s
void memblock::read (istream& is)
{
    written_size_type n;
    is >> n;
    is.verify_remaining ("read", "ustl::memblock", n);
    resize (n);
    is.read (data(), writable_size());
    is.align (alignof (n));
}

/// Reads the entire file \p "filename".
void memblock::read_file (const char* filename)
{
    fstream f;
    f.exceptions (fstream::allbadbits);
    f.open (filename, fstream::in);
    const off_t fsize (f.size());
    reserve (fsize);
    f.read (data(), fsize);
    f.close();
    resize (fsize);
}

} // namespace ustl