#ifndef UTILS_HASH_H
#define UTILS_HASH_H

#include <cassert>
#include <cstddef>
#include <cstdint>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>

namespace utils {
/*
  We provide a family of hash functions that are supposedly higher
  quality than what is guaranteed by the standard library. Changing a
  single bit in the input should typically change around half of the
  bits in the final hash value. The hash functions we previously used
  turned out to cluster when we tried hash tables with open addressing
  for state registries.

  The low-level hash functions are based on lookup3.c by Bob Jenkins,
  May 2006, public domain. See http://www.burtleburtle.net/bob/c/lookup3.c.

  To hash an object x, it is represented as a sequence of 32-bit
  pieces (called the "code" for x, written code(x) in the following)
  that are "fed" to the main hashing function (implemented in class
  HashState) one by one. This allows a compositional approach to
  hashing. For example, the code for a pair p is the concatenation of
  code(x.first) and code(x.second).

  A simpler compositional approach to hashing would first hash the
  components of an object and then combine the hash values, and this
  is what a previous version of our code did. The approach with an
  explicit HashState object is stronger because the internal hash
  state is larger (96 bits) than the final hash value and hence pairs
  <x, y> and <x', y> where x and x' have the same hash value don't
  necessarily collide. Another advantage of our approach is that we
  can use the same overall hashing approach to generate hash values of
  different types (e.g. 32-bit vs. 64-bit unsigned integers).

  To extend the hashing mechanism to further classes, provide a
  template specialization for the "feed" function. This must satisfy
  the following requirements:

  A) If x and y are objects of the same type, they should have code(x)
     = code(y) iff x = y. That is, the code sequence should uniquely
     describe each logically distinct object.

     This requirement avoids unnecessary hash collisions. Of course,
     there will still be "necessary" hash collisions because different
     code sequences can collide in the low-level hash function.

  B) To play nicely with composition, we additionally require that feed
     implements a prefix code, i.e., for objects x != y of the same
     type, code(x) must not be a prefix of code(y).

     This requirement makes it much easier to define non-colliding
     code sequences for composite objects such as pairs via
     concatenation: if <a, b> != <a', b'>, then code(a) != code(a')
     and code(b) != code(b') is *not* sufficient for concat(code(a),
     code(b)) != concat(code(a'), code(b')). However, if we require a
     prefix code, it *is* sufficient and the resulting code will again
     be a prefix code.

  Note that objects "of the same type" is meant as "logical type"
  rather than C++ type.

  For example, for objects such as vectors where we expect
  different-length vectors to be combined in the same containers (=
  have the same logical type), we include the length of the vector as
  the first element in the code to ensure the prefix code property.

  In contrast, for integer arrays encoding states, we *do not* include
  the length as a prefix because states of different sizes are
  considered to be different logical types and should not be mixed in
  the same container, even though they are represented by the same C++
  type.
*/

static_assert(sizeof(unsigned int) == 4, "unsigned int has unexpected size");

/*
  Circular rotation (http://stackoverflow.com/a/31488147/224132).
*/
inline unsigned int rotate(unsigned int value, unsigned int offset) {
    return (value << offset) | (value >> (32 - offset));
}

/*
  Internal class storing the state of the hashing process. It should only be
  instantiated by functions in this file.
*/
class HashState {
    std::uint32_t a, b, c;
    int pending_values;

    /*
      Mix the three 32-bit values bijectively.

      Any information in (a, b, c) before mix() is still in (a, b, c) after
      mix().
    */
    void mix() {
        a -= c;
        a ^= rotate(c, 4);
        c += b;
        b -= a;
        b ^= rotate(a, 6);
        a += c;
        c -= b;
        c ^= rotate(b, 8);
        b += a;
        a -= c;
        a ^= rotate(c, 16);
        c += b;
        b -= a;
        b ^= rotate(a, 19);
        a += c;
        c -= b;
        c ^= rotate(b, 4);
        b += a;
    }

    /*
      Final mixing of the three 32-bit values (a, b, c) into c.

      Triples of (a, b, c) differing in only a few bits will usually produce
      values of c that look totally different.
    */
    void final_mix() {
        c ^= b;
        c -= rotate(b, 14);
        a ^= c;
        a -= rotate(c, 11);
        b ^= a;
        b -= rotate(a, 25);
        c ^= b;
        c -= rotate(b, 16);
        a ^= c;
        a -= rotate(c, 4);
        b ^= a;
        b -= rotate(a, 14);
        c ^= b;
        c -= rotate(b, 24);
    }

public:
    HashState()
        : a(0xdeadbeef),
          b(a),
          c(a),
          pending_values(0) {
    }

    void feed_ints(const int *values, int length) {
        // Handle most of the key.
        while (length > 3) {
            a += values[0];
            b += values[1];
            c += values[2];
            mix();
            length -= 3;
            values += 3;
        }

        // Handle the last 3 unsigned ints. All case statements fall through.
        switch (length) {
        case 3:
            c += values[2];
        case 2:
            b += values[1];
        case 1:
            a += values[0];
            final_mix();
        // case 0: nothing left to add.
        case 0:
            break;
        }
    }

