contract WETHPoolDelegator is PoolStorage {
    uint256 private _totalSupply;
    mapping(address => uint256) private _balances;
    event NewImplementation(address oldImplementation, address newImplementation);
    
    constructor(
        string memory _poolname,
        address _rewardtoken,
        address _basetoken,
        uint256 _starttime,
        uint256 _period,
        uint256 _initreward,
        address implementation_,
        bytes memory becomeImplementationData
    )
        public
    {

        gov = msg.sender;

        delegateTo(
            implementation_,
            abi.encodeWithSignature(
                "initialize(string,address,address,uint256,uint256,uint256)",
                _poolname,
                _rewardtoken,
                _basetoken,
                _starttime,
                _period,
                _initreward
            )
        );

        // New implementations always get set via the settor (post-initialize)
        _setImplementation(implementation_, false, becomeImplementationData);

    }

    /**
     * @notice Called by the gov to update the implementation of the delegator
     * @param implementation_ The address of the new implementation for delegation
     * @param allowResign Flag to indicate whether to call _resignImplementation on the old implementation
     * @param becomeImplementationData The encoded bytes data to be passed to _becomeImplementation
     */
    function _setImplementation(address implementation_, bool allowResign, bytes memory becomeImplementationData) public {
        require(msg.sender == gov, "RewardPoolDelegator::_setImplementation: Caller must be gov");

        if (allowResign) {
            require(msg.sender == gov, "RewardPoolDelegator::_setImplementation: Caller must be gov");
            delegateToImplementation(abi.encodeWithSignature("_resignImplementation()"));
        }

        address oldImplementation = implementation;
        implementation = implementation_;

        delegateToImplementation(abi.encodeWithSignature("_becomeImplementation(bytes)", becomeImplementationData));
        emit NewImplementation(oldImplementation, implementation);
    }

    function totalSupply()
        external
        view
        returns (uint256)
    {
        delegateToViewAndReturn();
    }

    function balanceOf(address account)
        external
        view
        returns (uint256)
    {
        account;
        delegateToViewAndReturn();
    }

    function earned(address account)
        external
        view
        returns (uint256)
    {
        account;
        delegateToViewAndReturn();
    }

    function stake(uint256 amount)
        external
        returns (bool)
    {
        amount;
        delegateAndReturn();
    }

    function withdraw(uint256 amount)
        external
        returns (bool)
    {
        amount;
        delegateAndReturn();
    }

    function exit()
        external
        returns (bool)
    {
        delegateAndReturn();
    }

    function getReward()
        external
        returns (bool)
    {
        delegateAndReturn();
    }

    function _setPendingGov(address newPendingGov)
        external
    {
        newPendingGov;
        delegateAndReturn();
    }

    function _acceptGov()
        external
    {
        delegateAndReturn();
    }

    /**
     * @notice Internal method to delegate execution to another contract
     * @dev It returns to the external caller whatever the implementation returns or forwards reverts
     * @param callee The contract to delegatecall
     * @param data The raw data to delegatecall
     * @return The returned bytes from the delegatecall
     */
    function delegateTo(address callee, bytes memory data) internal returns (bytes memory) {
        (bool success, bytes memory returnData) = callee.delegatecall(data);
        assembly {
            if eq(success, 0) {
                revert(add(returnData, 0x20), returndatasize)
            }
        }
        return returnData;
    }

    /**
     * @notice Delegates execution to the implementation contract
     * @dev It returns to the external caller whatever the implementation returns or forwards reverts
     * @param data The raw data to delegatecall
     * @return The returned bytes from the delegatecall
     */
    function delegateToImplementation(bytes memory data) public returns (bytes memory) {
        return delegateTo(implementation, data);
    }

    /**
     * @notice Delegates execution to an implementation contract
     * @dev It returns to the external caller whatever the implementation returns or forwards reverts
     *  There are an additional 2 prefix uints from the wrapper returndata, which we ignore since we make an extra hop.
     * @param data The raw data to delegatecall
     * @return The returned bytes from the delegatecall
     */
    function delegateToViewImplementation(bytes memory data) public view returns (bytes memory) {
        (bool success, bytes memory returnData) = address(this).staticcall(abi.encodeWithSignature("delegateToImplementation(bytes)", data));
        assembly {
            if eq(success, 0) {
                revert(add(returnData, 0x20), returndatasize)
            }
        }
        return abi.decode(returnData, (bytes));
    }

    function delegateToViewAndReturn() private view returns (bytes memory) {
        (bool success, ) = address(this).staticcall(abi.encodeWithSignature("delegateToImplementation(bytes)", msg.data));

        assembly {
            let free_mem_ptr := mload(0x40)
            returndatacopy(free_mem_ptr, 0, returndatasize)

            switch success
            case 0 { revert(free_mem_ptr, returndatasize) }
            default { return(add(free_mem_ptr, 0x40), returndatasize) }
        }
    }

    function delegateAndReturn() private returns (bytes memory) {
        (bool success, ) = implementation.delegatecall(msg.data);

        assembly {
            let free_mem_ptr := mload(0x40)
            returndatacopy(free_mem_ptr, 0, returndatasize)

            switch success
            case 0 { revert(free_mem_ptr, returndatasize) }
            default { return(free_mem_ptr, returndatasize) }
        }
    }

    /**
     * @notice Delegates execution to an implementation contract
     * @dev It returns to the external caller whatever the implementation returns or forwards reverts
     */
    function () external payable {
        require(msg.value == 0,"RewardPoolDelegator:fallback: cannot send value to fallback");

        // delegate all other functions to current implementation
        delegateAndReturn();
    }
}
function delegateToImplementation(bytes memory data) public returns (bytes memory) {
    return delegateTo(implementation, data);
}

/**
 * @notice Delegates execution to the implementation contract
 * @dev It returns to the external caller whatever the implementation returns or forwards reverts
 * @param data The raw data to delegatecall
 * @return The returned bytes from the delegatecall
 */