#pragma once
#include "../acl_cpp_define.hpp"
#include <stdlib.h>
#include "stream.hpp"

namespace acl {

class string;

/**
 * 输入流操作类，如果想确切知道输入流是否关闭或出错或读到了文件流的
 * 尾部，应通过调用 stream->eof() 来进行判断
 */

class ACL_CPP_API istream : virtual public stream {
public:
	istream(void) {}
	virtual ~istream(void) {}

	/**
	 * 从输入流中读数据
	 * @param buf {void*} 用户缓冲区
	 * @param size {size_t} 用户缓冲区长度
	 * @param loop {bool} 是否读满 size 后才返回
	 * @return {int} 读操作结果, -1 表示关闭或出错, > 0 表示成功
	 */
	int read(void* buf, size_t size, bool loop = true);

	/**
	 * 从输入流读数据直至读到所要求的字符串或出错才返回
	 * @param buf {void*} 用户缓冲区
	 * @param inout {size_t*} 作为参数时 *inout 表示缓冲 buf
	 *  的空间大小，函数返回后记录存储于 buf 中的数据长度
	 * @param tag {const char*} 要求读到的字符串
	 * @param len {size_t} tag 字符串的长度
	 * @return {bool} 是否读到所要求的字符串数据
	 */

	bool readtags(void *buf, size_t* inout, const char *tag, size_t len);

	/**
	 * 从输入流中读到一行数据
	 * @param buf {void*} 用户缓冲区
	 * @param size_inout {size_t*} 作为参数时 *size_inout 表示缓冲 buf
	 *  的空间大小，函数返回后记录存储于 buf 中的数据长度
	 * @param nonl {bool} 如果为 true 则会将读到的一行数据尾部的 "\r\n"
	 *  或 "\n" 去掉，*size_inout 存储的数据长度是去掉 "\r\n" 或 "\n" 后
	 *  的长度；否则，保留数据行中的 "\r\n" 或 "\n"，同时 *size_inout 存
	 *  储的是包含 "\r\n" 或 "\n" 的数据长度
	 * @return {bool} 是否读到了一行数据, 出错则返回 false; 对文件输入流而
	 *  言，如果读到的数据是最后一部分数据且这部分数据不含 "\r\n" 或 "\n"
	 *  则也会返回 false, 调用者需要检查 *size_inout 值是否大于 0
	 *  来确定是否读到了最后一部分数据
	 */
	bool gets(void* buf, size_t* size_inout, bool nonl = true);

	/**
	 * 从输入流中读一个 64 位整数
	 * @param n {acl_int64&} 64 位整数
	 * @param loop {bool} 是否阻塞式读完8个字节
	 * @return {bool} 是否读取成功
	 */
#if defined(_WIN32) || defined(_WIN64)
	bool read(__int64& n, bool loop = true);
#else
	bool read(long long int& n, bool loop = true);
#endif

	/**
	 * 从输入流中读一个 32 位整数
	 * @param n {int&} 32 位整数
	 * @param loop {bool} 是否阻塞式读完4个字节
	 * @return {bool} 是否读取成功
	 */
	bool read(int& n, bool loop = true);

	/**
	 * 从输入流中读一个 16 位整数
	 * @param n {short&} 16 位整数
	 * @param loop {bool} 是否阻塞式读完2个字节
	 * @return {bool} 是否读取成功
	 */
	bool read(short& n, bool loop = true);

	/**
	 * 从输入流中读取一个字节
	 * @param ch {char&}
	 * @return {bool} 读取是否成功
	 */
	bool read(char& ch);

	/**
	 * 从输入流中读数据至缓冲区中
	 * @param s {string*} 缓冲区，内部会首先自动清空该缓冲区
	 * @param loop {bool} 是否阻塞式读满整个缓冲，缓冲区
	 *  的容量为 s.capacity()
	 * @return {bool} 读数据是否成功
	 */
	bool read(string& s, bool loop = true);
	bool read(string* s, bool loop = true);

	/**
	 * 从输入流中读数据至缓冲区中
	 * @param s {string*} 缓冲区，内部会首先自动清空该缓冲区
	 * @param max {size_t} 希望读到的数据的最大值
	 * @param loop {bool} 是否读到要求的 max 字节数为止
	 * @return {bool} 读数据是否成功
	 */
	bool read(string& s, size_t max, bool loop = true);
	bool read(string* s, size_t max, bool loop = true);

	/**
	 * 从输入流中读一行数据至缓冲区中
	 * @param s {string&} 缓冲区，内部会首先自动清空该缓冲区
	 * @param nonl {bool} 是否保留所读行数据中的 "\r\n" 或 "\n"
	 * @param max {size_t} 当该值 > 0 时，该值限定了所读到行的最大值，当
	 *  接收到数据行长度大于该值时，则仅返回部分数据，同时内部会记录警告；
	 *  当该值 = 0 时，则不限制行数据长度
	 * @return {bool} 是否读到了一行数据
	 *  1）如果返回 true 则说明读到了完整一行数据；如果该行数据中只有
	 *     "\r\n" 或 "\n"，则 s 的内容为空，即：s.empty() == true
	 *  2）如果返回 false 则说明读关闭且未读到一行数据，此时 s 中有可能
	 *     存储着部分数据，需要用 if (s.empty() == true) 判断一下
	 */
	bool gets(string& s, bool nonl = true, size_t max = 0);
	bool gets(string* s, bool nonl = true, size_t max = 0);

