// File: ApymonPack721.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "./IERC721Receiver.sol";
import "./ERC165.sol";
import './Events721.sol';
import './Ownable.sol';

interface IERC721 {
    function ownerOf(uint256 tokenId) external view returns (address owner);
    function exists(uint256 tokenId) external view returns (bool);
    function approve(address to, uint256 tokenId) external;
    function safeTransferFrom(address from, address to, uint256 tokenId) external;
}

interface IAPYMONPACK {
    function isLocked(uint256 eggId) external view returns (bool locked, uint256 endTime);
    function isOpened(uint256 eggId) external view returns (bool);
}

interface ISwap {
    function swapErc721(
        uint256 eggId,
        address inToken,
        uint256 inId,
        address outToken,
        uint8 router,
        address to
    ) external;
}

contract ApymonPack721 is ERC165, IERC721Receiver, Context, Events721, Ownable {

    // Mapping from egg ID -> tokens
    mapping(uint256 => address[]) private _insideTokens;

    // Mapping from egg ID -> token(erc721) -> ids
    mapping(uint256 => mapping(address => uint256[])) private _insideTokenIds;

    IERC721 public _apymon;
    IAPYMONPACK public _apymonPack;

    ISwap public _swap;

    modifier onlyEggOwner(uint256 eggId) {
        require(_apymon.exists(eggId));
        require(_apymon.ownerOf(eggId) == msg.sender);
        _;
    }

    modifier unlocked(uint256 eggId) {
        (bool locked, ) = _apymonPack.isLocked(eggId);
        require(!locked, "Egg has been locked.");
        _;
    }

    modifier opened(uint256 eggId) {
        require(_apymonPack.isOpened(eggId), "Egg has been closed");
        _;
    }

    constructor() {
        _apymon = IERC721(0x9C008A22D71B6182029b694B0311486e4C0e53DB);
        _apymonPack = IAPYMONPACK(0x3dFCB488F6e96654e827Ab2aB10a463B9927d4f9);
    }

    // View functions

    /**
     * @dev check if egg token id exists in egg.
     */
    function existsId(
        uint256 eggId,
        address token,
        uint256 id
    ) public view returns (bool) {
        uint256[] memory ids = _insideTokenIds[eggId][token];

        for (uint256 i; i < ids.length; i++) {
            if (ids[i] == id) {
                return true;
            }
        }

        return false;
    }

    /**
     * @dev check if tokenId exists in egg
     */
    function getInsideTokensCount(
        uint256 eggId
    ) public view opened(eggId) returns (
        uint256 erc721Len
    ) {
        return _insideTokens[eggId].length;
    }

    /**
     * @dev get ERC721 token info
     */
    function getERC721Tokens(
        uint256 eggId
    ) public view opened(eggId) returns (
        address[] memory addresses,
        uint256[] memory tokenBalances
    ) {
        address[] memory tokens = _insideTokens[eggId];
        uint256 erc721Len = tokens.length;
        
        tokenBalances = new uint256[](erc721Len);
        addresses = new address[](erc721Len);
        uint256 j;

        for (uint256 i; i < tokens.length; i++) {
            addresses[j] = tokens[i];
            tokenBalances[j] = _insideTokenIds[eggId][tokens[i]].length;
            j++;
        }
    }

    /**
     * @dev get ERC721 ids
     */
    function getTokenIds(
        uint256 eggId,
        address insideToken
    ) public view opened(eggId) returns (uint256[] memory) {
        return _insideTokenIds[eggId][insideToken];
    }

    // Write functions

    function setSwap(address swap) external onlyOwner {
        _swap = ISwap(swap);
    }

    function setApymonPack(address _pack) external onlyOwner {
        _apymonPack = IAPYMONPACK(_pack);
    }

    /**
     * @dev deposit erc721 tokens into egg.
     */
    function depositErc721IntoEgg(
        uint256 eggId,
        address token,
        uint256[] memory tokenIds
    ) external {
        require(token != address(0));

        for (uint256 i; i < tokenIds.length; i++) {
            require(
                token != address(_apymon) ||
                (token == address(_apymon) && eggId != tokenIds[i])
            );
            IERC721 iToken = IERC721(token);
            
            iToken.safeTransferFrom(
                msg.sender,
                address(this),
                tokenIds[i]
            );

            _putInsideTokenId(
                eggId,
                token,
                tokenIds[i]
            );

            if (_apymonPack.isOpened(eggId)) {
                emit DepositedErc721IntoEgg(
                    eggId,
                    msg.sender,
                    token,
                    tokenIds[i]
                );
            }
        }

        _putTokenIntoEgg(
            eggId,
            token
        );
    }

    /**
     * @dev withdraw erc721 token from egg.
     */
    function withdrawErc721FromEgg(
        uint256 eggId,
        address token,
        uint256[] memory tokenIds,
        address to
    ) public onlyEggOwner(eggId) unlocked(eggId) opened(eggId) {
        require(token != address(0));
        IERC721 iToken = IERC721(token);

        for (uint256 i; i < tokenIds.length; i++) {
            address tokenOwner = iToken.ownerOf(tokenIds[i]);

            require(tokenOwner == address(this));

            iToken.safeTransferFrom(
                tokenOwner,
                to,
                tokenIds[i]
            );

            _popInsideTokenId(
                eggId,
                token,
                tokenIds[i]
            );

            emit WithdrewErc721FromEgg(
                eggId,
                msg.sender,
                token,
                tokenIds[i],
                to
            );
        }

        uint256[] memory ids = _insideTokenIds[eggId][token];

        if (ids.length == 0) {
            _popTokenFromEgg(
                eggId,
                token
            );
        }
    }

    /**
     * @dev send erc721 tokens from my egg to another egg.
     */
    function sendErc721(
        uint256 fromEggId,
        address token,
        uint256[] memory tokenIds,
        uint256 toEggId
    ) public onlyEggOwner(fromEggId) unlocked(fromEggId) opened(fromEggId) {
        require(fromEggId != toEggId);
        require(token != address(0));
        require(_apymon.exists(toEggId));

        for (uint256 i; i < tokenIds.length; i++) {
            _popInsideTokenId(
                fromEggId,
                token,
                tokenIds[i]
            );

            _putInsideTokenId(
                toEggId,
                token,
                tokenIds[i]
            );

            emit SentErc721(
                fromEggId,
                msg.sender,
                token,
                tokenIds[i],
                toEggId
            );
        }

        uint256[] memory ids = _insideTokenIds[fromEggId][token];

        if (ids.length == 0) {
            _popTokenFromEgg(
                fromEggId,
                token
            );
        }

        _putTokenIntoEgg(
            toEggId,
            token
        );
    }

    function swapErc721(
        uint256 eggId,
        address inToken,
        uint256 inId,
        address outToken,
        uint8 router,
        address to
    ) external onlyEggOwner(eggId) unlocked(eggId) opened(eggId) {
        require(address(_swap) != address(0));
        require(existsId(eggId, inToken, inId));
        
        IERC721(inToken).approve(address(_swap), inId);

        _swap.swapErc721(
            eggId,
            inToken,
            inId,
            outToken,
            router,
            to
        );
        emit SwapedErc721(
            msg.sender,
            eggId,
            inToken,
            inId,
            outToken,
            to
        );

        _popInsideTokenId(
            eggId,
            inToken,
            inId
        );
    }

    /**
     * @dev private function to put a token id to egg
     */
    function _putInsideTokenId(
        uint256 eggId,
        address token,
        uint256 tokenId
    ) private {
        uint256[] storage ids = _insideTokenIds[eggId][token];
        ids.push(tokenId);
    }

    /**
     * @dev private function to pop a token id from egg
     */
    function _popInsideTokenId(
        uint256 eggId,
        address token,
        uint256 tokenId
    ) private {
        uint256[] storage ids = _insideTokenIds[eggId][token];
        for (uint256 i; i < ids.length; i++) {
            if (ids[i] == tokenId) {
                ids[i] = ids[ids.length - 1];
                ids.pop();
            }
        }

        if (ids.length == 0) {
            delete _insideTokenIds[eggId][token];
        }
    }

    /**
     * @dev put token(type, address) to egg
     */
    function _putTokenIntoEgg(
        uint256 eggId,
        address tokenAddress
    ) private {
        address[] storage tokens = _insideTokens[eggId];
        bool exists = false;
        for (uint256 i; i < tokens.length; i++) {
            if (tokens[i] == tokenAddress) {
                exists = true;
                break;
            }
        }

        if (!exists) {
            tokens.push(tokenAddress);
        }
    }

    /**
     * @dev pop token(type, address) from egg
     */
    function _popTokenFromEgg(
        uint256 eggId,
        address tokenAddress
    ) private {
        address[] storage tokens = _insideTokens[eggId];
        for (uint256 i; i < tokens.length; i++) {
            if (tokens[i] == tokenAddress) {
                tokens[i] = tokens[tokens.length - 1];
                tokens.pop();
                break;
            }
        }

        if (tokens.length == 0) {
            delete _insideTokens[eggId];
        }
    }
   
    function onERC721Received(
        address,
        address,
        uint256,
        bytes calldata
    ) external pure override returns (bytes4) {
        return this.onERC721Received.selector;
    }
}

