#pragma once

#include "common/platform.h"

#include <cstdint>
#include <stdexcept>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>

#include "common/exception.h"
#include "common/io_limits_config_loader.h"
#include "common/serialization_macros.h"
#include "common/token_bucket.h"

/**
 * Simple class to make serialization easier
 */
LIZARDFS_DEFINE_SERIALIZABLE_CLASS(IoGroupAndLimit,
		std::string, group,
		uint64_t   , limit);

/**
 * This class is responsible for limiting resources for every group. It gives a user a possibility
 * to define how does he want to limit each group and provides an interface for acquiring these
 * resources. It does that by having a separate TokenBucket rate limiter for every GroupID,
 * initializing it with proper values and passing requests for a given GroupID to an appropriate
 * TokenBucket.
 */
class IoLimitsDatabase {
public:
	typedef std::string GroupId;

	// An exception that is thrown if a user requestes an assignment for a group
	// that cannot be served
	LIZARDFS_CREATE_EXCEPTION_CLASS_MSG(InvalidGroupIdException, Exception,
			"invalid group id");

	// default constructor, all limits are zero
	IoLimitsDatabase() {}

	// set limits (as generated by IoLimitsConfigLoader)
	//
	// all limits in bytes per second
	void setLimits(SteadyTimePoint now, const IoLimitsConfigLoader::LimitsMap& limits,
			uint32_t accumulate_ms);

	// get a list of all groups
	std::vector<std::string> getGroups() const;

	// get a vector of all groups with their limits in bytes per second
	std::vector<IoGroupAndLimit> getGroupsAndLimits() const;

	// try to satisfy client's request to change limit in given I/O group, return assigned limit
	uint64_t request(SteadyTimePoint now, const GroupId& groupId, uint64_t bytes);

private:
	typedef std::map<GroupId, TokenBucket> Groups;
	Groups groups_;
};
