// SPDX-License-Identifier: agpl-3.0
pragma solidity ^0.8.0;

interface IADXToken {
	function totalSupply() external returns (uint);
	function mint(address owner, uint amount) external;
	function changeSupplyController(address newSupplyController) external;
}

contract ADXSupplyController {
	enum GovernanceLevel { None, Mint, All }

	uint public immutable CAP = 150000000 * 1e18;
	IADXToken public immutable ADX = IADXToken(0xADE00C28244d5CE17D72E40330B1c318cD12B7c3);

	mapping (address => uint8) public governance;
	// Some addresses (eg StakingPools) are incentivized with a certain allowance of ADX per year
	mapping (address => uint) public incentivePerSecond;
	// Keep track of when incentive tokens were last minted for a given addr
	mapping (address => uint) public incentiveLastMint;

	constructor() {
		governance[msg.sender] = uint8(GovernanceLevel.All);
	}

	function changeSupplyController(address newSupplyController) external {
		require(governance[msg.sender] >= uint8(GovernanceLevel.All), 'NOT_GOVERNANCE');
		ADX.changeSupplyController(newSupplyController);
	}

	function setGovernance(address addr, uint8 level) external {
		require(governance[msg.sender] >= uint8(GovernanceLevel.All), 'NOT_GOVERNANCE');
		governance[addr] = level;
	}

	function setIncentive(address addr, uint amountPerSecond) external {
		require(governance[msg.sender] >= uint8(GovernanceLevel.All), 'NOT_GOVERNANCE');
		// no more than 1 ADX per second
		require(amountPerSecond < 1e18, 'AMOUNT_TOO_LARGE');
		incentiveLastMint[addr] = block.timestamp;
		incentivePerSecond[addr] = amountPerSecond;
	}

	function innerMint(IADXToken token, address owner, uint amount) internal {
		uint totalSupplyAfter = token.totalSupply() + amount;
		require(totalSupplyAfter <= CAP, 'MINT_TOO_LARGE');
		token.mint(owner, amount);
	}

	// Kept because it's used for ADXLoyaltyPool
	function mint(IADXToken token, address owner, uint amount) external {
		require(governance[msg.sender] >= uint8(GovernanceLevel.Mint), 'NOT_GOVERNANCE');
		innerMint(token, owner, amount);
	}

	// Incentive mechanism
	function mintableIncentive(address addr) public view returns (uint) {
		return (block.timestamp - incentiveLastMint[addr]) * incentivePerSecond[addr];
	}

	function mintIncentive(address addr) external {
		uint amount = mintableIncentive(addr);
		incentiveLastMint[addr] = block.timestamp;
		innerMint(ADX, addr, amount);
	}
}