// File: Context.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

/*
 * @dev Provides information about the current execution context, including the
 * sender of the transaction and its data. While these are generally available
 * via msg.sender and msg.data, they should not be accessed in such a direct
 * manner, since when dealing with meta-transactions the account sending and
 * paying for execution may not be the actual sender (as far as an application
 * is concerned).
 *
 * This contract is only required for intermediate, library-like contracts.
 */
abstract contract Context {
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes calldata) {
        this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
        return msg.data;
    }
}

// File: ERC165.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "./IERC165.sol";

/**
 * @dev Implementation of the {IERC165} interface.
 *
 * Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check
 * for the additional interface id that will be supported. For example:
 *
 * ```solidity
 * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
 *     return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
 * }
 * ```
 *
 * Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation.
 */
abstract contract ERC165 is IERC165 {
    /*
     * bytes4(keccak256('supportsInterface(bytes4)')) == 0x01ffc9a7
     */
    bytes4 private constant _INTERFACE_ID_ERC165 = 0x01ffc9a7;

    /**
     * @dev Mapping of interface ids to whether or not it's supported.
     */
    mapping(bytes4 => bool) private _supportedInterfaces;

    constructor () {
        // Derived contracts need only register support for their own interfaces,
        // we register support for ERC165 itself here
        _registerInterface(_INTERFACE_ID_ERC165);
    }

    /**
     * @dev See {IERC165-supportsInterface}.
     *
     * Time complexity O(1), guaranteed to always use less than 30 000 gas.
     */
    function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
        return _supportedInterfaces[interfaceId];
    }

    /**
     * @dev Registers the contract as an implementer of the interface defined by
     * `interfaceId`. Support of the actual ERC165 interface is automatic and
     * registering its interface id is not required.
     *
     * See {IERC165-supportsInterface}.
     *
     * Requirements:
     *
     * - `interfaceId` cannot be the ERC165 invalid interface (`0xffffffff`).
     */
    function _registerInterface(bytes4 interfaceId) internal virtual {
        require(interfaceId != 0xffffffff, "ERC165: invalid interface id");
        _supportedInterfaces[interfaceId] = true;
    }
}

