library Pool {
	using FixedPointMath for FixedPointMath.uq192x64;
	using Pool for Pool.Data;
	using Pool for Pool.List;
	using SafeMath for uint256;

	struct Context {
		uint256 rewardRate;
		uint256 totalRewardWeight;
	}

	struct Data {
		IERC20 token;
		uint256 totalDeposited;
		uint256 rewardWeight;
		FixedPointMath.uq192x64 accumulatedRewardWeight;
		uint256 lastUpdatedBlock;
	}

	struct List {
		Data[] elements;
	}

	/// @dev Updates the pool.
	///
	/// @param _ctx the pool context.
	function update(Data storage _data, Context storage _ctx) internal {
		_data.accumulatedRewardWeight = _data.getUpdatedAccumulatedRewardWeight(_ctx);
		_data.lastUpdatedBlock = block.number;
	}

	/// @dev Gets the rate at which the pool will distribute rewards to stakers.
	///
	/// @param _ctx the pool context.
	///
	/// @return the reward rate of the pool in tokens per block.
	function getRewardRate(Data storage _data, Context storage _ctx) internal view returns (uint256) {
		// console.log("get reward rate");
		// console.log(uint(_data.rewardWeight));
		// console.log(uint(_ctx.totalRewardWeight));
		// console.log(uint(_ctx.rewardRate));
		return _ctx.rewardRate.mul(_data.rewardWeight).div(_ctx.totalRewardWeight);
	}

	/// @dev Gets the accumulated reward weight of a pool.
	///
	/// @param _ctx the pool context.
	///
	/// @return the accumulated reward weight.
	function getUpdatedAccumulatedRewardWeight(Data storage _data, Context storage _ctx)
		internal
		view
		returns (FixedPointMath.uq192x64 memory)
	{
		if (_data.totalDeposited == 0) {
			return _data.accumulatedRewardWeight;
		}

		uint256 _elapsedTime = block.number.sub(_data.lastUpdatedBlock);
		if (_elapsedTime == 0) {
			return _data.accumulatedRewardWeight;
		}

		uint256 _rewardRate = _data.getRewardRate(_ctx);
		uint256 _distributeAmount = _rewardRate.mul(_elapsedTime);

		if (_distributeAmount == 0) {
			return _data.accumulatedRewardWeight;
		}

		FixedPointMath.uq192x64 memory _rewardWeight =
			FixedPointMath.fromU256(_distributeAmount).div(_data.totalDeposited);
		return _data.accumulatedRewardWeight.add(_rewardWeight);
	}

	/// @dev Adds an element to the list.
	///
	/// @param _element the element to add.
	function push(List storage _self, Data memory _element) internal {
		_self.elements.push(_element);
	}

	/// @dev Gets an element from the list.
	///
	/// @param _index the index in the list.
	///
	/// @return the element at the specified index.
	function get(List storage _self, uint256 _index) internal view returns (Data storage) {
		return _self.elements[_index];
	}

	/// @dev Gets the last element in the list.
	///
	/// This function will revert if there are no elements in the list.
	///ck
	/// @return the last element in the list.
	function last(List storage _self) internal view returns (Data storage) {
		return _self.elements[_self.lastIndex()];
	}

	/// @dev Gets the index of the last element in the list.
	///
	/// This function will revert if there are no elements in the list.
	///
	/// @return the index of the last element.
	function lastIndex(List storage _self) internal view returns (uint256) {
		uint256 _length = _self.length();
		return _length.sub(1, "Pool.List: list is empty");
	}

	/// @dev Gets the number of elements in the list.
	///
	/// @return the number of elements.
	function length(List storage _self) internal view returns (uint256) {
		return _self.elements.length;
	}
}
/// @title Pool
///
/// @dev A library which provides the Pool data struct and associated functions.
function length(List storage _self) internal view returns (uint256) {
	return _self.elements.length;
}

/// @dev Gets the number of elements in the list.
///
/// @return the number of elements.