#ifndef __COGAPS_ATOMIC_DOMAIN_H__
#define __COGAPS_ATOMIC_DOMAIN_H__

#include "Archive.h"

#include <boost/unordered_set.hpp>

#include <cstddef>
#include <map>
#include <stdint.h>
#include <vector>

class AtomicDomain;

// Want the neighbors to be pointers, but can't use pointers since vector
// is resized, shifted integers allow for 0 to be "null" and 1 to be the
// first index. AtomicDomain is responsible for managing this correctly.
struct Atom
{
private:    

    friend class AtomicDomain;
    uint64_t leftNdx; // shift these up 1, 0 == no neighbor
    uint64_t rightNdx;

public:    

    uint64_t pos;
    float mass;

    Atom(uint64_t p, float m)
        : leftNdx(0), rightNdx(0), pos(p), mass(m)
    {}

    bool operator==(const Atom &other) const
    {
        return pos == other.pos;
    }

    bool operator!=(const Atom &other) const
    {
        return !(pos == other.pos);
    }

    // serialization
    friend Archive& operator<<(Archive &ar, Atom &a);
    friend Archive& operator>>(Archive &ar, Atom &a);
};

struct RawAtom
{
    uint64_t pos;
    float mass;

    RawAtom() : pos(0), mass(0.f) {}
    RawAtom(uint64_t p, float m) : pos(p), mass(m) {}
};

// data structure that holds atoms
class AtomicDomain
{
private:

    // size of atomic domain
    uint64_t mDomainSize;

    // domain storage
    std::vector<Atom> mAtoms;
    std::map<uint64_t, uint64_t> mAtomPositions;

    boost::unordered_set<uint64_t> mUsedPositions;

    mutable std::vector<RawAtom> mInsertCache;
    mutable std::vector<uint64_t> mEraseCache;

    mutable unsigned mInsertCacheIndex;
    mutable unsigned mEraseCacheIndex;

    Atom& _left(const Atom &atom);
    Atom& _right(const Atom &atom);

public:

    void setDomainSize(uint64_t size) { mDomainSize = size; }

    // access atoms
    Atom front() const;
    Atom randomAtom() const;
    uint64_t randomFreePosition() const;
    uint64_t size() const;

    const Atom& left(const Atom &atom) const;
    const Atom& right(const Atom &atom) const;
    bool hasLeft(const Atom &atom) const;
    bool hasRight(const Atom &atom) const;

    // modify domain
    Atom insert(uint64_t pos, float mass);
    void erase(uint64_t pos);
    void cacheInsert(uint64_t pos, float mass) const;
    void cacheErase(uint64_t pos) const;
    void updateMass(uint64_t pos, float newMass);
    void flushCache();
    void resetCache(unsigned n);

    // serialization
    friend Archive& operator<<(Archive &ar, AtomicDomain &domain);
    friend Archive& operator>>(Archive &ar, AtomicDomain &domain);
};

#endif