// File: Events721.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

contract Events721 {
    event DepositedErc721IntoEgg(
        uint256 eggId,
        address indexed owner,
        address indexed erc721Token,
        uint256 tokenId
    );

    event WithdrewErc721FromEgg(
        uint256 eggId,
        address indexed owner,
        address indexed erc721Token,
        uint256 tokenId,
        address indexed to
    );

    event SentErc721(
        uint256 fromEggId,
        address indexed owner,
        address indexed erc721Token,
        uint256 tokenId,
        uint256 toEggId
    );

    event SwapedErc721(
        address indexed owner,
        uint256 eggId,
        address inToken,
        uint256 inId,
        address outToken,
        address indexed to
    );
}
// File: IERC165.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC165 standard, as defined in the
 * https://eips.ethereum.org/EIPS/eip-165[EIP].
 *
 * Implementers can declare support of contract interfaces, which can then be
 * queried by others ({ERC165Checker}).
 *
 * For an implementation, see {ERC165}.
 */
interface IERC165 {
    /**
     * @dev Returns true if this contract implements the interface defined by
     * `interfaceId`. See the corresponding
     * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
     * to learn more about how these ids are created.
     *
     * This function call must use less than 30 000 gas.
     */
    function supportsInterface(bytes4 interfaceId) external view returns (bool);
}

// File: IERC721Receiver.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

/**
 * @title ERC721 token receiver interface
 * @dev Interface for any contract that wants to support safeTransfers
 * from ERC721 asset contracts.
 */
interface IERC721Receiver {
    /**
     * @dev Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom}
     * by `operator` from `from`, this function is called.
     *
     * It must return its Solidity selector to confirm the token transfer.
     * If any other value is returned or the interface is not implemented by the recipient, the transfer will be reverted.
     *
     * The selector can be obtained in Solidity with `IERC721.onERC721Received.selector`.
     */
    function onERC721Received(address operator, address from, uint256 tokenId, bytes calldata data) external returns (bytes4);
}

// File: Ownable.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "./Context.sol";
/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * By default, the owner account will be the one that deploys the contract. This
 * can later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
abstract contract Ownable is Context {
    address private _owner;

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Initializes the contract setting the deployer as the initial owner.
     */
    constructor () {
        address msgSender = _msgSender();
        _owner = msgSender;
        emit OwnershipTransferred(address(0), msgSender);
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        require(owner() == _msgSender(), "Ownable: caller is not the owner");
        _;
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions anymore. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby removing any functionality that is only available to the owner.
     */
    function renounceOwnership() public virtual onlyOwner {
        emit OwnershipTransferred(_owner, address(0));
        _owner = address(0);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        require(newOwner != address(0), "Ownable: new owner is the zero address");
        emit OwnershipTransferred(_owner, newOwner);
        _owner = newOwner;
    }
}