contract Reputation is DAOUpgradeableContract, AccessControlUpgradeable {
	bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");

	string public name;
	string public symbol;

	uint8 public decimals; //Number of decimals of the smallest unit
	// Event indicating minting of reputation to an address.
	event Mint(address indexed _to, uint256 _amount);
	// Event indicating burning of reputation for an address.
	event Burn(address indexed _from, uint256 _amount);
	uint256 private constant ZERO_HALF_256 = 0xffffffffffffffffffffffffffffffff;

	/// @dev `Checkpoint` is the structure that attaches a block number to a
	///  given value, the block number attached is the one that last changed the
	///  value
	//Checkpoint is uint256 :
	// bits 0-127 `fromBlock` is the block number that the value was generated from
	// bits 128-255 `value` is the amount of reputation at a specific block number

	// `balances` is the map that tracks the balance of each address, in this
	//  contract when the balance changes the block number that the change
	//  occurred is also included in the map
	mapping(address => uint256[]) public balances;

	// Tracks the history of the `totalSupply` of the reputation
	uint256[] public totalSupplyHistory;

	/**
	 * @dev initialize
	 */
	function initialize(INameService _ns) public initializer {
		__Reputation_init(_ns);
	}

	function __Reputation_init(INameService _ns) internal {
		decimals = 18;
		name = "GoodDAO";
		symbol = "GOOD";
		__Context_init_unchained();
		__ERC165_init_unchained();
		__AccessControl_init_unchained();

		setDAO(_ns);
		_setupRole(DEFAULT_ADMIN_ROLE, address(avatar));
		_setupRole(MINTER_ROLE, address(avatar));
	}

	function _canMint() internal view virtual {
		require(hasRole(MINTER_ROLE, _msgSender()), "Reputation: need minter role");
	}

	/// @notice Generates `_amount` reputation that are assigned to `_owner`
	/// @param _user The address that will be assigned the new reputation
	/// @param _amount The quantity of reputation generated
	/// @return True if the reputation are generated correctly
	function mint(address _user, uint256 _amount) public returns (bool) {
		_canMint();
		_mint(_user, _amount);
		return true;
	}

	function _mint(address _user, uint256 _amount)
		internal
		virtual
		returns (uint256)
	{
		uint256 curTotalSupply = totalSupplyLocalAt(block.number);
		uint256 previousBalanceTo = balanceOfLocalAt(_user, block.number);

		updateValueAtNow(totalSupplyHistory, curTotalSupply + _amount);
		updateValueAtNow(balances[_user], previousBalanceTo + _amount);
		emit Mint(_user, _amount);
		return _amount;
	}

	/// @notice Burns `_amount` reputation from `_owner`
	/// @param _user The address that will lose the reputation
	/// @param _amount The quantity of reputation to burn
	/// @return True if the reputation are burned correctly
	function burn(address _user, uint256 _amount) public returns (bool) {
		//user can burn his own rep other wise we check _canMint
		if (_user != _msgSender()) _canMint();
		_burn(_user, _amount);
		return true;
	}

	function _burn(address _user, uint256 _amount)
		internal
		virtual
		returns (uint256)
	{
		uint256 curTotalSupply = totalSupplyLocalAt(block.number);
		uint256 amountBurned = _amount;
		uint256 previousBalanceFrom = balanceOfLocalAt(_user, block.number);
		if (previousBalanceFrom < amountBurned) {
			amountBurned = previousBalanceFrom;
		}
		updateValueAtNow(totalSupplyHistory, curTotalSupply - amountBurned);
		updateValueAtNow(balances[_user], previousBalanceFrom - amountBurned);
		emit Burn(_user, amountBurned);
		return amountBurned;
	}

	function balanceOfLocal(address _owner) public view returns (uint256) {
		return balanceOfLocalAt(_owner, block.number);
	}

	/// @dev Queries the balance of `_owner` at a specific `_blockNumber`
	/// @param _owner The address from which the balance will be retrieved
	/// @param _blockNumber The block number when the balance is queried
	/// @return The balance at `_blockNumber`
	function balanceOfLocalAt(address _owner, uint256 _blockNumber)
		public
		view
		virtual
		returns (uint256)
	{
		if (
			(balances[_owner].length == 0) ||
			(uint128(balances[_owner][0]) > _blockNumber)
		) {
			return 0;
			// This will return the expected balance during normal situations
		} else {
			return getValueAt(balances[_owner], _blockNumber);
		}
	}

	function totalSupplyLocal() public view virtual returns (uint256) {
		return totalSupplyLocalAt(block.number);
	}

	/// @notice Total amount of reputation at a specific `_blockNumber`.
	/// @param _blockNumber The block number when the totalSupply is queried
	/// @return The total amount of reputation at `_blockNumber`
	function totalSupplyLocalAt(uint256 _blockNumber)
		public
		view
		virtual
		returns (uint256)
	{
		if (
			(totalSupplyHistory.length == 0) ||
			(uint128(totalSupplyHistory[0]) > _blockNumber)
		) {
			return 0;
			// This will return the expected totalSupply during normal situations
		} else {
			return getValueAt(totalSupplyHistory, _blockNumber);
		}
	}

	////////////////
	// Internal helper functions to query and set a value in a snapshot array
	////////////////
	/// @dev `getValueAt` retrieves the number of reputation at a given block number
	/// @param checkpoints The history of values being queried
	/// @param _block The block number to retrieve the value at
	/// @return The number of reputation being queried
	function getValueAt(uint256[] storage checkpoints, uint256 _block)
		internal
		view
		returns (uint256)
	{
		uint256 len = checkpoints.length;
		if (len == 0) {
			return 0;
		}
		// Shortcut for the actual value
		uint256 cur = checkpoints[len - 1];
		if (_block >= uint128(cur)) {
			return cur >> 128;
		}

		if (_block < uint128(checkpoints[0])) {
			return 0;
		}

		// Binary search of the value in the array
		uint256 min = 0;
		uint256 max = len - 1;
		while (max > min) {
			uint256 mid = (max + min + 1) / 2;
			if (uint128(checkpoints[mid]) <= _block) {
				min = mid;
			} else {
				max = mid - 1;
			}
		}
		return checkpoints[min] >> 128;
	}

	/// @dev `updateValueAtNow` used to update the `balances` map and the
	///  `totalSupplyHistory`
	/// @param checkpoints The history of data being updated
	/// @param _value The new number of reputation
	function updateValueAtNow(uint256[] storage checkpoints, uint256 _value)
		internal
	{
		require(uint128(_value) == _value, "reputation overflow"); //check value is in the 128 bits bounderies
		if (
			(checkpoints.length == 0) ||
			(uint128(checkpoints[checkpoints.length - 1]) < block.number)
		) {
			checkpoints.push(uint256(uint128(block.number)) | (_value << 128));
		} else {
			checkpoints[checkpoints.length - 1] = uint256(
				(checkpoints[checkpoints.length - 1] & uint256(ZERO_HALF_256)) |
					(_value << 128)
			);
		}
	}
}
/**
 * based on https://github.com/daostack/infra/blob/60a79a1be02942174e21156c3c9655a7f0695dbd/contracts/Reputation.sol
 * @title Reputation system
 * @dev A DAO has Reputation System which allows peers to rate other peers in order to build trust .
 * A reputation is used to assign influence measure to a DAO'S peers.
 * Reputation is similar to regular tokens but with one crucial difference: It is non-transferable.
 * The Reputation contract maintain a map of address to reputation value.
 * It provides an only minter role functions to mint and burn reputation _to (or _from) a specific address.
 */
function initialize(INameService _ns) public initializer {
	__Reputation_init(_ns);
}

/**
 	 * @dev initialize
 	 */