	/**
	 * 从输入流中读数据直到读到要求的字符串数据作为分隔符的数据，
	 * 读取的数据的最后部分应该是该字符串
	 * @param s {string&} 缓冲区，内部会首先自动清空该缓冲区
	 * @param tag {const string&} 要求读的字符串数据
	 * @return {bool} 是否读到要求的字符串数据
	 */
	bool readtags(string& s, const string& tag);
	bool readtags(string* s, const string& tag);

	/**
	 * 从输入流中读一个字节数据
	 * @return {int} 所读字节的 ASCII 码值，如果返回值为 -1 则表示对方关闭或
	 *  读出错
	 */
	int getch(void);

	/**
	 * 向输入流中放加一个字节的数据
	 * @param ch {int} 一个字符的 ASCII 码值
	 * @return {int} 如果返回值与 ch 值相同则表示正确，否则表示出错
	 */
	int ugetch(int ch);

	/**
	 * 检测当前流是否可读(有数据, 连接关闭或出错均表示可读),该方法可用于和
	 * 下面的 xxx_peek() 结合.
	 * @return {bool}
	 */
	bool readable(void) const;

	/**
	 * 尝试性从输入流中读取一行数据
	 * @param buf {string&} 缓冲区
	 * @param nonl {bool} 是否保留所读行数据中的 "\r\n" 或 "\n"
	 * @param clear {bool} 是否内部自动清空 buf 缓冲区
	 * @param max {int} 当该值 > 0 时则设置所读行数据的最大长度以避免本地
	 *  缓冲区溢出
	 * @return {bool} 是否读了一行数据; 如果返回 false 并不表示输入
	 *  流结束，只是表示未读到一个完整行数据，应该通过调用 stream->eof()
	 *  来检查输入流是否关闭，另外，如果仅读到了部分数据，则 buf 会存储
	 *  这些部分数据
	 *  注意：为了防止 buf 缓冲区溢出，调用者调用该方法获得的数据即使不够
	 *  一行数据，应尽量取出 buf 中的数据然后将 buf->clear()，以防止 buf
	 *  内存过大导致缓冲区溢出
	 */
	bool gets_peek(string& buf, bool nonl = true,
		bool clear = false, int max = 0);
	bool gets_peek(string* buf, bool nonl = true,
		bool clear = false, int max = 0);

	/**
	 * 尝试性从输入流中读取数据
	 * @param buf {string&} 缓冲区
	 * @param clear {bool} 函数开始时是否内部自动清空 buf 缓冲区
	 * @return {bool} 是否读到数据, 如果返回 false 仅 表示未读完所要求
	 *  的数据长度，应该通过调用 stream->eof() 来检查输入流是否关闭
	 *  注意：为了防止 buf 缓冲区溢出，调用者调用该方法获得的数据即使不够
	 *  一行数据，应尽量取出 buf 中的数据然后将 buf->clear()，以防止 buf
	 *  内存过大导致缓冲区溢出
	 */
	bool read_peek(string& buf, bool clear = false);
	bool read_peek(string* buf, bool clear = false);

	/**
	 * 尝试性从输入流中读取数据
	 * @param buf {void*} 缓冲区
	 * @param size {size_t} buf 缓冲区大小
	 * @return {int} 返回 -1 表示读出错或关闭，> 0 表示读到的数据长度，
	 *  如果返回 0 表示本次没有读到数据，可以继续读，当返回值 < 0 时，
	 *  可通过 eof() 判断流是否应该关闭
	 */
	int read_peek(void* buf, size_t size);

	/**
	 * 尝试性从输入流中读取指定长度的数据
	 * @param buf {string&} 缓冲区
	 * @param cnt {size_t} 要求读到的数据长度
	 * @param clear {bool} 函数开始时是否内部自动清空 buf 缓冲区
	 * @return {bool} 是否读到所要求数据长度的数据, 如果返回 false 仅
	 *  表示未读完所要求的数据长度，应该通过调用 stream->eof() 来检查
	 *  输入流是否关闭
	 *  注意：为了防止 buf 缓冲区溢出，调用者调用该方法获得的数据即使不够
	 *  一行数据，应尽量取出 buf 中的数据然后将 buf->clear()，以防止 buf
	 *  内存过大导致缓冲区溢出
	 */
	bool readn_peek(string& buf, size_t cnt, bool clear = false);
	bool readn_peek(string* buf, size_t cnt, bool clear = false);

	/* 以下几个函数重载了输入操作符，它们都是阻塞式操作过程，且需要
	 * 调用 stream->eof() 来判断输入流是否关闭或是否读到了文件尾
	 */

	istream& operator>>(string& s);
#if defined(_WIN32) || defined(_WIN64)
	istream& operator>>(__int64& n);
#else
	istream& operator>>(long long int& n);
#endif
	istream& operator>>(int& n);
	istream& operator>>(short& n);
	istream& operator>>(char& ch);
};

} // namespace acl
