#ifndef INCLUDED_BOBCAT_SHAREDMEMORY_
#define INCLUDED_BOBCAT_SHAREDMEMORY_

#include <ios>

#include <bobcat/fswap>
#include <bobcat/sharedpos>
#include <bobcat/exception>

namespace FBB
{

class SharedSegment;

struct SharedEnum__
{
    enum SizeUnit
    {
        kB = 10,
        MB = 20,
        GB = 30
    };
};

class SharedMemory: public virtual SharedEnum__
{
    friend std::ostream &operator<<(std::ostream &out,
                                    SharedMemory const &mem);

    static size_t const s_pageSize = 1 << 12;

        // updated by the non-default constructors.
    int d_id = -1;                      // id of SharedSegment
    SharedSegment *d_sharedSegment = 0; // points to the attached shared
                                        //  memory. This is NOT a pointer
                                        // to dynamically allocated memory
                                        // but a static_cast pointer to
                                        // the attached shared data segment
    SharedPos d_pos;

    size_t d_lockCount = 0;             // # of nested locks
    char *d_data = 0;                   // pointer to shared memory data

    public:
        SharedMemory() = default;

        SharedMemory(SharedMemory const &other) = delete;

        SharedMemory(size_t maxSize, SizeUnit sizeUnit,
                     size_t access = 0600);             // 2: creation mode;

        SharedMemory(int id);                           // 3

        ~SharedMemory();

        SharedMemory &operator=(SharedMemory &&tmp);


        std::streamsize blockOffset() const;// offset matching offset(),
                                            // relative to the current data
                                            // block.

        void clear();                   // clear all existing data and reduce
                                        // until only the segment at
                                        // d_sharedSegment

        size_t dataSegmentSize() const;

        int get();                      // get char from d_segmentData
                                        // locks), or EOF

        int id() const;                 // id of the d_sharedSegment segment


                // after kill/remove: shared segment is unusable
        void kill();                    // delete all shared segments w/o locks

        std::streamsize maxOffset() const;

        std::streamsize nReadable() const;  // beyond last readable byte
        std::streamsize offset() const;     // read/write offset relative to
                                            // ios::beg

        char *ptr();                        // 0 if at maxOffset

        int put(int ch);                // put char at d_offset (locks),
                                        // ch == EOF immediately returns EOF

                                        // read len chars, return nRead or -1,
                                        // locks
        int read(char *data, std::streamsize len);

        template <typename Type>
        int read(Type *value);              // 1.f

        template <typename Type>            // 2.f
        int read(std::ios::off_type offset, Type *value,
                  std::ios::seekdir origin = std::ios::beg);

                // after kill/remove: shared segment is unusable
        void remove();                  // delete all shared segments.

                                        // returns -1 if inaccessible
        std::ios::pos_type seek(std::ios::off_type offset,
                                std::ios::seekdir origin = std::ios::beg);

        std::streamsize showmanyc();

        void swap(SharedMemory &other);

        bool truncate(std::streamsize offset);  // nReadable is reduced to
                                            // offset


                                        // write len bytes at d_offset
                                        // locks, returns #written or -1
        int write(char const *data, std::streamsize len);

        template <typename Type>
        int write(Type const *value);       // 1.f

        template <typename Type>            // 2.f
        int write(std::ios::off_type offset, Type const *value,
                  std::ios::seekdir origin = std::ios::beg);

        template <typename SharedType, typename ... Params>
        SharedType *install(std::streamsize *offset, Params &&...params);

    private:
        std::ostream &insert(std::ostream &out) const;


                                        // lockAll: d_lockCount should be 0.
        void lockAll();                 // lock all block[] mutexes
        void unlockAll();               // unlock all block[] mutexes

        void lock(size_t idx);          // (recursively) lock block idx
        void unlock(size_t idx);        // unlock block idx

        void clearAll();                // clear without locking

        bool blockAvailable(size_t idx);

        void map();                     // 1    maps shared data to d_data
        void map(size_t idx);           // 2    only called by load


        int writeBlock(char const *data, size_t len);   // locks, returns
                                                        // #written or -1
        int readBlock(char *data, size_t len);          // same, but now reads
                                                        // both update offset

        static size_t computeSegmentSize(
                            size_t *nBlocks,
                            long long maxMemory, SizeUnit sizeUnit);

        void validate() const;
};

inline std::streamsize SharedMemory::blockOffset() const
{
    return d_pos.blockOffset();
}
inline size_t SharedMemory::dataSegmentSize() const
{
    return d_sharedSegment ? d_sharedSegment->segmentSize() : 0;
}
inline int SharedMemory::id() const
{
    return d_id;
}
template <typename SharedType, typename ... Params>
SharedType *SharedMemory::install(std::streamsize *offsetPtr,
                                  Params &&...params)
{
    size_t segmentSize = dataSegmentSize();
    if (segmentSize == 0)
        throw Exception() << "SharedMemory::install: no memory";

                        // find a suitable offset for the shared memory object
    size_t begin = blockOffset();
    size_t end = (begin + sizeof(SharedType)) % segmentSize;
    if (begin + sizeof(SharedType) != end)
        seek((1 + offset() / segmentSize) * segmentSize);

                                // determine the object's offset and address
    std::streamsize location = offset();
    void *address = ptr();

                                            // go to the object's last byte
    if (address == 0 || seek(sizeof(SharedType) - 1) == -1)
        throw Exception() << "SharedMemory::install: out of memory.";

            // make sure shmem knows it exists by writing a byte at its last
            // byte location
    put(0);

    if (offsetPtr)
        *offsetPtr = location;

            // install the object at 'address'
    return new (address) SharedType(std::forward<Params>(params)...);
}
inline std::streamsize SharedMemory::maxOffset() const
{
    return d_pos.maxOffset();
}
inline std::streamsize SharedMemory::nReadable() const
{
    return d_sharedSegment ? d_sharedSegment->nReadable() : 0;
}
inline std::streamsize SharedMemory::offset() const
{
    return d_pos.offset();
}
inline SharedMemory &SharedMemory::operator=(SharedMemory &&tmp)
{
    swap(tmp);
    return *this;
}
inline std::ostream &operator<<(std::ostream &out, SharedMemory const &mem)
{
    return mem.insert(out);
}
template <typename Type>
inline int SharedMemory::read(Type *value)
{
    return read(reinterpret_cast<char *>(value), sizeof(Type));
}
template <typename Type>
int SharedMemory::read(std::ios::off_type offset, Type *value,
                        std::ios::seekdir origin)
{
    if (seek(offset, origin) == -1)
        throw Exception() << "SharedMemory::read: seek to " << offset <<
                                                                "failed";

    return read(value);
}
inline void SharedMemory::swap(SharedMemory &other)
{
    FBB::fswap(*this, other);
}
template <typename Type>
inline int SharedMemory::write(Type const *value)
{
    return write(reinterpret_cast<char const *>(value), sizeof(Type));
}
template <typename Type>
int SharedMemory::write(std::ios::off_type offset, Type const *value,
                        std::ios::seekdir origin)
{
    if (seek(offset, origin) == -1)
        throw Exception() << "SharedMemory::write: seek to " << offset <<
                                                                "failed";
    return write(value);
}

} // FBB
#endif
