// Contract: MythicGateway
// Target function: function removeActiveJourneyKey(address owner, uint256 key)
 be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
     */
    function values(AddressSet storage set)
        internal
        view
        returns (address[] memory)
    {
        bytes32[] memory store = _values(set._inner);
        address[] memory result;

        assembly {
            result := store
        }

        return result;
    }

    // UintSet

    struct UintSet {
        Set _inner;
    }

    /**
     * @dev Add a value to a set. O(1).
     *
     * Returns true if the value was added to the set, that is if it was not
     * already present.
     */
    function add(UintSet storage set, uint256 value) internal returns (bool) {
        return _add(set._inner, bytes32(value));
    }

    /**
     * @dev Removes a value from a set. O(1).
     *
     * Returns true if the value was removed from the set, that is if it was
     * present.
     */
    function remove(UintSet storage set, uint256 value)
        internal
        returns (bool)
    {
        return _remove(set._inner, bytes32(value));
    }

    /**
     * @dev Returns true if the value is in the set. O(1).
     */
    function contains(UintSet storage set, uint256 value)
        internal
        view
        returns (bool)
    {
        return _contains(set._inner, bytes32(value));
    }

    /**
     * @dev Returns the number of values on the set. O(1).
     */
    function length(UintSet storage set) internal view returns (uint256) {
        return _length(set._inner);
    }

    /**
     * @dev Returns the value stored at position `index` in the set. O(1).
     *
     * Note that there are no guarantees on the ordering of values inside the
     * array, and it may change when more values are added or removed.
     *
     * Requirements:
     *
     * - `index` must be strictly less than {length}.
     */
    function at(UintSet storage set, uint256 index)
        internal
        view
        returns (uint256)
    {
        return uint256(_at(set._inner, index));
    }

    /**
     * @dev Return the entire set in an array
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
     */
    function values(UintSet storage set)
        internal
        view
        returns (uint256[] memory)
    {
        bytes32[] memory store = _values(set._inner);
        uint256[] memory result;

        assembly {
            result := store
        }

        return result;
    }
}

pragma solidity ^0.8.0;

/**
 * @dev Required interface of an ERC1155 compliant contract, as defined in the
 * https://eips.ethereum.org/EIPS/eip-1155[EIP].
 *
 * _Available since v3.1._
 */
interface IERC1155 is IERC165 {
    /**
     * @dev Emitted when `value` tokens of token type `id` are transferred from `from` to `to` by `operator`.
     */
    event TransferSingle(
        address indexed operator,
        address indexed from,
        address indexed to,
        uint256 id,
        uint256 value
    );

    /**
     * @dev Equivalent to multiple {TransferSingle} events, where `operator`, `from` and `to` are the same for all
     * transfers.
     */
    event TransferBatch(
        address indexed operator,
        address indexed from,
        address indexed to,
        uint256[] ids,
        uint256[] values
    );

    /**
     * @dev Emitted when `account` grants or revokes permission to `operator` to transfer their tokens, according to
     * `approved`.
     */
    event ApprovalForAll(
        address indexed account,
        address indexed operator,
        bool approved
    );

    /**
     * @dev Emitted when the URI for token type `id` changes to `value`, if it is a non-programmatic URI.
     *
     * If an {URI} event was emitted for `id`, the standard
     * https://eips.ethereum.org/EIPS/eip-1155#metadata-extensions[guarantees] that `value` will equal the value
     * returned by {IERC1155MetadataURI-uri}.
     */
    event URI(string value, uint256 indexed id);

    /**
     * @dev Returns the amount of tokens of token type `id` owned by `account`.
     *
     * Requirements:
     *
     * - `account` cannot be the zero address.
     */
    function balanceOf(address account, uint256 id)
        external
        view
        returns (uint256);

    /**
     * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {balanceOf}.
     *
     * Requirements:
     *
     * - `accounts` and `ids` must have the same length.
     */
    function balanceOfBatch(address[] calldata accounts, uint256[] calldata ids)
        external
        view
        returns (uint256[] memory);

    /**
     * @dev Grants or revokes permission to `operator` to transfer the caller's tokens, according to `approved`,
     *
     * Emits an {ApprovalForAll} event.
     *
     * Requirements:
     *
     * - `operator` cannot be the caller.
     */
    function setApprovalForAll(address operator, bool approved) external;

    /**
     * @dev Returns true if `operator` is approved to transfer ``account``'s tokens.
     *
     * See {setApprovalForAll}.
     */
    function isApprovedForAll(address account, address operator)
        external
        view
        returns (bool);

