#pragma once
#ifndef __binbuf_h__
#define __binbuf_h__
#include <cstdint>
#include <cctype>
#include <iostream>
#include <string>
#include <array>
using std::string;
/*
 * Binary data buffer.  Mainly for binary network protocols, to build messages
 * for sending and extract data from received messages.  Allows general access
 * to the whole buffer for network transmission, as well as building or reading
 * from front to to back.  The data itself is held in a C++ template array.
 * This object does not try to remember how much (if any) of its data is
 * meaningful, just how much space it contains and where the reading/writing
 * process is currently.
 */
class BinaryBuffer {
public:
        static const int SIZE = 1024;
        BinaryBuffer(): m_rwloc(0) { m_data.fill(0); }
        
        // Direct access to the data contents.  Resets the reading or writing
        // pointer because it is intended to be used to receive data to be
        // extracted, or send data which has been inserted.
        auto & arr() { reset_loc(); return m_data; }
        unsigned char *buf() { return arr().data(); }
        // Gets the full physical size.  Same as SIZE.
        auto bufsize() const { return m_data.size(); }
        // The read/write location.  When writing the buffer, this is the
        // amount of meaningful data added, and is probably the size needed
        // for any send operation.  When extracting, this is the amount
        // of data extracted.  Extraction would usually continue until this
        // equals the size of the message received.
        unsigned int loc() const { return m_rwloc; }
        void reset_loc() { m_rwloc = 0; }
        // Skip a portion of the data.  May be used on reading.
        void skip(int n) { m_rwloc += n; }
        // Add a single byte.
        void put(unsigned char byte) { m_data[m_rwloc++] = byte; }
        // Add arbitrary bytes.  Adds all of the argument array, or
        // a portion of an argument size is specified.
        void put(const unsigned char *start, int amt) {
                while(amt-- > 0) { put(*start++); }
        }
        template <unsigned int ADDSIZE>
        void put(const std::array<unsigned char, ADDSIZE> &data,
                 unsigned int amt = ADDSIZE) {
                put(data.data(),amt);
        }
        // Get the next byte.
        unsigned char get() { return m_data[m_rwloc++]; }
        // Peek the next byte without updating the location pointer.
        unsigned char peek() const { return m_data[m_rwloc]; }
        // Extract arbitrary bytes of a 
        void get(unsigned char *start, int amt) {
                while(amt-- > 0) { *start++ = get(); }
        }
        template <unsigned int GETSIZE>
        void get(std::array<unsigned char, GETSIZE> &data,
                 unsigned int amt = GETSIZE) {
                get(data.data(),amt);
        }
        // Put a numeric value in big-endian order (largest-order byte first)
        // T should be a numeric type.  Unless you're being carful about the
        // size of x, you might want to use one of the specific put8, put16
        // or put32.
        template<typename T>
        void put_be(T x)
        {
                // How to get the largest byte in the smallest place.
                unsigned char shift = 8*((sizeof x)-1);
                // Place each byte in descending 
                for(int i = sizeof x; i--; shift -= 8) {
                        put(static_cast<unsigned char>((x>>shift)&0x0ff));
                }
                        
        }
        void put8(uint8_t s) { put(s); }
        void put16(uint16_t s) { put_be(s); }
        void put32(uint32_t i) { put_be(i); }
        // Get a numeric value in big-endian order (largest-order byte first)
        // T should be a numeric type.  Since there's no parameter for the
        // the system to figure out the type parameter T, you will need to 
        // provide it with <> on the call.  So it might just be easier to
        // use one of the specific get8, get16 or get32.
        template<typename T>
        T get_be()
        {
                // Place each byte in descending
                T ret = 0;
                for(int i = sizeof(T); i--; ) {
                        ret = (ret << 8) | (get() & 0x0ff);
                }
                return ret;
        }
        uint8_t get8() { return get(); }
        uint16_t get16() { return get_be<uint16_t>(); }
        uint32_t get32() { return get_be<uint32_t>(); }
        // Insert a string using a length byte followed by it's characters.
        // For DNS requests, the limit is 63 characters, for the basic format
        // 255 character limit.  This method performs no check on all that,
        // so don't abuse it.
        void puts(std::string s);
        // Get a string by reading a length byte then that many characters.
        std::string gets();
        // Get or put a host name.  Put produces no compression, but get
        // recognizes it.
        void puthost(string host);
        string gethost(); 
        // Dump the contents up to the location, or the indicated size.
        void dump(int limit = -1, std::ostream &strm = std::cout) const;
                
private:
        // Helper for gethost() that appends one segment and returns the
        // following location.
        unsigned int segment(unsigned int start, string &accum);
        // Detect the first byte of a hostname back reference.
        bool is_jump(unsigned char ch) {
                return (ch & 0xc0) == 0xc0;
        }
        
        // The data is here.
        std::array<unsigned char, SIZE> m_data;
        // This is the current location for the next insert or extract.
        unsigned int m_rwloc;
};
#endif