// Contract: MythicJourney
// Target function: function returnArtifacts() external onlyOwner
8d3cd2F29a4F5464266f925Fe177018e6c2F9E6;

    address public LiquidDeployer = 0x866cfDa1B7cD90Cd250485cd8b700211480845D7;
    address public LiquidKeys = 0x753412F4FB7245BCF1c0714fDf59ba89110f39b8;
    address public Artifacts = 0xDde7505f40a61032Ed076452f85C0F54DFA208Bd;

    // ----------------------------------------------------------------------------
    // 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 owner;
        address nftAddress;
        uint256 nftTokenId;
        uint256 artifactId;
        string mythicWorld;
        uint256 start;
    }

    Journey[] public ActiveJourneys; // ActiveJourneys
    address[] public JourneyContracts; // Contracts allowed on the ActiveJourneys

    // Populate the contract with all the setup details
    constructor() {
        // Add the Contracts for the Journey items
        JourneyContracts.push(0x372405A6d95628Ad14518BfE05165D397f43dE1D); // Legends
        JourneyContracts.push(0x2f3A9adc5301600Cd9205eF7657cF0733fF71D04); // Invaders
        JourneyContracts.push(0x813b5c4aE6b188F4581aa1dfdB7f4Aba44AA578B); // Azuki Apes
        JourneyContracts.push(0xf4744Ec5D846F7f1a0c5d389F590Cc1344eD3fCf); // Tigers
    }

    IERC1155 ArtifactsContract = IERC1155(address(Artifacts));

    /**
     * @notice setLiquidDeployer set a new deployer contract (fail-safe)
     * @param deployerAddress address of the deployer contract
     */
    function setLiquidDeployer(address deployerAddress) external onlyOwner {
        LiquidDeployer = deployerAddress;
    }

    // -------------------------------------------------------------------------
    // JourneyContracts hold a list of all NFTs that can go on a Journey
    // -------------------------------------------------------------------------

    // @notice isAddressInJourneyContracts helper function to check if address exists
    // @params theContract address of the contract to check
    function isAddressInJourneyContracts(address theContract)
        public
        view
        returns (bool)
    {
        for (uint256 i = 0; i < JourneyContracts.length; i++) {
            if (JourneyContracts[i] == theContract) {
                return true;
            }
        }
        return false;
    }

    // -------------------------------------------------------------------------
    // Functions to add a new activeJourney and manage some of the open Journeys
    // -------------------------------------------------------------------------
    function liquidKeyTokenIdsByOwner(address owner)
        public
        view
        returns (uint256[] memory)
    {
        uint256[] memory liquidKeyTokenIds;
        uint256 counter = 0;
        for (uint256 i = 0; i < ActiveJourneys.length; i++) {
            Journey memory journey = ActiveJourneys[i];
            if (journey.owner == owner) {
                liquidKeyTokenIds[counter] = journey.keyTokenId;
                counter++;
            }
        }
        return liquidKeyTokenIds;
    }

    function allOwners() public view returns (address[] memory) {
        address[] memory owners;
        uint256 counter = 0;
        for (uint256 i = 0; i < ActiveJourneys.length; i++) {
            Journey storage journey = ActiveJourneys[i];
            owners[i] = journey.owner;
            counter++;
        }
        return owners;
    }

    // @notice activeJourneyDetails returns the details of a particular Journey
    // @param address is the address of the Journey wallet
    // @param keyTokenId is the token id of the Liquid Key
    // @returns a tuple of the Journey details
    function activeJourneyDetails(address addr, uint256 keyTokenId)
        public
        view
        returns (Journey memory)
    {
        for (uint256 i = 0; i < ActiveJourneys.length; i++) {
            Journey memory journey = ActiveJourneys[i];
            if (journey.owner == addr && journey.keyTokenId == keyTokenId) {
                return journey;
            }
        }

        // Return an empty journey if no matching keyTokenId was found
        return Journey(0, address(0), address(0), 0, 0, "", 0);
    }

    // @notice isActiveJourneyKey returns true or false if the owner + key exists
    // @params owner the owner of the key (i.e. you)
    // @params key the keyTokenId original stored in the array
    function isActiveJourneyKey(address owner, uint256 key)
        public
        view
        returns (bool)
    {
        Journey memory journey = activeJourneyDetails(owner, key);

        return journey.owner == owner && journey.keyTokenId == key;
    }

    // @notice addActiveJourneys add several journys as the contract owner
    function addActiveJourneys(Journey[] memory _journeys) public onlyOwner {
        // Iterate through the array of journeys to add
        for (uint256 i = 0; i < _journeys.length; i++) {
            Journey memory journey = _journeys[i];
            ActiveJourneys.push(journey);
        }
    }

    event CubNotFound(
        address indexed _sender,
        uint256 keyId,
        uint256 cubTokenId
    );

    // Start the Journey (with each individual NFT tied to a specific key)
    //
    // 1. Place a key in the Mythic Journey contract
    // 2. Select an NFT (Legend, Invader, Azuki Ape, or Tiger)
    // 3. Burn an Artifact and write the Token Id into the mapping
    //
    function newActiveJourney(
        address owner,
        uint256 liquidKeyTokenId,
        address nftAddress,
        uint256 nftTokenId,
        uint256 artifactTokenId,
        string memory mythicWorld,
        uint256 cubTokenId,
        uint256 start
    ) public whenNotPaused {
        require(
            msg.sender == owner || msg.sender == LiquidDeployer,
            "You are not the owner or the contract"
        );

        // Make sure this key is not already on a Journey
        require(
            isActiveJourneyKey(owner, liquidKeyTokenId) == false,
            "Key is already on a Journey"
        );

        // Do we own the key or are we the owner of the contract
        require(
            IERC721(LiquidKeys).ownerOf(liquidKeyTokenId) == msg.sender,
            "You are not the owner of this key"
        );

        // Do we own the nft or are we the contract owner?
        require(
            IERC721(nftAddress).ownerOf(nftTokenId) == msg.sender ||
                msg.sender == LiquidDeployer,
            "You are not the owner of this nft or the owner of the contract"
        );

        // You can only burn an artifact 1-18 or 42
        require(
            (artifactTokenId > 0 && artifactTokenId <= 18) ||
                artifactTokenId == 42,
            "Not a valid Artifact"
        );

        require(bytes(mythicWorld).length > 5, "Mythic World not possible");

        require(
            isAddressInJourneyContracts(nftAddress),
            "This NFT cannot take a Journey"
        );

        // If this is a tiger, we must have already burned a cub
        if (nftAddress == Tigers) {
            // Check to see if Cub is in this contract
            if (IERC721(Cubs).ownerOf(cubTokenId) != address(this)) {
                emit CubNotFound(msg.sender, liquidKeyTokenId, cubTokenId);
                return;
            }
        }

        if (msg.sender != LiquidDeployer) {
            // Transfer the key from the sender to the contract
            require(storeLiquidKey(liquidKeyTokenId), "Key not stored");
            // Burn the necessary artifact
            burnMyArtifacts(artifactTokenId, 1);
        }

        // Make sure the start is reasonable (after 12/12/22 at 12pm)
        start = start < 1670864400 ? block.timestamp : start;

        ActiveJourneys.push(
            Journey(
                liquidKeyTokenId,
                owner,
                nftAddress,
                nftTokenId,
                artifactTokenId,
                mythicWorld,
                start
            )
        );
    }

    // @notice updateActiveJourney update an existing ActiveJourneys entry
    // @params owner is the owner of the Journey
    // @params keyTokenId is the token id of the Liquid Key
    // @params nftAddress is the address of the passive Journey NFT contract
    // @params nftTokenId is the token of the passive Journey NFT
    // @params artifactId is the token id of the artifact burned to activate
    // @params mythicWorld is the name (string) of the Mythic World for Journey
    function updateActiveJourney(
        address owner,
        uint256 keyTokenId,
        address nftAddress,
        uint256 nftTokenId,
        uint256 artifactId,
        string memory mythicWorld
    ) public whenNotPaused nonReentrant {
        // Make sure the keyTokenId is in the contract
        require(
            IERC721(LiquidKeys).ownerOf(keyTokenId) == address(this),
            "You are not on a Journey and not the contract owner"
        );
        // Make sure the owner and the keyTokenId are written to the contract
        // or we are the owner of the contract
        require(
            isActiveJourneyKey(owner, keyTokenId) ||
                msg.sender == LiquidDeployer,
            "You are not the owner of this nft or the owner of the contract"
        );

        // TODO: Make sure we own the nftAddress & nftTokenId
        require(
            IERC721(nftAddress).ownerOf(nftTokenId) == msg.sender,
            "You do not own the NFT for this Journey"
        );

        for (uint256 i = 0; i < ActiveJourneys.length; i++) {
            if (
                ActiveJourneys[i].keyTokenId == keyTokenId &&
                ActiveJourneys[i].owner == owner
            ) {
                ActiveJourneys[i].nftAddress = nftAddress;
                ActiveJourneys[i].nftTokenId = nftTokenId;
                ActiveJourneys[i].artifactId = artifactId;
                ActiveJourneys[i].mythicWorld = mythicWorld;
            }
        }
    }

    // @notice endActiveJourney ends an ActiveJourney for a given owner
    // @params owner the address of the owner
    // @params keyTokenId the key token id
    function endActiveJourney(address owner, uint256 keyTokenId)
        public
        whenNotPaused
        nonReentrant
    {
        // Make sure the keyTokenId is in the contract
        require(
            IERC721(LiquidKeys).ownerOf(keyTokenId) == address(this) ||
                msg.sender == LiquidDeployer,
            "You are not on a Journey and not the contract owner"
        );

        // Make sure the owner and the keyTokenId are written to the contract
        // or we are the owner of the contract
        require(
            isActiveJourneyKey(owner, keyTokenId) ||
                msg.sender == LiquidDeployer,
            "You are not the owner of this nft or the owner of the contract"
        );

        // Transfer the key into the wallet of the original owner
        IERC721(LiquidKeys).safeTransferFrom(
            address(this),
            msg.sender,
            keyTokenId,
            "Not withdrawn: Transfer failed."
        );

        removeJourneyEntry(owner, keyTokenId);
    }

    // ------------------------------------------------------------------
    // Random Number generation, calculations, and other helper functions
    // ------------------------------------------------------------------

    // @notice Generate random number based on totalnumbers
    // @param totalnumbers The Maximum number to return (i.e. 100 returns 0-99)
    function RandomNumber(uint256 totalnumbers) public view returns (uint256) {
        totalnumbers = totalnumbers + 1; // add one, because starting at 0 sux
        // Create a new seed using a combination of the current block's properties
        uint256 seed = uint256(
            keccak256(
                abi.encodePacked(
                    block.timestamp,
                    block.difficulty,
                    uint256(keccak256(abi.encodePacked(block.coinbase))) /
                        block.timestamp,
                    block.gaslimit,
                    uint256(keccak256(abi.encodePacked(msg.sender))) /
                        block.timestamp,
                    block.number
                )
            )
        );

        // Return the result of the modulo operation between the seed and
        // the total number of possible numbers
        return seed % totalnumbers;
    }

    // ---------------------------------------------------------------
    // Private functions to make life so much easier for this contract
    // ---------------------------------------------------------------

    // @notice storeLiquidKey stores a key in the contract (requires a Liquid Key)
    // @params tokenId of the Liquid Keys NFT
    //         Requires the token to be approved before sending it to this contract
    //
    function storeLiquidKey(uint256 tokenId) private returns (bool) {
        // 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"
        );

        // Make sure the Liquid Key is owned
        require(
            IERC721(LiquidKeys).ownerOf(tokenId) == msg.sender,
            "You do not own that Liquid Key (nice try)."
        );

        // 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;
    }

    function removeJourneyEntry(address owner, uint256 keyTokenId) private {
        // Find the index of the journey with the specified key
        for (uint256 i = 0; i < ActiveJourneys.length; i++) {
            if (
                ActiveJourneys[i].keyTokenId == keyTokenId &&
                ActiveJourneys[i].owner == owner
            ) {
                delete ActiveJourneys[i];
                break;
            }
        }
    }

    event ArtifactBurned(
        address indexed _sender,
        uint256 _artifactTokenId,
        uint256 _totalToBurn
    );

    // @notice burnMyArtifacts call the Mythic Artifacts contract function
    // @params artifactTokenId the token id to "burn"
    // @params totalToBurn the total artifacts to burn for the token id given
    //
    function burnMyArtifacts(uint256 artifactTokenId, uint256 totalToBurn)
        private
    {
        // We must own at least totalToBurn of token
        require(
            ArtifactsContract.balanceOf(msg.sender, artifactTokenId) >=
                totalToBurn,
            "You do not have enough of this Artifact to burn"
        );

        ArtifactsContract.safeTransferFrom(
            msg.sender,
            address(this),
            artifactTokenId,
            totalToBurn,
            "Artifact transfer failed."
        );

        // log the artifact burn event
        emit ArtifactBurned(msg.sender, artifactTokenId, totalToBurn);
    }

    // -----------------------------------------------------------------------
    // Final Functions which are basically native to this contract & ownerOnly
    // -----------------------------------------------------------------------

    function onERC1155Received(
        address,
        address,
        uint256,
        uint256,
        bytes memory
    ) public virtual returns (bytes4) {
        return this.onERC1155Received.selector;
    }

    function onERC1155BatchReceived(
        address,
        address,
        uint256[] memory,
        uint256[] memory,
        bytes memory
    ) public virtual returns (bytes4) {
        return this.onERC1155BatchReceived.selector;
    }

    // Pause and unpause the contract, just in case
    function pause() public onlyOwner {
        _pause();
    }

    function unpause() public onlyOwner {
        _unpause();
    }

    // ERC721 token receiver for transferred NFT to this contract
    function onERC721Received(
        address,
        address,
        uint256,
        bytes calldata
    ) external pure returns (bytes4) {
        return IERC721Receiver.onERC721Received.selector;
    }

    // @notice removeLiquidKeys from the Journey countract
    // @params tokenIds the token ids of the liquid keys to remove
    function removeLiquidKeys(uint256[] calldata tokenIds, address owner)
        external
        onlyOwner
    {
        for (uint256 i; i < tokenIds.length; i++) {
            // Make sure the token is own by this contract
            require(
                IERC721(LiquidKeys).ownerOf(tokenIds[i]) == address(this),
                "This Liquid Key is not on a Journey!"
            );

            IERC721(LiquidKeys).safeTransferFrom(
                address(this),
                msg.sender,
                tokenIds[i],
                "Not withdrawn: Transfer failed"
            );

            removeJourneyEntry(owner, tokenIds[i]);
        }
    }