    /**
     * @dev Transfers `amount` tokens of token type `id` from `from` to `to`.
     *
     * Emits a {TransferSingle} event.
     *
     * Requirements:
     *
     * - `to` cannot be the zero address.
     * - If the caller is not `from`, it must have been approved to spend ``from``'s tokens via {setApprovalForAll}.
     * - `from` must have a balance of tokens of type `id` of at least `amount`.
     * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the
     * acceptance magic value.
     */
    function safeTransferFrom(
        address from,
        address to,
        uint256 id,
        uint256 amount,
        bytes calldata data
    ) external;

    /**
     * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {safeTransferFrom}.
     *
     * Emits a {TransferBatch} event.
     *
     * Requirements:
     *
     * - `ids` and `amounts` must have the same length.
     * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155BatchReceived} and return the
     * acceptance magic value.
     */
    function safeBatchTransferFrom(
        address from,
        address to,
        uint256[] calldata ids,
        uint256[] calldata amounts,
        bytes calldata data
    ) external;
}

pragma solidity ^0.8.0;

/* Mythic Gateways and the Mythic Journey [an overview]

   Turn-based RPG which does at least the following:

   1. Requires a Liquid Key to open a Mythic Gateway.
      The Liquid Key must be added to the Mythic Gateway contract.

   2. Requires the user to select an NFT from one of the pre-approved contracts,
      such as Liquid Legends, Liquid Invaders, Azuki Ape Social Club, or
      Typical Tigers.

      a) If a Typical Tiger is selected, also requires a Baby Cub Tiger
         to be permanently stored inside the contract

      - Writes the Token ID of the Liquid Key + the contract of the NFT
        being used along with the Token ID of the NFT being used.

   3. Requires a Mythic Artifact to be burned during the initial setup
      of the Mythic Gateway.

   4. We use the ETH blockrate to determine how many points are gained during
      the time the gateway is open (the NFT is on a "Journey"). Points accrue
      at a specific rate per Journey.

   5. During the Journey, the NFT randomly accumulates items, which are dynamic,
      based on a list of contracts (also dynamic) and periodically the user can
      interact with the contract and claim any item which was randomly
      acquired on the Journey.

   *  Other stuff is proprietary, so... go fack.

 */

// Burn an artifact and open the Gateway
interface burnArtifactOpenGateway {
    function openGateway(address tokenOwner, uint256 tokenId)
        external
        returns (bool);
}

pragma solidity ^0.8.16;

