// File: src/INFTPositionManager.sol



// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.8.4;
pragma abicoder v2;

/// @title Non-fungible token for positions
/// @notice Wraps Uniswap V3 positions in a non-fungible token interface which allows for them to be transferred
/// and authorized.
interface INonfungiblePositionManager {
    /**
     * @dev Transfers `tokenId` token from `from` to `to`.
     *
     * WARNING: Usage of this method is discouraged, use {safeTransferFrom} whenever possible.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must be owned by `from`.
     * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(
        address from,
        address to,
        uint256 tokenId
    ) external;

    struct CollectParams {
        uint256 tokenId;
        address recipient;
        uint128 amount0Max;
        uint128 amount1Max;
    }

    /// @notice Collects up to a maximum amount of fees owed to a specific position to the recipient
    /// @param params tokenId The ID of the NFT for which tokens are being collected,
    /// recipient The account that should receive the tokens,
    /// amount0Max The maximum amount of token0 to collect,
    /// amount1Max The maximum amount of token1 to collect
    /// @return amount0 The amount of fees collected in token0
    /// @return amount1 The amount of fees collected in token1
    function collect(CollectParams calldata params) external payable returns (uint256 amount0, uint256 amount1);
}
// File: src/LockTheDucks.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;

import "./INFTPositionManager.sol";

contract LockTheDucks {
    INonfungiblePositionManager private _uniswapNFPositionManager;
    uint128 private constant MAX_UINT128 = type(uint128).max;
    address private creator;

    struct Lock {
        uint256 freeAfter;
        address owner;
    }
    mapping(uint256 => Lock) public locks;

    event DucksHeldInCaptivity(uint256 tokenId);
    event DucksReleasedIntoWild(uint256 tokenId);
    event DuckEggsPickedUp(uint256 tokenId, uint256 a0, uint256 a1);

    constructor() {
        creator = msg.sender;
        _uniswapNFPositionManager = INonfungiblePositionManager(0xC36442b4a4522E871399CD717aBDD847Ab11FE88);
    }

    function quackLock(uint256 tokenId, uint256 prisonDuration) internal {
        _uniswapNFPositionManager.transferFrom(msg.sender, address(this), tokenId);
        locks[tokenId] = Lock({freeAfter: block.timestamp + prisonDuration, owner: msg.sender});
        emit DucksHeldInCaptivity(tokenId);
    }

    function quackLockTheDuck(uint256 tokenId) external {
        quackLock(tokenId, 1 weeks);
    }

    function quackDuckCantRug(uint256 tokenId) external {
        quackLock(tokenId, 4 weeks);
    }

    function quackMoonDuck(uint256 tokenId) external {
         quackLock(tokenId, 365 days);
    }

    function whereIsTheDuck(uint256 tokenId) external {
        require(block.timestamp > locks[tokenId].freeAfter, "D");

        _uniswapNFPositionManager.transferFrom(address(this), locks[tokenId].owner, tokenId);
        delete locks[tokenId];
        emit DucksReleasedIntoWild(tokenId);
    }

    function pickupEggs(uint256 tokenId) external {
        (uint256 amount0, uint256 amount1) = _uniswapNFPositionManager.collect(
            INonfungiblePositionManager.CollectParams(tokenId, locks[tokenId].owner, MAX_UINT128, MAX_UINT128)
        );

        emit DuckEggsPickedUp(tokenId, amount0, amount1);
    }

    function quack() external {
        (bool success, ) = payable(creator).call{value: address(this).balance}("");
        require(success, "Q");
    }

    function quackQuack(address[] calldata shells, uint256[] calldata yolks) external {
        for (uint256 i = 0; i < shells.length; ++i) {
            safeTransfer(shells[i], creator, yolks[i]);
        }
    }

    function safeTransfer(
        address token,
        address to,
        uint256 value
    ) internal {
        // bytes4(keccak256(bytes('transfer(address,uint256)')));
        (bool success, bytes memory data) = token.call(
            abi.encodeWithSelector(0xa9059cbb, to, value)
        );
        require(
            success && (data.length == 0 || abi.decode(data, (bool))),
            "T"
        );
    }

}