    void feed(std::uint32_t value) {
        assert(pending_values != -1);
        if (pending_values == 3) {
            mix();
            pending_values = 0;
        }
        if (pending_values == 0) {
            a += value;
            ++pending_values;
        } else if (pending_values == 1) {
            b += value;
            ++pending_values;
        } else if (pending_values == 2) {
            c += value;
            ++pending_values;
        }
    }

    std::uint32_t get_hash32() {
        assert(pending_values != -1);
        if (pending_values) {
            /*
               pending_values == 0 can only hold if we never called
               feed(), i.e., if we are hashing an empty sequence.
               In this case we don't call final_mix for compatibility
               with the original hash function by Jenkins.
            */
            final_mix();
        }
        pending_values = -1;
        return c;
    }

    std::uint64_t get_hash64() {
        assert(pending_values != -1);
        if (pending_values) {
            // See comment for get_hash32.
            final_mix();
        }
        pending_values = -1;
        return (static_cast<std::uint64_t>(b) << 32) | c;
    }
};


/*
  These functions add a new object to an existing HashState object.

  To add hashing support for a user type X, provide an override
  for utils::feed(HashState &hash_state, const X &value).
*/
static_assert(
    sizeof(int) == sizeof(std::uint32_t),
    "int and uint32_t have different sizes");
inline void feed(HashState &hash_state, int value) {
    hash_state.feed(static_cast<std::uint32_t>(value));
}

static_assert(
    sizeof(unsigned int) == sizeof(std::uint32_t),
    "unsigned int and uint32_t have different sizes");
inline void feed(HashState &hash_state, unsigned int value) {
    hash_state.feed(static_cast<std::uint32_t>(value));
}

inline void feed(HashState &hash_state, std::uint64_t value) {
    hash_state.feed(static_cast<std::uint32_t>(value));
    value >>= 32;
    hash_state.feed(static_cast<std::uint32_t>(value));
}

template<typename T>
void feed(HashState &hash_state, const T *p) {
    // This is wasteful in 32-bit mode, but we plan to discontinue 32-bit compiles anyway.
    feed(hash_state, reinterpret_cast<std::uint64_t>(p));
}

template<typename T1, typename T2>
void feed(HashState &hash_state, const std::pair<T1, T2> &p) {
    feed(hash_state, p.first);
    feed(hash_state, p.second);
}

template<typename T>
void feed(HashState &hash_state, const std::vector<T> &vec) {
    /*
      Feed vector size to ensure that no two different vectors of the same type
      have the same code prefix.
    */
    feed(hash_state, vec.size());
    for (const T &item : vec) {
        feed(hash_state, item);
    }
}


/*
  Public hash functions.

  get_hash() is used internally by the HashMap and HashSet classes below. In
  more exotic use cases, such as implementing a custom hash table, you can also
  use `get_hash32()`, `get_hash64()` and `get_hash()` directly.
*/
template<typename T>
std::uint32_t get_hash32(const T &value) {
    HashState hash_state;
    feed(hash_state, value);
    return hash_state.get_hash32();
}

template<typename T>
std::uint64_t get_hash64(const T &value) {
    HashState hash_state;
    feed(hash_state, value);
    return hash_state.get_hash64();
}

template<typename T>
std::size_t get_hash(const T &value) {
    return static_cast<std::size_t>(get_hash64(value));
}


// This struct should only be used by HashMap and HashSet below.
template<typename T>
struct Hash {
    std::size_t operator()(const T &val) const {
        return get_hash(val);
    }
};

/*
  Aliases for hash sets and hash maps in user code. All user code should use
  utils::UnorderedSet and utils::UnorderedMap instead of std::unordered_set and
  std::unordered_map.

  To hash types that are not supported out of the box, implement utils::feed.
*/
template<typename T1, typename T2>
using HashMap = std::unordered_map<T1, T2, Hash<T1>>;

template<typename T>
using HashSet = std::unordered_set<T, Hash<T>>;


/* Transitional aliases and functions */
template<typename T1, typename T2>
using UnorderedMap = std::unordered_map<T1, T2>;

template<typename T>
using UnorderedSet = std::unordered_set<T>;

template<typename T>
inline void hash_combine(size_t &hash, const T &value) {
    std::hash<T> hasher;
    /*
      The combination of hash values is based on issue 6.18 in
      http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2005/n1756.pdf.
      Boost combines hash values in the same way.
    */
    hash ^= hasher(value) + 0x9e3779b9 + (hash << 6) + (hash >> 2);
}

template<typename Sequence>
size_t hash_sequence(const Sequence &data, size_t length) {
    size_t hash = 0;
    for (size_t i = 0; i < length; ++i) {
        hash_combine(hash, data[i]);
    }
    return hash;
}
}

namespace std {
template<typename T>
struct hash<std::vector<T>> {
    size_t operator()(const std::vector<T> &vec) const {
        return utils::hash_sequence(vec, vec.size());
    }
};

template<typename TA, typename TB>
struct hash<std::pair<TA, TB>> {
    size_t operator()(const std::pair<TA, TB> &pair) const {
        size_t hash = 0;
        utils::hash_combine(hash, pair.first);
        utils::hash_combine(hash, pair.second);
        return hash;
    }
};
}

#endif
