abstract contract GetterUtils is StateUtils, IGetterUtils {
    /// @notice Called to get the voting power of a user at a specific block
    /// @param userAddress User address
    /// @param _block Block number for which the query is being made for
    /// @return Voting power of the user at the block
    function userVotingPowerAt(
        address userAddress,
        uint256 _block
        )
        public
        view
        override
        returns (uint256)
    {
        // Users that have a delegate have no voting power
        if (userDelegateAt(userAddress, _block) != address(0))
        {
            return 0;
        }
        return userSharesAt(userAddress, _block)
            + delegatedToUserAt(userAddress, _block);
    }

    /// @notice Called to get the current voting power of a user
    /// @param userAddress User address
    /// @return Current voting power of the user
    function userVotingPower(address userAddress)
        external
        view
        override
        returns (uint256)
    {
        return userVotingPowerAt(userAddress, block.number);
    }

    /// @notice Called to get the total pool shares at a specific block
    /// @dev Total pool shares also corresponds to total voting power
    /// @param _block Block number for which the query is being made for
    /// @return Total pool shares at the block
    function totalSharesAt(uint256 _block)
        public
        view
        override
        returns (uint256)
    {
        return getValueAt(poolShares, _block);
    }

    /// @notice Called to get the current total pool shares
    /// @dev Total pool shares also corresponds to total voting power
    /// @return Current total pool shares
    function totalShares()
        public
        view
        override
        returns (uint256)
    {
        return totalSharesAt(block.number);
    }

    /// @notice Called to get the pool shares of a user at a specific block
    /// @param userAddress User address
    /// @param _block Block number for which the query is being made for
    /// @return Pool shares of the user at the block
    function userSharesAt(
        address userAddress,
        uint256 _block
        )
        public
        view
        override
        returns (uint256)
    {
        return getValueAt(users[userAddress].shares, _block);
    }

    /// @notice Called to get the current pool shares of a user
    /// @param userAddress User address
    /// @return Current pool shares of the user
    function userShares(address userAddress)
        public
        view
        override
        returns (uint256)
    {
        return userSharesAt(userAddress, block.number);
    }

    /// @notice Called to get the current staked tokens of the user
    /// @param userAddress User address
    /// @return Current staked tokens of the user
    function userStake(address userAddress)
        public
        view
        override
        returns (uint256)
    {
        return userShares(userAddress) * totalStake / totalShares();
    }

    /// @notice Called to get the voting power delegated to a user at a
    /// specific block
    /// @param userAddress User address
    /// @param _block Block number for which the query is being made for
    /// @return Voting power delegated to the user at the block
    function delegatedToUserAt(
        address userAddress,
        uint256 _block
        )
        public
        view
        override
        returns (uint256)
    {
        return getValueAt(users[userAddress].delegatedTo, _block);
    }

    /// @notice Called to get the current voting power delegated to a user
    /// @param userAddress User address
    /// @return Current voting power delegated to the user
    function delegatedToUser(address userAddress)
        public
        view
        override
        returns (uint256)
    {
        return delegatedToUserAt(userAddress, block.number);
    }

    /// @notice Called to get the delegate of the user at a specific block
    /// @param userAddress User address
    /// @param _block Block number
    /// @return Delegate of the user at the specific block
    function userDelegateAt(
        address userAddress,
        uint256 _block
        )
        public
        view
        override
        returns (address)
    {
        return getAddressAt(users[userAddress].delegates, _block);
    }

    /// @notice Called to get the current delegate of the user
    /// @param userAddress User address
    /// @return Current delegate of the user
    function userDelegate(address userAddress)
        public
        view
        override
        returns (address)
    {
        return userDelegateAt(userAddress, block.number);
    }

    /// @notice Called to get the current locked tokens of the user
    /// @param userAddress User address
    /// @return locked Current locked tokens of the user
    function userLocked(address userAddress)
        public
        view
        override
        returns (uint256 locked)
    {
        Checkpoint[] storage _userShares = users[userAddress].shares;
        uint256 currentEpoch = block.timestamp / EPOCH_LENGTH;
        uint256 oldestLockedEpoch = getOldestLockedEpoch();
        uint256 indUserShares = _userShares.length;
        for (
                uint256 indEpoch = currentEpoch;
                indEpoch >= oldestLockedEpoch;
                indEpoch--
            )
        {
            // The user has never staked at this point, we can exit early
            if (indUserShares == 0)
            {
                break;
            }
            Reward storage lockedReward = epochIndexToReward[indEpoch];
            if (lockedReward.atBlock != 0)
            {
                for (; indUserShares > 0; indUserShares--)
                {
                    Checkpoint storage userShare = _userShares[indUserShares - 1];
                    if (userShare.fromBlock <= lockedReward.atBlock)
                    {
                        locked += lockedReward.amount * userShare.value / lockedReward.totalSharesThen;
                        break;
                    }
                }
            }
        }
    }

    /// @notice Called to get the details of a user
    /// @param userAddress User address
    /// @return unstaked Amount of unstaked API3 tokens
    /// @return vesting Amount of API3 tokens locked by vesting
    /// @return unstakeAmount Amount scheduled to unstake
    /// @return unstakeShares Shares revoked to unstake
    /// @return unstakeScheduledFor Time unstaking is scheduled for
    /// @return lastDelegationUpdateTimestamp Time of last delegation update
    /// @return lastProposalTimestamp Time when the user made their most
    /// recent proposal
    function getUser(address userAddress)
        external
        view
        override
        returns (
            uint256 unstaked,
            uint256 vesting,
            uint256 unstakeAmount,
            uint256 unstakeShares,
            uint256 unstakeScheduledFor,
            uint256 lastDelegationUpdateTimestamp,
            uint256 lastProposalTimestamp
            )
    {
        User storage user = users[userAddress];
        unstaked = user.unstaked;
        vesting = user.vesting;
        unstakeAmount = user.unstakeAmount;
        unstakeShares = user.unstakeShares;
        unstakeScheduledFor = user.unstakeScheduledFor;
        lastDelegationUpdateTimestamp = user.lastDelegationUpdateTimestamp;
        lastProposalTimestamp = user.lastProposalTimestamp;
    }

    /// @notice Called to get the value of a checkpoint array at a specific
    /// block using binary search
    /// @dev Adapted from
    /// https://github.com/aragon/minime/blob/1d5251fc88eee5024ff318d95bc9f4c5de130430/contracts/MiniMeToken.sol#L431
    /// @param checkpoints Checkpoints array
    /// @param _block Block number for which the query is being made
    /// @return Value of the checkpoint array at the block
    function getValueAt(
        Checkpoint[] storage checkpoints,
        uint256 _block
        )
        internal
        view
        returns (uint256)
    {
        if (checkpoints.length == 0)
            return 0;

        // Shortcut for the actual value
        if (_block >= checkpoints[checkpoints.length -1].fromBlock)
            return checkpoints[checkpoints.length - 1].value;
        if (_block < checkpoints[0].fromBlock)
            return 0;

        // Limit the search to the last 1024 elements if the value being
        // searched falls within that window
        uint min = 0;
        if (
            checkpoints.length > 1024
                && checkpoints[checkpoints.length - 1024].fromBlock < _block
            )
        {
            min = checkpoints.length - 1024;
        }

        // Binary search of the value in the array
        uint max = checkpoints.length - 1;
        while (max > min) {
            uint mid = (max + min + 1) / 2;
            if (checkpoints[mid].fromBlock <= _block) {
                min = mid;
            } else {
                max = mid - 1;
            }
        }
        return checkpoints[min].value;
    }

    /// @notice Called to get the value of an address-checkpoint array at a
    /// specific block using binary search
    /// @dev Adapted from
    /// https://github.com/aragon/minime/blob/1d5251fc88eee5024ff318d95bc9f4c5de130430/contracts/MiniMeToken.sol#L431
    /// @param checkpoints Address-checkpoint array
    /// @param _block Block number for which the query is being made
    /// @return Value of the address-checkpoint array at the block
    function getAddressAt(
        AddressCheckpoint[] storage checkpoints,
        uint256 _block
        )
        private
        view
        returns (address)
    {
        if (checkpoints.length == 0)
            return address(0);

        // Shortcut for the actual value
        if (_block >= checkpoints[checkpoints.length -1].fromBlock)
            return checkpoints[checkpoints.length - 1]._address;
        if (_block < checkpoints[0].fromBlock)
            return address(0);

        // Limit the search to the last 1024 elements if the value being
        // searched falls within that window
        uint min = 0;
        if (
            checkpoints.length > 1024
                && checkpoints[checkpoints.length - 1024].fromBlock < _block
            )
        {
            min = checkpoints.length - 1024;
        }

        // Binary search of the value in the array
        uint max = checkpoints.length - 1;
        while (max > min) {
            uint mid = (max + min + 1) / 2;
            if (checkpoints[mid].fromBlock <= _block) {
                min = mid;
            } else {
                max = mid - 1;
            }
        }
        return checkpoints[min]._address;
    }

    /// @notice Called internally to get the index of the oldest epoch whose
    /// reward should be locked in the current epoch
    /// @return oldestLockedEpoch Index of the oldest epoch with locked rewards
    function getOldestLockedEpoch()
        internal
        view
        returns (uint256 oldestLockedEpoch)
    {
        uint256 currentEpoch = block.timestamp / EPOCH_LENGTH;
        oldestLockedEpoch = currentEpoch - REWARD_VESTING_PERIOD + 1;
        if (oldestLockedEpoch < genesisEpoch + 1)
        {
            oldestLockedEpoch = genesisEpoch + 1;
        }
    }
}
/// @title Contract that implements getters
function getUser(address userAddress)
    external
    view
    override
    returns (
        uint256 unstaked,
        uint256 vesting,
        uint256 unstakeAmount,
        uint256 unstakeShares,
        uint256 unstakeScheduledFor,
        uint256 lastDelegationUpdateTimestamp,
        uint256 lastProposalTimestamp
        )
{
    User storage user = users[userAddress];
    unstaked = user.unstaked;
    vesting = user.vesting;
    unstakeAmount = user.unstakeAmount;
    unstakeShares = user.unstakeShares;
    unstakeScheduledFor = user.unstakeScheduledFor;
    lastDelegationUpdateTimestamp = user.lastDelegationUpdateTimestamp;
    lastProposalTimestamp = user.lastProposalTimestamp;
}

/// @notice Called to get the details of a user
/// @param userAddress User address
/// @return unstaked Amount of unstaked API3 tokens
/// @return vesting Amount of API3 tokens locked by vesting
/// @return unstakeAmount Amount scheduled to unstake
/// @return unstakeShares Shares revoked to unstake
/// @return unstakeScheduledFor Time unstaking is scheduled for
/// @return lastDelegationUpdateTimestamp Time of last delegation update
/// @return lastProposalTimestamp Time when the user made their most
/// recent proposal