/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * Copyright 2015 Danny Robson <danny@nerdcruft.net>
 */

#include "circular.hpp"

#include "../debug/assert.hpp"
#include "../maths.hpp"
#include "../memory/system.hpp"
#include "../posix/except.hpp"
#include "../random.hpp"
#include "../scoped.hpp"
#include "../std.hpp"

#include <unistd.h>
#include <sys/mman.h>
#include <fcntl.h>

#include <algorithm>

using cruft::buffer::circular;


///////////////////////////////////////////////////////////////////////////////
// generate a random string that could be used as a path leaf
//
// it looks a lot like a shitty tmpnam replacement because it is. we can't use
// tmpnam without security warnings being emitted by binutils linker, despite
// using it safely in this particular scenario.
static void
tmpname (std::string &str, size_t length)
{
    static const char alphanum[] =
        "abcdefghijklmnopqrstuvwxyz"
        "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
        "0123456789";

    str.resize (length);
    std::generate_n (str.begin (), length, [&] (void) {
        return *cruft::random::choose (alphanum);
    });
}


///////////////////////////////////////////////////////////////////////////////
template <typename ValueT>
circular<ValueT>::circular (size_t bytes)
{
    bytes = max (bytes, sizeof (value_type));
    bytes = round_up (bytes, memory::pagesize ());

    int fd = -1;

    constexpr size_t RETRIES = 128;
    constexpr size_t NAME_LENGTH = 16;
    std::string name (NAME_LENGTH, '\0');

    // keep generating paths and attempting to create the shm backing. we may
    // fall through this loop upon failure, so be sure to check the validity
    // of the fd at the end.
    for (size_t i = 0; fd < 0 && i < RETRIES; ++i) {
        tmpname (name, NAME_LENGTH);
        name[0] = '/';

        fd = shm_open (name.c_str (), O_EXCL | O_CREAT | O_TRUNC | O_RDWR, 0600);
    }

    if (fd < 0)
        throw std::runtime_error ("unable to generate shm name");

    // setup a desctructor for the shm data. mmap retains a reference, so do
    // this whether we succeed or fail in the next phase.
    cruft::scoped::function unlink_callback ([&name] (void) {
        shm_unlink (name.c_str ());
    });

    // embiggen to the desired size
    if (ftruncate (fd, bytes))
        posix::error::throw_code ();

    // pre-allocate a sufficiently large virtual memory block. it doesn't
    // matter much what flags we use because we'll just be overwriting it
    // shortly.
    m_begin = reinterpret_cast<ValueT*> (
        mmap (nullptr, bytes * 2, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)
    );
    if (MAP_FAILED == m_begin)
        posix::error::throw_code ();

    // preemptively setup an unmapping object in case the remapping fails
    cruft::scoped::function unmapper (
        [this, bytes] (void) noexcept { munmap (m_begin, bytes); }
    );

    // overwrite the map with two adjacent copies of the memory object. this
    // must be a shared mapping for the values to propogate across segments.
    auto prot = PROT_READ | PROT_WRITE;
    auto flag = MAP_FIXED | MAP_SHARED;

    m_begin = reinterpret_cast<ValueT*> (mmap (m_begin,         bytes, prot, flag, fd, 0));
    m_end   = reinterpret_cast<ValueT*> (mmap (m_begin + bytes, bytes, prot, flag, fd, 0));

    if (m_begin == MAP_FAILED || m_end == MAP_FAILED)
        posix::error::throw_code ();

    // all went well, disarm the failsafe
    unmapper.release ();
}


//-----------------------------------------------------------------------------
template <typename ValueT>
circular<ValueT>::~circular ()
{
    auto res = munmap (m_begin, 2 * (m_end - m_begin));
    (void)res;
    CHECK_ZERO (res);
}


///////////////////////////////////////////////////////////////////////////////
template <typename ValueT>
ValueT*
circular<ValueT>::begin (void)&
{
    return m_begin;
}


//-----------------------------------------------------------------------------
template <typename ValueT>
ValueT*
circular<ValueT>::end (void)&
{
    return m_end;
}


///////////////////////////////////////////////////////////////////////////////
template <typename ValueT>
size_t
circular<ValueT>::size (void) const
{
    return m_end - m_begin;
}


//-----------------------------------------------------------------------------
template <typename ValueT>
typename circular<ValueT>::iterator
circular<ValueT>::constrain (iterator cursor)
{
    CHECK_INCLUSIVE (cursor, m_begin, m_begin + 2 * size ());

    return m_begin + (cursor - m_begin) % size ();
}


//-----------------------------------------------------------------------------
template <typename ValueT>
cruft::view<typename circular<ValueT>::iterator>
circular<ValueT>::constrain (cruft::view<iterator> window)
{
    CHECK_INCLUSIVE (window.begin (), m_begin, m_begin + 2 * size ());
    CHECK_INCLUSIVE (window.end   (), m_begin, m_begin + 2 * size ());

    auto first = window.begin ();
    auto last  = first + window.size ();
    cruft::view res { first, last };

    CHECK_EQ (res.size (), window.size ());
    CHECK_LE (res.begin (), res.end ());
    return res;
}


///////////////////////////////////////////////////////////////////////////////
template class cruft::buffer::circular<u08>;