abstract contract PayingToken is IPayingToken, ERC20 {

    // optimize, see https://github.com/ethereum/EIPs/issues/1726#issuecomment-472352728
    uint256 constant internal pointsMultiplier = 2**128;
    uint256 internal pointsPerShare;
    uint256 internal activeTokens; 

    mapping(address => int256) internal pointsCorrection;
    mapping(address => uint256) internal withdrawnFunds;
    mapping(address => bool) internal activatedAddresses;


    constructor (
        string memory name, 
        string memory symbol
    ) 
        ERC20(name, symbol) 
    {}


    function activateAddress() public {
        address account = msg.sender;

        require(!isAddressActivated(account), "PayingToken.activateAddress: already activated");

        activatedAddresses[account] = true;
        uint256 balance = balanceOf(account);

        activeTokens = activeTokens + balance;

        pointsCorrection[account] = pointsCorrection[account] -  int256(pointsPerShare * balance);
    }

    function isAddressActivated(address account) public view returns(bool) {
        return activatedAddresses[account];
    }

    /** 
     * prev. distributeDividends
     * @notice Distributes funds to token holders.
     * @dev It reverts if the total supply of tokens is 0.
    **/
    function _distributeFunds(uint256 value) internal {
        pointsPerShare = pointsPerShare + (value * pointsMultiplier) / activeTokens;
    }

    /**
     * prev. withdrawDividend
     * @notice Prepares funds withdrawal
     * @dev It emits a `FundsWithdrawn` event if the amount of withdrawn ether is greater than 0.
     */
    function _prepareWithdraw() internal returns (uint256) {
        uint256 _withdrawableDividend = withdrawableFundsOf(msg.sender);
    
        withdrawnFunds[msg.sender] = withdrawnFunds[msg.sender] + _withdrawableDividend;
        
        emit FundsWithdrawn(msg.sender, _withdrawableDividend);

        return _withdrawableDividend;
    }

    /** 
     * prev. withdrawableDividendOf
     * @notice View the amount of funds that an address can withdraw.
     * @param _owner The address of a token holder.
     * @return The amount funds that `_owner` can withdraw.
     */
    function withdrawableFundsOf(address _owner) public view override returns(uint256) {
        return accumulativeFundsOf(_owner)- withdrawnFunds[_owner];
    }
    
    /**
     * prev. withdrawnDividendOf
     * @notice View the amount of funds that an address has withdrawn.
     * @param _owner The address of a token holder.
     * @return The amount of funds that `_owner` has withdrawn.
     */
    function withdrawnFundsOf(address _owner) public view returns(uint256) {
        return withdrawnFunds[_owner];
    }

    /**
     * prev. accumulativeDividendOf
     * @notice View the amount of funds that an address has earned in total.
     * @dev accumulativeFundsOf(_owner) = withdrawableFundsOf(_owner) + withdrawnFundsOf(_owner)
     * = (pointsPerShare * balanceOf(_owner) + pointsCorrection[_owner]) / pointsMultiplier
     * @param _owner The address of a token holder.
     * @return The amount of funds that `_owner` has earned in total.
     */
    function accumulativeFundsOf(address _owner) public view returns(uint256) {
        if (!isAddressActivated(_owner)) {
            return 0;
        }

        return uint256(int256(pointsPerShare * balanceOf(_owner)) + pointsCorrection[_owner]) / pointsMultiplier;
    }

    /**
     * @dev Internal function that transfer tokens from one address to another.
     * Update pointsCorrection to keep funds unchanged.
     * @param from The address to transfer from.
     * @param to The address to transfer to.
     * @param value The amount to be transferred.
     */
    function _transfer(address from, address to, uint256 value) internal override {
        super._transfer(from, to, value);

        if (!isAddressActivated(from) && !isAddressActivated(to)) {
            return;
        }

        int256 _magCorrection = int256(pointsPerShare * value);

        if (isAddressActivated(from) && !isAddressActivated(to)) {
            activeTokens = activeTokens - value;
            require(activeTokens > 0, "PayingToken: you cannot transfer all active tokens to an inactive address");
            pointsCorrection[from] = pointsCorrection[from] + _magCorrection;

            return;
        }

        if (!isAddressActivated(from) && isAddressActivated(to)) {
            activeTokens = activeTokens + value;
            pointsCorrection[to] = pointsCorrection[to] - _magCorrection;
            
            return;
        }

        pointsCorrection[from] = pointsCorrection[from] + _magCorrection;
        pointsCorrection[to] = pointsCorrection[to] - _magCorrection;
    }
}
/** 
 * @title PayingToken
 * Based on https://github.com/atpar/funds-distribution-token
 * @dev A  mintable token that can represent claims on cash flow of arbitrary assets such as dividends, loan repayments, 
 * fee or revenue shares among large numbers of token holders. Anyone can deposit funds, token holders can withdraw 
 * their claims.
 */
function _distributeFunds(uint256 value) internal {
    pointsPerShare = pointsPerShare + (value * pointsMultiplier) / activeTokens;
}

/** 
 * prev. distributeDividends
 * @notice Distributes funds to token holders.
 * @dev It reverts if the total supply of tokens is 0.
 **/