contract DoodleNoodles is IERC721, Ownable, Functional, ControlledAccess {

    using Address for address;

    // Declare variables
    string private _name;
    string private _symbol;
    string private _baseURI;
    string public provenanceHash;

    bool public mintActive;
    bool public presaleActive;
    bool public revealed;
    uint256 public price;
    uint256 public totalNoodles;
    uint256 public numberMinted;
    uint256 public maxPerWallet;
    uint256 public maxPerTx;
    uint256 public reservedNoodles;
    uint256 public goldenNoodles;
    uint256 public startingIndex;
    uint256 public startingIndexBlock;
    
    // Mappings
    mapping(uint256 => address) private _owners;
    mapping(address => uint256) private _balances;
    mapping(uint256 => address) private _tokenApprovals;
    mapping(address => mapping(address => bool)) private _operatorApprovals;
    mapping(address => uint256) private _tokensMintedby;

    // Initialise contract
    constructor() {
        _name = "Doodle Noodles Official";
        _symbol = "NOODS";

        revealed = false;
        mintActive = false;
        presaleActive = false;
        totalNoodles = 5555;
        price = 0.06 ether;
        maxPerWallet = 5;
        maxPerTx = 5;
        reservedNoodles = 40; // reserved for giveaways and such
        goldenNoodles = 5; // Defines 5 custom Golden Noodles
        signerAddress = 0x5916cbaF66a7bFa2B0F2bF60c05cb16763F051F8;
    }

    //@dev See {IERC165-supportsInterface}. Interfaces Supported by this Standard
    function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
        return  interfaceId == type(IERC721).interfaceId ||
                interfaceId == type(IERC721Metadata).interfaceId ||
                interfaceId == type(IERC165).interfaceId ||
                interfaceId == DoodleNoodles.onERC721Received.selector;
    }

    // Withdraw function
    function withdraw() public onlyOwner {
        uint256 balance = address(this).balance;
        payable(msg.sender).transfer(balance);
    }

    function giveAway(address _to, uint256 _qty) external onlyOwner() {
        require( _qty <= (reservedNoodles - goldenNoodles), "Exceeds reserved Noodles supply" );

        uint256 mintSeedValue = numberMinted;
        for(uint256 i = 0; i < _qty; i++) {
            _safeMint(_to, mintSeedValue + i);
            numberMinted ++; 
        }
        reservedNoodles -= _qty;
    }

     function goldenNoodle(address _to, uint256 _qty) external onlyOwner() {
        require( _qty <= goldenNoodles, "Exceeds reserved Golden Noodles supply" );

        uint256 mintSeedValue = numberMinted;
        for(uint256 i = 0; i < _qty; i++) {
            _safeMint(_to, mintSeedValue + i);
            numberMinted ++; 
        }
        goldenNoodles -= _qty;
    }

    function mintNoodle(uint256 qty) external payable reentryLock {
        require(mintActive);
        require((qty + numberMinted + reservedNoodles) < totalNoodles, "Not enough availability");
        require(qty <= maxPerTx, "Exceeds max allowed per transaction");
        require((_tokensMintedby[_msgSender()] + qty) <= maxPerWallet, "Exceeds Max allowed per wallet");
        require(msg.value >= qty * price, "Insufficient Funds");
        
        uint256 mintSeedValue = numberMinted; //Store the starting value of the mint batch

        //Handle ETH transactions
        uint256 cashIn = msg.value;
        uint256 cashChange = cashIn - (qty * price);

        //send tokens
        for(uint256 i = 0; i < qty; i++) {
            _safeMint(_msgSender(), mintSeedValue + i);
            numberMinted ++;
            _tokensMintedby[_msgSender()] ++;
        }

        if (cashChange > 0){
            (bool success, ) = msg.sender.call{value: cashChange}("");
            require(success, "Mint: unable to send change to user");
        }
    }
    
    function presaleMintNoodle(uint256 qty,
        bytes32 _r,
        bytes32 _s,
        uint8 _v
                        ) external payable onlyValidAccess(_r, _s, _v) reentryLock {
        require(presaleActive);
        require((qty + numberMinted + reservedNoodles) < totalNoodles, "Not enough availability");
        require(qty <= maxPerTx, "Exceeds max allowed per transaction");
        require((_tokensMintedby[_msgSender()] + qty) <= maxPerWallet, "Exceeds Max allowed per wallet");
        require(msg.value >= qty * price, "Insufficient Funds");
        
        uint256 mintSeedValue = numberMinted; //Store the starting value of the mint batch

        //Handle ETH transactions
        uint256 cashIn = msg.value;
        uint256 cashChange = cashIn - (qty * price);

        //send tokens
        for(uint256 i = 0; i < qty; i++) {
            _safeMint(_msgSender(), mintSeedValue + i);
            numberMinted ++;
            _tokensMintedby[_msgSender()] ++;
        }

        if (cashChange > 0){
            (bool success, ) = msg.sender.call{value: cashChange}("");
            require(success, "Mint: unable to send change to user");
        }
    }


    function tokenURI(uint256 tokenId) public view virtual returns (string memory) {
        require(_exists(tokenId),"ERC721Metadata: URI query for nonexistent token");
        if(revealed == false) {
        return string(abi.encodePacked(_baseURI, "0.json"));
        }
        // Otherwise
        return string(abi.encodePacked(_baseURI, toString(tokenId), ".json")); 
    }

    function reveal() public onlyOwner {
        revealed = true;
    }

    function setMaxPerWallet(uint256 maxWallet) external onlyOwner {
        maxPerWallet = maxWallet;
    }

    function setMaxPerTx(uint256 newMax) external onlyOwner {
        maxPerTx = newMax;
    }

    function setBaseURI(string memory newURI) public onlyOwner {
        _baseURI = newURI;
    }

    function activateMint() public onlyOwner {
        mintActive = true;
    }

     function activatePresale() public onlyOwner {
        presaleActive = true;
    }

    function deactivateMint() public onlyOwner {
        mintActive = false;
    }

    function deactivatePresale() public onlyOwner {
        presaleActive = false;
    }

    function setPrice(uint256 newPrice) public onlyOwner {
        price = newPrice;
    }

    function setTotalTokens(uint256 numTokens) public onlyOwner {
        totalNoodles = numTokens;
    }

    function totalSupply() external view returns (uint256) {
        return numberMinted;
    }

    ///////// required functions
    function getBalance(address tokenAddress) view external returns (uint256) {
        return _balances[tokenAddress];
    }

    /**
     * @dev See {IERC721-balanceOf}.
     */
    function balanceOf(address owner) public view virtual override returns (uint256) {
        require(owner != address(0), "ERC721: balance query for the zero address");
        return _balances[owner];
    }

    /**
     * @dev See {IERC721-ownerOf}.
     */
    function ownerOf(uint256 tokenId) public view virtual override returns (address) {
        address owner = _owners[tokenId];
        require(owner != address(0), "ERC721: owner query for nonexistent token");
        return owner;
    }

    /**
     * @dev See {IERC721-approve}.
     */
    function approve(address to, uint256 tokenId) public virtual override {
        address owner = ownerOf(tokenId);
        require(to != owner, "ERC721: approval to current owner");

        require(
            msg.sender == owner || isApprovedForAll(owner, msg.sender),
            "ERC721: approve caller is not owner nor approved for all"
        );

        _approve(to, tokenId);
    }

    /**
     * @dev See {IERC721-getApproved}.
     */
    function getApproved(uint256 tokenId) public view virtual override returns (address) {
        require(_exists(tokenId), "ERC721: approved query for nonexistent token");

        return _tokenApprovals[tokenId];
    }

    /**
     * @dev See {IERC721-setApprovalForAll}.
     */
    function setApprovalForAll(address operator, bool approved) public virtual override {
        require(operator != msg.sender, "ERC721: approve to caller");

        _operatorApprovals[msg.sender][operator] = approved;
        emit ApprovalForAll(msg.sender, operator, approved);
    }

    /**
     * @dev See {IERC721-isApprovedForAll}.
     */
    function isApprovedForAll(address owner, address operator) public view virtual override returns (bool) {
        return _operatorApprovals[owner][operator];
    }

    /**
     * @dev See {IERC721-transferFrom}.
     */
    function transferFrom(
        address from,
        address to,
        uint256 tokenId
    ) public virtual override {
        //solhint-disable-next-line max-line-length
        require(_isApprovedOrOwner(msg.sender, tokenId), "ERC721: transfer caller is not owner nor approved");

        _transfer(from, to, tokenId);
    }

    /**
     * @dev See {IERC721-safeTransferFrom}.
     */
    function safeTransferFrom(
        address from,
        address to,
        uint256 tokenId
    ) public virtual override {
        safeTransferFrom(from, to, tokenId, "");
    }

    /**
     * @dev See {IERC721-safeTransferFrom}.
     */
    function safeTransferFrom(
        address from,
        address to,
        uint256 tokenId,
        bytes memory _data
    ) public virtual override {
        require(_isApprovedOrOwner(msg.sender, tokenId), "ERC721: transfer caller is not owner nor approved");
        _safeTransfer(from, to, tokenId, _data);
    }

    function _safeTransfer(
        address from,
        address to,
        uint256 tokenId,
        bytes memory _data
    ) internal virtual {
        _transfer(from, to, tokenId);
        require(_checkOnERC721Received(from, to, tokenId, _data), "ERC721: transfer to non ERC721Receiver implementer");
    }

    function _exists(uint256 tokenId) internal view virtual returns (bool) {
        return _owners[tokenId] != address(0);
    }

    function _isApprovedOrOwner(address spender, uint256 tokenId) internal view virtual returns (bool) {
        require(_exists(tokenId), "ERC721: operator query for nonexistent token");
        address owner = ownerOf(tokenId);
        return (spender == owner || getApproved(tokenId) == spender || isApprovedForAll(owner, spender));
    }

    function _safeMint(address to, uint256 tokenId) internal virtual {
        _safeMint(to, tokenId, "");
    }

    function _safeMint(
        address to,
        uint256 tokenId,
        bytes memory _data
    ) internal virtual {
        _mint(to, tokenId);
        require(
            _checkOnERC721Received(address(0), to, tokenId, _data),
            "ERC721: transfer to non ERC721Receiver implementer"
        );
    }

    function _mint(address to, uint256 tokenId) internal virtual {
        require(to != address(0), "ERC721: mint to the zero address");
        require(!_exists(tokenId), "ERC721: token already minted");

        _beforeTokenTransfer(address(0), to, tokenId);

        _balances[to] += 1;
        _owners[tokenId] = to;

        emit Transfer(address(0), to, tokenId);
    }

    function _burn(uint256 tokenId) internal virtual {
        address owner = ownerOf(tokenId);

        _beforeTokenTransfer(owner, address(0), tokenId);

        // Clear approvals
        _approve(address(0), tokenId);

        _balances[owner] -= 1;
        delete _owners[tokenId];

        emit Transfer(owner, address(0), tokenId);
    }

    function _transfer(
        address from,
        address to,
        uint256 tokenId
    ) internal virtual {
        require(ownerOf(tokenId) == from, "ERC721: transfer of token that is not own");
        require(to != address(0), "ERC721: transfer to the zero address");

        _beforeTokenTransfer(from, to, tokenId);

        // Clear approvals from the previous owner
        _approve(address(0), tokenId);

        _balances[from] -= 1;
        _balances[to] += 1;
        _owners[tokenId] = to;

        emit Transfer(from, to, tokenId);
    }

    function _approve(address to, uint256 tokenId) internal virtual {
        _tokenApprovals[tokenId] = to;
        emit Approval(ownerOf(tokenId), to, tokenId);
    }

    function _checkOnERC721Received(
        address from,
        address to,
        uint256 tokenId,
        bytes memory _data
    ) private returns (bool) {
        if (to.isContract()) {
            try IERC721Receiver(to).onERC721Received(msg.sender, from, tokenId, _data) returns (bytes4 retval) {
                return retval == IERC721Receiver(to).onERC721Received.selector;
            } catch (bytes memory reason) {
                if (reason.length == 0) {
                    revert("ERC721: transfer to non ERC721Receiver implementer");
                } else {
                    assembly {
                        revert(add(32, reason), mload(reason))
                    }
                }
            }
        } else {
            return true;
        }
    }

    function onERC721Received(address _operator, address _from, uint256 _tokenId, bytes calldata _data) external returns(bytes4) {
        //InterfaceID=0x150b7a02
        return this.onERC721Received.selector;
    }

    function _beforeTokenTransfer(
        address from,
        address to,
        uint256 tokenId
    ) internal virtual {}

    function name() external view returns (string memory){
        return _name;
    }

    function symbol() external view returns (string memory){
        return _symbol;
    }

    function contractURI() public view returns (string memory) {
        return string(abi.encodePacked(_baseURI,"contract.json"));
    }

    //Provenance
    function setProvenanceHash(string calldata hash) public onlyOwner {
        provenanceHash = hash;
    }
    
    function setStartingIndex() public onlyOwner {
        require(startingIndex == 0, "Starting index is already set");
        require(startingIndexBlock != 0, "Starting index block must be set");

        startingIndex = uint256(blockhash(startingIndexBlock)) % totalNoodles;
        // Just a sanity case in the worst case if this function is called late (EVM only stores last 256 block hashes)
        if (block.number % startingIndexBlock > 255) {
            startingIndex = uint256(blockhash(block.number - 1)) % totalNoodles;
        }
        // Prevent default sequence
        if (startingIndex == 0) {
            startingIndex = startingIndex + 1;
        }
    }

    function setStartingIndexBlock() public onlyOwner {
        require(startingIndex == 0, "Starting index is already set");

        startingIndexBlock = block.number;
    }

    receive() external payable {}

    fallback() external payable {}
}
/**
 ____                        __   ___               __  __                      __   ___
 /\  _`\                     /\ \ /\_ \             /\ \/\ \                    /\ \ /\_ \
 \ \ \/\ \    ___     ___    \_\ \\//\ \      __    \ \ `\\ \    ___     ___    \_\ \\//\ \      __    ____
 \ \ \ \ \  / __`\  / __`\  /'_` \ \ \ \   /'__`\   \ \ , ` \  / __`\  / __`\  /'_` \ \ \ \   /'__`\ /',__\
 \ \ \_\ \/\ \L\ \/\ \L\ \/\ \L\ \ \_\ \_/\  __/    \ \ \`\ \/\ \L\ \/\ \L\ \/\ \L\ \ \_\ \_/\  __//\__, `\
 \ \____/\ \____/\ \____/\ \___,_\/\____\ \____\    \ \_\ \_\ \____/\ \____/\ \___,_\/\____\ \____\/\____/
 \/___/  \/___/  \/___/  \/__,_ /\/____/\/____/     \/_/\/_/\/___/  \/___/  \/__,_ /\/____/\/____/\/___/
 */
function setProvenanceHash(string calldata hash) public onlyOwner {
        provenanceHash = hash;
    }
//Provenance