// See notes above for details... this contract is pretty intense
contract MythicGateway is Ownable, ReentrancyGuard, Pausable {
    using EnumerableSet for EnumerableSet.UintSet;
    using Strings for uint256;

    // @notice burnMyArtifacts call the Mythic Artifacts contract function
    // @params owner of the artifact
    // @params artifactTokenId
    function burnMyArtifacts(uint256 artifactTokenId, uint256 totalToBurn)
        public
        whenNotPaused
        nonReentrant
        returns (bool, bytes memory)
    {
        // Encode the function call and arguments into a bytes memory variable
        bytes memory functionCall = abi.encodeWithSignature(
            "burnMyArtifacts(uint256,uint256)",
            artifactTokenId,
            totalToBurn
        );

        address targetContract = Artifacts;

        // Call the function using the `delegatecall` function
        (bool success, bytes memory returnData) = targetContract.delegatecall(
            functionCall
        );

        // Check the success status of the call
        if (!success) {
            // The call failed
            // You can use the returnData variable to get more information about the error
        } else {
            // The call was successful
            // You can use the returnData variable to get the return value of the function, if any
        }

        return (success, returnData);
    }

    // Setup the contracts for the pieces in the Journey
    // TODO: Can we remove these items? I believe so, as long as
    //       we first verify that each item exists before we write it
    //       to the Journey entry.
    //
    address public Legends = 0x372405A6d95628Ad14518BfE05165D397f43dE1D;
    address public Invaders = 0x2f3A9adc5301600Cd9205eF7657cF0733fF71D04;
    address public AzukiApes = 0x813b5c4aE6b188F4581aa1dfdB7f4Aba44AA578B;
    address public Tigers = 0xf4744Ec5D846F7f1a0c5d389F590Cc1344eD3fCf;
    address public Cubs = 0x98d3cd2F29a4F5464266f925Fe177018e6c2F9E6;

    address public LiquidKeys = 0x753412F4FB7245BCF1c0714fDf59ba89110f39b8;
    address public Artifacts = 0xDde7505f40a61032Ed076452f85C0F54DFA208Bd;

    address[] public journeyContracts;
    string[] public MythicWorlds;

    // ----------------------------------------------------------------------------
    // Setup the Liquid Key Journey (one address and many possible keys)
    // Define a struct to hold the key, ID, and token ID values
    struct Journey {
        uint256 keyTokenId;
        address nftAddress;
        uint256 nftTokenId;
        uint256 artifactId;
        string mythicWorld;
        uint256 start;
        uint256 lastClaimCheck;
    }
    mapping(address => Journey[]) public activeJourneys;

    // @notice Claims are items we have claimed on our Journey
    struct Claims {
        address claimContract;
        uint256 tokenId; // token id of claim or highest token id
    }

    // Items for Claim by each address on a Journey
    mapping(address => Claims[]) public JourneyClaims;

    mapping(address => uint256) public ClaimableItems;

    // Populate the contract with all the setup details
    constructor() {
        // Add the Contracts for the Journey items
        addJourneyContract(0x372405A6d95628Ad14518BfE05165D397f43dE1D); // Legends
        addJourneyContract(0x2f3A9adc5301600Cd9205eF7657cF0733fF71D04); // Invaders
        addJourneyContract(0x813b5c4aE6b188F4581aa1dfdB7f4Aba44AA578B); // Azuki Apes
        addJourneyContract(0xf4744Ec5D846F7f1a0c5d389F590Cc1344eD3fCf); // Tigers

        // Add the possible ClaimableItems
        // Artifacts
        ClaimableItems[0xDde7505f40a61032Ed076452f85C0F54DFA208Bd] = 18;
    }

    address public LiquidDeployer = 0x866cfDa1B7cD90Cd250485cd8b700211480845D7;

    // METL Token
    address public METLToken = 0xFcbE615dEf610E806BB64427574A2c5c1fB55510;

    // Specify the IERC20 interface to the METL Token
    IERC20 METLTokenAddress = IERC20(address(METLToken));

    // Total number of days before we can claim anything on a Journey
    uint256 public RequiredDays = 7;

    // Probability of finding something on a Journey
    uint256 public Probability = 2;

    /**
     * @notice setRequiredDays days before we can claim items
     * @param newnum the new number of days
     */
    function setRequiredDays(uint256 newnum) external onlyOwner {
        RequiredDays = newnum;
    }

    /**
     * @notice setProbability probability of finding something
     * @param newnum the probability (i.e. 2 is 2% chance)
     */
    function setProbability(uint256 newnum) external onlyOwner {
        Probability = newnum;
    }

    // -------------------------------------------------------------------------
    // JourneyContracts hold a list of all NFTs that can go on a Journey
    // -------------------------------------------------------------------------

    // @notice addContract add a mew address to the list of journeyContracts
    // @params newAddress the address to add for the smart contract
    //         such as Mythic Keys, etc. All are ERC-1155 tokens.
    //
    function addJourneyContract(address newAddress) public onlyOwner {
        for (uint256 i = 0; i < journeyContracts.length; i++) {
            if (journeyContracts[i] == address(0)) {
                journeyContracts[i] = newAddress;
                return;
            }
        }

        // If we did not replace an empty contract, add a new one
        journeyContracts.push(newAddress);
    }

    // Some code goes here, beyatch

    // @notice storeLiquidKey stores a key in the contract (requires a Liquid Key)
    // Works if we approve the transfer before calling this function
    //
    function storeLiquidKey(uint256 tokenId)
        public
        whenNotPaused
        nonReentrant
        returns (bool)
    {
        // Cannot be the LiquidKeys contract
        require(msg.sender != LiquidKeys, "Invalid NFT contract address");

        // Make sure the Liquid Key is owned
        require(
            IERC721(LiquidKeys).ownerOf(tokenId) == msg.sender,
            "You do not own that Liquid Key (nice try, asshole)."
        );

        // Check if the contract is approved to transfer the specified token
        require(
            IERC721(LiquidKeys).getApproved(tokenId) == address(this),
            "Journeys Contract is not approved to transfer this token"
        );

        // Transfer the key from the sender to the contract
        IERC721(LiquidKeys).safeTransferFrom(
            msg.sender,
            address(this),
            tokenId,
            "Failed to transfer token to contract"
        );

        // Return true if the key transfer was successful
        return true;
    }