contract nonFungibleFungiGenerative is ERC721, Ownable, VRFConsumerBase {
    /* Created by: 0xInuarashi
    Discord: 0xInuarashi#1234
    Twitter: https://twitter.com/0xinuarashi

    NonFungibleFungi Generative
    Features: 
        1. VRF Random Generator [X]
        2. Mint for MintPass [X] */

    /*
    This is for Rinkeby
    constructor() ERC721("Non Fungible Fungi", "NFF") 
    VRFConsumerBase(
        0xb3dCcb4Cf7a26f6cf6B120Cf5A73875B7BBc655B,
        0x01BE23585060835E02B77ef475b0Cc51aA1e0709
    ) {
        keyHash = 0x2ed0feb3e7fd2022120aa84fab1945545a9f2ffc9076fd6156fa96eaff4c1311;
        fee = 0.1 * (10**18);
    }
    */

    // This is for Mainnet
    constructor() ERC721("Non Fungible Fungi", "NFF") 
    VRFConsumerBase(
        0xf0d54349aDdcf704F77AE15b96510dEA15cb7952,
        0x514910771AF9Ca656af840dff83E8264EcF986CA
    ) {
        keyHash = 0xAA77729D3466CA35AE8D28B3BBAC7CC36A5031EFDC430821C02BC31A238AF445;
        fee = 2 * (10**18);
    }

    // General NFT Variables
    uint public constant maxTokens = 10000;
    uint public totalSupply = 0;

    string internal baseTokenURI;
    string internal baseTokenURI_EXT;

    event Mint(address indexed to, uint mintPassId, uint tokenId);

    modifier onlySender {
        require(msg.sender == tx.origin, "No smart contracts!");
        _;
    }

    // Withdraw Ether
    function withdrawEther() public onlyOwner {
        payable(msg.sender).transfer(address(this).balance); 
    }


    // Mint Pass Interfacing
    address public mintPassAddress;
    IERC721 MintPass;

    function setMintPass(address address_) public onlyOwner {
        mintPassAddress = address_;
        MintPass = IERC721(address_);
    }

    ////////////////////////// VRF /////////////////////////////
    bytes32 internal keyHash;
    uint internal fee;
    uint internal randomResult;
    
    // VRF Functions
    function getRandomNumber() public onlyOwner returns (bytes32 requestId) {
        require(LINK.balanceOf(address(this)) >= fee, "Not enough LINK!");
        return requestRandomness(keyHash, fee);
    }
    function fulfillRandomness(bytes32 requestId, uint randomness) internal override {
        randomResult = randomness;
    }    
    function getRandomResult() public view onlyOwner returns (uint) {
        return randomResult;
    }

    // >>> Now, the VRF is stored in [uint internal randomResult]
    ////////////////////////// VRF /////////////////////////////

    /////////////////////// Token ID Generator ////////////////////////
    // ** Thanks LarvaLabs for this big brain method. **             //
    // This function is modified by 0xInuarashi                      //
    // and now includes the usage of generated input from VRF.       //
    ///////////////////////////////////////////////////////////////////

    uint[maxTokens] internal indices;
    uint32 internal nonce;

    function getVRFRandomIndex(uint tokenId_) internal returns (uint) {
        require(randomResult != 0, "VRF Random Result has not been set!");
        uint _tokensRemaining = maxTokens - totalSupply; // require that this calculation is possible from all caller functions
        uint _maxIndex = _tokensRemaining == 0 ? 0 : _tokensRemaining - 1; // shorthand if for safety 
        uint _rand = uint(keccak256(abi.encodePacked(randomResult, nonce, tokenId_, msg.sender, block.difficulty, block.timestamp))) % _tokensRemaining; // magic random
    
        uint _output = 0;

        _output = indices[_rand] != 0 ? indices[_rand] :_rand;
        indices[_rand] = indices[_maxIndex] == 0 ? _maxIndex : indices[_maxIndex];

        uint32 _nonceAdd = uint32(uint256(keccak256(abi.encodePacked(randomResult, nonce, tokenId_, msg.sender, block.difficulty, block.timestamp)))) % 10; // magic nonce
        nonce += _nonceAdd; // increase nonce

        return _output;
    }

    /////////////////////// Token ID Generator ////////////////////////

    // Minting with MintPass, It uses VRF and Token ID Generator
    bool public mintWithPassEnabled;

    // mapping(uint => uint) mintPassUsed; // some safety

    modifier mintWithPass {
        require(mintWithPassEnabled, "Minting with Mint Pass is not enabled yet!");
        _;
    }

    function setMintWithPassStatus(bool bool_) public onlyOwner {
        mintWithPassEnabled = bool_;
    }

    function mintWithMintPass(uint mintPassId_) public onlySender mintWithPass {
        require(msg.sender == MintPass.ownerOf(mintPassId_), "You are not the owner of this mint pass!");
        require(maxTokens > totalSupply, "No more available tokens!");

        // require(mintPassUsed[mintPassId_] == 0, "This mint pass is already used!"); // this should never happen
        // mintPassUsed[mintPassId_]++; // increment this mapping as used

        MintPass.transferFrom(msg.sender, address(this), mintPassId_); // transfer the mint pass to this address (equivalent to burning it)

        uint _mintId = getVRFRandomIndex(mintPassId_);
        totalSupply++;

        _mint(msg.sender, _mintId);
        emit Mint(msg.sender, mintPassId_, _mintId);
    }

    function multiMintWithMintPass(uint[] memory mintPassIds_) public onlySender mintWithPass {
        for (uint i = 0; i < mintPassIds_.length; i++) {
            mintWithMintPass(mintPassIds_[i]);
        }
    }

    // 0xInuarashi Custom Functions (Saves Gas!)
    function multiTransferFrom(address from_, address to_, uint256[] memory tokenIds_) public {
        for (uint256 i = 0; i < tokenIds_.length; i++) {
            transferFrom(from_, to_, tokenIds_[i]);
        }
    }
    function multiSafeTransferFrom(address from_, address to_, uint256[] memory tokenIds_, bytes memory data_) public {
        for (uint256 i = 0; i < tokenIds_.length; i++) {
            safeTransferFrom(from_, to_, tokenIds_[i], data_);
        }
    }

    // General NFT Administration
    function setBaseTokenURI(string memory uri_) external onlyOwner {
        baseTokenURI = uri_;
    }
    function setBaseTokenURI_EXT(string memory ext_) external onlyOwner {
        baseTokenURI_EXT = ext_;
    }

    // View Functions
    function tokenURI(uint tokenId_) public view override returns (string memory) {
        require(_exists(tokenId_), "Query for non-existent token!");
        return string(abi.encodePacked(baseTokenURI, Strings.toString(tokenId_), baseTokenURI_EXT));
    }
    function walletOfOwner(address address_) public view returns (uint[] memory) {
        uint _balance = balanceOf(address_); // get balance of address
        uint[] memory _tokens = new uint[](_balance); // initialize array 
        uint _index;
        for (uint i = 0; i < maxTokens; i++) {
            if (address_ == ownerOf(i)) { _tokens[_index] = i; _index++; }
        }
        return _tokens;
    }
}
function withdrawEther() public onlyOwner {
    payable(msg.sender).transfer(address(this).balance); 
}

// Withdraw Ether