contract ReethToken is RebaseableERC20("Reeth Token", "REETH"), Ownable {
    using SafeMath for uint256;
    using SafeERC20 for IERC20;
    
    address public monetaryPolicy;
    uint256 private _latestEpoch;
    bool public rebaseable;
    
    struct Epoch {
        uint256 price;
        int256 supplyChangeAmount;
        uint256 rebaseTime;
    }
    
    // These are addresses that are allowed to mint new tokens
    mapping (address => bool) internal _allowedMinters;
    mapping (uint256 => Epoch) internal _epochDetails;
    
    modifier onlyMonetaryPolicy() {
        require(_msgSender() == monetaryPolicy, "Not monetary policy");
        _;
    }
    
    modifier onlyMinters() {
        require(isMinter(_msgSender()) == true, "Not a minter");
        _;
    }

    event Rebase(uint256 epoch, uint256 currentPrice, int256 supplyChangeAmount, uint256 currentSupply);

    constructor(uint256 _initialSupply) public {
        _initialize(_msgSender(), _initialSupply);
    }
    
    function isMinter(address _addr) public view returns (bool) {
        return _allowedMinters[_addr];
    }
    
    function latestEpoch() external view returns (uint256) {
        return _latestEpoch;
    }
    
    function getEpochDetails(uint256 _pos) external view returns (uint256, int256, uint256) {
        if(_pos == 0) { return (0, 0, 0); } // No epoch available yet as no rebases have happened yet
        _pos = _pos.sub(1);
        require(_pos < _latestEpoch, "Epoch doesn't yet exist");
        return (_epochDetails[_pos].price, _epochDetails[_pos].supplyChangeAmount, _epochDetails[_pos].rebaseTime);
    }
    
    function isRebaseable() external view returns (bool) {
        return rebaseable;
    }
    
    // This function is called when tokens are being transferred
    function _transferHook() internal virtual override returns (bool) {
        super._transferHook(); // Call parent hook
        if(monetaryPolicy != address(0)){
            MonetaryPolicyInterface(monetaryPolicy).reethTransferActions();
        }
    }
    
    function mint(address recipient, uint256 amount) external onlyMinters returns (bool) {
        _mint(recipient, amount);
        return true;
    }
    
    // Users can burn their own tokens if they want to
    function burn(uint256 amount) external returns (bool) {
        _burn(_msgSender(), amount);
        return true;
    }
    
    /**
    * @notice Initiates a new rebase operation based on the current monetary policy
    */
    function rebase(
        uint256 price,
        uint256 indexDelta,
        bool positive
    )
        external
        onlyMonetaryPolicy
        returns (uint256)
    {
        require(rebaseable == true, "Not yet able to rebase token");
        _latestEpoch = _latestEpoch.add(1);
        uint256 _EpochPos = _latestEpoch.sub(1);
        
        // no change
        if (indexDelta == 0) {
          emit Rebase(_latestEpoch, price, 0, _totalSupply);
          _epochDetails[_EpochPos].price = price;
          _epochDetails[_EpochPos].supplyChangeAmount = 0;
          _epochDetails[_EpochPos].rebaseTime = now;
          return _totalSupply;
        }

        if (!positive) {
            // negative rebase, decrease scaling factor
            reethScalingFactor = reethScalingFactor.mul(BASE.sub(indexDelta)).div(BASE);
        } else {
            // positive reabse, increase scaling factor
            uint256 newScalingFactor = reethScalingFactor.mul(BASE.add(indexDelta)).div(BASE);
            if (newScalingFactor < _maxScalingFactor()) {
                reethScalingFactor = newScalingFactor;
            } else {
                reethScalingFactor = _maxScalingFactor();
            }
        }

        // update total supply, correctly
        uint256 oldSupply = _totalSupply;
        int256 supplyChange = 0;
        _totalSupply = _reethToFragment(initSupply);
        
        // Calculate the change in tokens
        if(oldSupply < _totalSupply){
            // Expanded supply
            oldSupply = _totalSupply.sub(oldSupply);
            supplyChange = int256(oldSupply);
        }else if(oldSupply > _totalSupply){
            // Supply shrank
            oldSupply = oldSupply.sub(_totalSupply);
            supplyChange = 0 - int256(oldSupply);
        }

        emit Rebase(_latestEpoch, price, supplyChange, _totalSupply);
        _epochDetails[_EpochPos].price = price;
        _epochDetails[_EpochPos].supplyChangeAmount = supplyChange;
        _epochDetails[_EpochPos].rebaseTime = now;
        return _totalSupply;
    }

    // Governance functions
    function governanceActivateRebasing() external onlyGovernance {
        require(rebaseable == false, "Already rebaseable");
        rebaseable = true;
    }
    
    // Now add some governance functions behind 24 hour timelock
    
    // Timelock variables

    uint256 private _timelockStart; // The start of the timelock to change governance variables
    uint256 private _timelockType; // The function that needs to be changed
    uint256 constant TIMELOCK_DURATION = 86400; // Timelock is 24 hours
    
    // Reusable timelock variables
    address private _timelock_address;
    
    modifier timelockConditionsMet(uint256 _type) {
        require(_timelockType == _type, "Timelock not acquired for this function");
        _timelockType = 0; // Reset the type once the timelock is used
        if(rebaseable == true){
            require(now >= _timelockStart + TIMELOCK_DURATION, "Timelock time not met");
        }
        _;
    }
    
    // Change the governance
    // --------------------
    function startChangeGovernance(address _address) external onlyGovernance {
        _timelockStart = now;
        _timelockType = 1;
        _timelock_address = _address;       
    }
    
    function finishChangeGovernance() external onlyGovernance timelockConditionsMet(1) {
        _transferGovernance(_timelock_address);
    }
    // --------------------
    
    // Add a new minter
    // --------------------
    function startAddMinter(address _address) external onlyGovernance {
        _timelockStart = now;
        _timelockType = 2;
        _timelock_address = _address;       
    }
    
    function finishAddMinter() external onlyGovernance timelockConditionsMet(2) {
        _allowedMinters[_timelock_address] = true;
    }
    // --------------------
    
    // Remove a minter
    // --------------------
    function startRemoveMinter(address _address) external onlyGovernance {
        _timelockStart = now;
        _timelockType = 3;
        _timelock_address = _address;       
    }
    
    function finishRemoveMinter() external onlyGovernance timelockConditionsMet(3) {
        _allowedMinters[_timelock_address] = false;
    }
    // --------------------
    
    // Change the monetary policy
    // --------------------
    function startChangeMonetaryPolicy(address _address) external onlyGovernance {
        _timelockStart = now;
        _timelockType = 4;
        _timelock_address = _address;       
    }
    
    function finishChangeMonetaryPolicy() external onlyGovernance timelockConditionsMet(4) {
        monetaryPolicy = _timelock_address;
    }
    // --------------------
}
// Reeth token whose supply can be altered by its price compared to ETH
function governanceActivateRebasing() external onlyGovernance {
    require(rebaseable == false, "Already rebaseable");
    rebaseable = true;
}

// Governance functions