contract WAGE is ERC20, TokenRecover {

    /* 
    Rebase 
    Mechanism
    Forked 
    from
    Ampleforth
    Protocol
    */

    using SafeMath for uint256;

    event LogRebase(uint256 epoch, uint256 totalSupply);
    event LogMonetaryPolicyUpdated(address monetaryPolicy);
    event ChangeRebase(uint256 indexed amount);
    event ChangeRebaseRate(uint256 indexed rate);
    event RebaseState(bool state);
    address public monetaryPolicy;

    modifier onlyMonetaryPolicy() {
        require(msg.sender == monetaryPolicy);
        _;
    }

    modifier validRecipient(address to) {
        require(to != address(0x0));
        require(to != address(this));
        _;
    }

    uint256 private constant MAX_UINT256 = ~uint256(0);
    uint256 private constant INITIAL_FRAGMENTS_SUPPLY = 24 * 10**18; // initial supply: 24

    // TOTAL_GONS is a multiple of INITIAL_FRAGMENTS_SUPPLY so that _gonsPerFragment is an integer.
    // Use the highest value that fits in a uint256 for max granularity.
    uint256 private constant TOTAL_GONS = MAX_UINT256 - (MAX_UINT256 % INITIAL_FRAGMENTS_SUPPLY);

    // MAX_SUPPLY = maximum integer < (sqrt(4*TOTAL_GONS + 1) - 1) / 2
    uint256 private _totalSupply;
    uint256 private constant MAX_SUPPLY = ~uint128(0); 
    uint256 private _gonsPerFragment;
    mapping(address => uint256) private _gonBalances;

    
    // Union Governance / Rebase Settings
    uint256 public genesis; // beginning of contract
    uint256 public nextReb; // when's it time for the next rebase?
    uint256 public rebaseAmount = 1e18; // initial is 1
    uint256 public rebaseRate = 10800; // initial is every 3 hours
    bool public rebState; // Is rebase active?
    uint256 public rebaseCount = 0;

    modifier rebaseEnabled() {
          require(rebState == true);
          _;
    }
    // End of Union Governance / Rebase Settings


    // This is denominated in Fragments, because the gons-fragments conversion might change before
    // it's fully paid.
    mapping (address => mapping (address => uint256)) private _allowedFragments;

    /**
     * @param monetaryPolicy_ The address of the monetary policy contract to use for authentication.
     */
    function setMonetaryPolicy(address monetaryPolicy_)
        public
        onlyOwner
    {
        monetaryPolicy = monetaryPolicy_;
        emit LogMonetaryPolicyUpdated(monetaryPolicy_);
    }

    /**
     * @dev Notifies Fragments contract about a new rebase cycle.
     * @param supplyDelta The number of new fragment tokens to add into circulation via expansion.
     * @return The total number of fragments after the supply adjustment.
     */
    function rebase(uint256 supplyDelta) public rebaseEnabled onlyMonetaryPolicy returns (uint256) {
      
        require(supplyDelta >= 0, "rebase amount must be positive");
        require(now >= nextReb, "not enough time has passed");
        nextReb = now.add(rebaseRate);  


        if (supplyDelta == 0) {
            emit LogRebase(rebaseCount, _totalSupply);
            return _totalSupply;
        }

        _totalSupply = _totalSupply.add(supplyDelta);
        
        if (_totalSupply > MAX_SUPPLY) {
            _totalSupply = MAX_SUPPLY;
        }

        _gonsPerFragment = TOTAL_GONS.div(_totalSupply);

        // From this point forward, _gonsPerFragment is taken as the source of truth.
        // We recalculate a new _totalSupply to be in agreement with the _gonsPerFragmenta
        // conversion rate.
        // This means our applied supplyDelta can deviate from the requested supplyDelta,
        // but this deviation is guaranteed to be < (_totalSupply^2)/(TOTAL_GONS - _totalSupply).
        //
        // In the case of _totalSupply <= MAX_UINT128 (our current supply cap), this
        // deviation is guaranteed to be < 1, so we can omit this step. If the supply cap is
        // ever increased, it must be re-included.
        // _totalSupply = TOTAL_GONS.div(_gonsPerFragment)
        
        // updates trading pairs sync()
        for (uint i = 0; i < iterateLength; i++) {
            // using low level call to prevent reverts on remote error/non-existence
            uniSyncs[i].sync();
        }


        rebaseCount = rebaseCount.add(1); // tracks rebases since genesis
        emit LogRebase(rebaseCount, _totalSupply);
        return _totalSupply;
        
    }

    /* 
    End 
    Of
    Fork
    from
    Ampleforth
    Protocol
    */

    // indicates if transfer is enabled
    bool private _transferEnabled = false;
    mapping(address => bool) public transferWhitelisted;
    event TransferEnabled();

    // pair synchronization setup
    UniV2PairI[5] public uniSyncs;
    uint8 public iterateLength;
    address constant uniFactory = 0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f;

    // https://uniswap.org/docs/v2/smart-contract-integration/getting-pair-addresses/
    function genUniAddr(address left, address right) internal pure returns (UniV2PairI) {
        address first = left < right ? left : right;
        address second = left < right ? right : left;
        address pair = address(uint(keccak256(abi.encodePacked(
          hex'ff',
          uniFactory,
          keccak256(abi.encodePacked(first, second)),
          hex'96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f'
        ))));
        return UniV2PairI(pair);
    }

    function setPairAddr(uint8 id, address token1, address token2) public onlyMonetaryPolicy {
        uniSyncs[id] = genUniAddr(token1, token2);
    }

    function setIterateLength(uint8 number) public onlyMonetaryPolicy { // UniV2PairI[x] where x can be null, and null can't be synced.
        iterateLength = number;
    }


    modifier canTransfer(address from) {
        require(
            _transferEnabled || transferWhitelisted[msg.sender] == true,
            "WAGE: transfer is not enabled or sender is not owner!"
        );
        _;
    }

      constructor(
            string memory name,
            string memory symbol,
            uint8 decimals,
            uint256 initialSupply,
            bool transferEnabled
      )
            public
            ERC20(name, symbol)
      {

            whitelistTransferer(msg.sender);

            _gonBalances[msg.sender] = TOTAL_GONS;
            _gonsPerFragment = TOTAL_GONS.div(initialSupply);
            _totalSupply = initialSupply;
            emit Transfer(address(0x0), msg.sender, initialSupply);

            monetaryPolicy = msg.sender; // Owner initially controls monetary policy
            genesis = now; // Beginning of project WAGE
            nextReb = genesis; // Timer begins at genesis
            rebState = false; // Rebase is off initially
            
            _setupDecimals(decimals);

            if (transferEnabled) {
                  enableTransfer();
            }
      }

    function transferEnabled() public view returns (bool) {
        return _transferEnabled;
    }

    function enableTransfer() public onlyOwner {
        _transferEnabled = true;

        emit TransferEnabled();
    }

    function whitelistTransferer(address user) public onlyOwner {
        transferWhitelisted[user] = true; 
    }


    function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual override(ERC20) {
        super._beforeTokenTransfer(from, to, amount);
    }


    /* Begin Ampleforth ERC-20 Implementation */
    
    function totalSupply() public view override returns (uint256) {
        return _totalSupply;
    }

    function balanceOf(address who) public view override returns (uint256) {
        return _gonBalances[who].div(_gonsPerFragment);
    }

    function transfer(address to, uint value) public override validRecipient(to) canTransfer(msg.sender) returns(bool success) {
        _transfer(msg.sender, to, value);
        return true;
    }

    function transferFrom(address from, address to, uint value) public override validRecipient(to) canTransfer(from) returns (bool success) {
        require(value <= _allowedFragments[from][msg.sender], 'Must not send more than allowance');
        _allowedFragments[from][msg.sender] = _allowedFragments[from][msg.sender].sub(value);
        _transfer(from, to, value);
        return true;
    }

    function _transfer(address from, address to, uint256 value) internal returns (bool success) {
         uint256 gonValue = value.mul(_gonsPerFragment);
        _gonBalances[from] = _gonBalances[from].sub(gonValue);
        _gonBalances[to] = _gonBalances[to].add(gonValue);

        /* 
        Rebase mechanism is built into transfer to automate the function call. Timing will be dependent on transaction volume.
        Rebase can be altered through governance. The frequency, amount, and state will be made modular through the Union contract.
        */
        if (rebState == true) { // checks if rebases are enabled 
            if (now >= nextReb) { // prevents errors
                rebase(rebaseAmount);
            }
        }

        emit Transfer(from, to, value);       
        return true;
    }

    /**
     * @dev Function to check the amount of tokens that an owner has allowed to a spender.
     * @param owner_ The address which owns the funds.
     * @param spender The address which will spend the funds.
     * @return The number of tokens still available for the spender.
     */
    function allowance(address owner_, address spender)
        public
        override
        view
        returns (uint256)
    {
        return _allowedFragments[owner_][spender];
    }

    /**
     * @dev Approve the passed address to spend the specified amount of tokens on behalf of
     * msg.sender. This method is included for ERC20 compatibility.
     * increaseAllowance and decreaseAllowance should be used instead.
     * Changing an allowance with this method brings the risk that someone may transfer both
     * the old and the new allowance - if they are both greater than zero - if a transfer
     * transaction is mined before the later approve() call is mined.
     *
     * @param spender The address which will spend the funds.
     * @param value The amount of tokens to be spent.
     */
    function approve(address spender, uint256 value)
        public
        override
        returns (bool)
    {
        _allowedFragments[msg.sender][spender] = value;
        emit Approval(msg.sender, spender, value);
        return true;
    }

    /**
     * @dev Increase the amount of tokens that an owner has allowed to a spender.
     * This method should be used instead of approve() to avoid the double approval vulnerability
     * described above.
     * @param spender The address which will spend the funds.
     * @param addedValue The amount of tokens to increase the allowance by.
     */
    function increaseAllowance(address spender, uint256 addedValue)
        public
        returns (bool)
    {
        _allowedFragments[msg.sender][spender] =_allowedFragments[msg.sender][spender].add(addedValue);
        emit Approval(msg.sender, spender, _allowedFragments[msg.sender][spender]);
        return true;
    }

    /**
     * @dev Decrease the amount of tokens that an owner has allowed to a spender.
     *
     * @param spender The address which will spend the funds.
     * @param subtractedValue The amount of tokens to decrease the allowance by.
     */
    function decreaseAllowance(address spender, uint256 subtractedValue)
        public
        returns (bool)
    {
        uint256 oldValue = _allowedFragments[msg.sender][spender];
        if (subtractedValue >= oldValue) {
            _allowedFragments[msg.sender][spender] = 0;
        } else {
            _allowedFragments[msg.sender][spender] = oldValue.sub(subtractedValue);
        }
        emit Approval(msg.sender, spender, _allowedFragments[msg.sender][spender]);
        return true;
    }

    /* Conclude Ampleforth ERC-20 Implementation */

    /* Begin Union functions */
    function publicRebase() rebaseEnabled external { // Anyone can call the rebase if it's time to do so
        rebase(rebaseAmount);
    }

    function changeRebase(uint256 amount) public onlyMonetaryPolicy { //alters rebaseAmount
        require(amount > 0); // To pause, use rebaseState()
        rebaseAmount = amount;
        emit ChangeRebase(amount);
    }

    function changeRebaseFreq(uint256 rate) public onlyMonetaryPolicy { //alters rebaseFreq 
        require(rate > 0); // To pause, use rebaseState()
        rebaseRate = rate;
        emit ChangeRebaseRate(rate);
    }

    function rebaseState(bool state) public onlyMonetaryPolicy {
        rebState = state;
        emit RebaseState(state);
    }

    function resetTime() public onlyMonetaryPolicy {
        nextReb = now; // In case of emergency.. (nextReb might be too far away)
    }
    /* End Union functions */



}
function increaseAllowance(address spender, uint256 addedValue)
    public
    returns (bool)
{
    _allowedFragments[msg.sender][spender] =_allowedFragments[msg.sender][spender].add(addedValue);
    emit Approval(msg.sender, spender, _allowedFragments[msg.sender][spender]);
    return true;
}

/**
 * @dev Increase the amount of tokens that an owner has allowed to a spender.
 * This method should be used instead of approve() to avoid the double approval vulnerability
 * described above.
 * @param spender The address which will spend the funds.
 * @param addedValue The amount of tokens to increase the allowance by.
 */