// -*- mode: c++; c-basic-offset:4 -*-

// This file is part of libdap, A C++ implementation of the OPeNDAP Data
// Access Protocol.

// Copyright (c) 2013 OPeNDAP, Inc.
// Author: James Gallagher <jgallagher@opendap.org>
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//
// You can contact OPeNDAP, Inc. at PO Box 112, Saunderstown, RI. 02874-0112.
//
// Portions of this code were taken verbatim from Josuttis,
// "The C++ Standard Library," p.672

#ifndef _chunked_istream_h
#define _chunked_istream_h

#include "chunked_stream.h"

#include <stdint.h>

#include <streambuf>
#include <istream>
#include <stdexcept>
#include <string>

namespace libdap {

class chunked_inbuf: public std::streambuf {
private:
	std::istream &d_is;

	uint32_t d_buf_size;	// Size of the data buffer
	char *d_buffer;			// data buffer

	// In the original implementation of this class, the byte order of the data stream
	// was passed in via constructors. When BYTE_ORDER_PREFIX is defined that is the
	// case. However, when it is not defined, the byte order is read from the chunk
	// header's high order byte (in bit position 2 - see chunked_stream.h). jhrg 11/24/13

	bool d_twiddle_bytes; 	// receiver-makes-right encoding (byte order)...
	bool d_set_twiddle;

	// If an error chunk is read, save the message here
	std::string d_error_message;
	bool d_error;

	/**
	 * @brief allocate the internal buffer.
	 * Allocate d_buf_size + putBack characters for the read buffer.
	 * @param size How much can the buffer hold? Does not include the putBack
	 * chars.
	 */
	void m_buffer_alloc() {
		delete d_buffer;
		d_buffer = new char[d_buf_size];
		setg(d_buffer, 	// beginning of put back area
			 d_buffer, 	// read position
		     d_buffer); // end position
	}

public:
	/**
	 * @brief Build a chunked input buffer.
	 *
	 * This reads from a chunked stream, extracting an entire chunk and storing it in a
	 * buffer in one operation. If the chunked_inbuf reads a chunk header that indicates
	 * the next chunk is going  be bigger than its current buffer size, the object will
	 * make the buffer larger. This object support 128 characters of 'put back' space. Since
	 * DAP4 uses receiver makes right, the buffer must be told if it should 'twiddle' the
	 * header size information. In DAP4 the byte order is sent using a one-byte code _before_
	 * the chunked transmission starts.
	 *
	 * @note In the current implementation, the byte order of the sender is read from the
	 * first chunk header. The method twiddle_bytes() returns false until the first chunk is
	 * read, then it returns the correct value. Only the first chunk_header is tested for the
	 * byte order flag; all subsequent chunks are assumed to use the same byte order.
	 *
	 * @param is Use this as a data source
	 * @param size The size of the input buffer. This should match the likely chunk size.
	 * If it is smaller than a chunk, it will be resized.
	 * @param twiddle_bytes Should the header bytes be twiddled? True if this host and the
	 * send use a different byte-order. The sender's byte order must be sent out-of-band.
	 */
#if BYTE_ORDER_PREFIX
	chunked_inbuf(std::istream &is, int size, bool twiddle_bytes = false)
        : d_is(is), d_buf_size(size), d_buffer(0), d_twiddle_bytes(twiddle_bytes), d_error(false) {
		if (d_buf_size & CHUNK_TYPE_MASK)
			throw std::out_of_range("A chunked_outbuf (or chunked_ostream) was built using a buffer larger than 0x00ffffff");

		m_buffer_alloc();
	}
#else
    chunked_inbuf(std::istream &is, int size)
        : d_is(is), d_buf_size(size), d_buffer(0), d_twiddle_bytes(false), d_set_twiddle(false), d_error(false) {
        if (d_buf_size & CHUNK_TYPE_MASK)
            throw std::out_of_range("A chunked_outbuf (or chunked_ostream) was built using a buffer larger than 0x00ffffff");

        m_buffer_alloc();
    }
#endif

	virtual ~chunked_inbuf() {
		delete d_buffer;
	}

	int_type read_next_chunk();

	int bytes_in_buffer() const { return (egptr() - gptr()); }

	// d_twiddle_bytes is false initially and is set to the correct value
	// once the first chunk is read.
	bool twiddle_bytes() const { return d_twiddle_bytes; }

	bool error() const { return d_error; }
	std::string error_message() const { return d_error_message; }

protected:
	virtual int_type underflow();

	virtual std::streamsize xsgetn(char* s, std::streamsize num);
};

class chunked_istream: public std::istream {
protected:
	chunked_inbuf d_cbuf;
public:
#if BYTE_ORDER_PREFIX
	chunked_istream(std::istream &is, int size, bool twiddle_bytes = false) : std::istream(&d_cbuf), d_cbuf(is, size, twiddle_bytes) { }
#else
    chunked_istream(std::istream &is, int size) : std::istream(&d_cbuf), d_cbuf(is, size) { }
#endif

	int read_next_chunk() { return d_cbuf.read_next_chunk(); }

	/**
	 * How many bytes have been read from the stream and are now in the internal buffer?
	 * @return Number of buffered bytes.
	 */
	int bytes_in_buffer() const { return d_cbuf.bytes_in_buffer(); }

	/**
	 * Should the receiver twiddle the bytes to match the local machine's byte order?
	 * Since DAP4 uses 'receiver makes right' encoding, the onus is on the client to
	 * reorder the bytes if it is, e.g., a big endian machine reading data from a little
	 * endian server.
	 *
	 * @return True if the client (caller) should swap bytes in multi-byte integers, etc.,
	 * and false if not. This does not directly tell the endian nature of the remote server,
	 * although that can be inferred.
	 */
	bool twiddle_bytes() const { return d_cbuf.twiddle_bytes(); }
	bool error() const { return d_cbuf.error(); }
	std::string error_message() const { return d_cbuf.error_message(); }
};

}

#endif	// _chunked_istream_h
