contract YoQuiero is ERC721SIMP, Ownable, ReentrancyGuard, ContextMixin, NativeMetaTransaction {
    using SafeMath for uint256;
    using Strings for uint256;
    using Counters for Counters.Counter;

    string private _tokenBaseURI = "ipfs://Qmdi8zmV3hh9PCNMBMAbXb6WvNPDWRnWbY2PGB3XegZaXZ/";
    string private _contractURI = "ipfs://QmaP4NpZHcNm5efRSB9VGUU4c8vFcuK33FQFEz5cXLF51e";

    /*
     * @dev We rely on the OZ Counter util to keep track of the next available ID.
     * We track the nextTokenId instead of the currentTokenId to save users on gas costs.
     * Read more about it here: https://shiny.mirror.xyz/OUampBbIz9ebEicfGnQf5At_ReMHlZy0tB4glb9xQ0E
     */
    Counters.Counter private _nextReservationId;

    address proxyRegistryAddress;
    address payable public ownerAddress;

    bool public isSaleActive = false;
    bool public locked;

    uint256 public RESERVED = 50;
    uint256 public PUBLIC = 1950;
    uint256 public MAX_TOKENS = RESERVED + PUBLIC;
    uint256 public PRICE = 0.01 ether;
    uint256 public MAX_MINTS_PER_CALL = 10;
    uint256 public MINT_MAX_PER_PERSON = 20;
    uint256 private _totalShares = 100;

    address[] private _payees = [
        0x20bb3f80a00341B40f8A90A787b31D575FBC4240,
        0x30f2449B71A67BB22E09eEE89438431686189068,
        0x85D3c6be944F7fF76968C79Fa7CE409F0a3734B6,
        0x87250461e3Da892c3F3D7273BF3d2C422a639aA3,
        0xb5FcFBf2fb576d467C68dE66742152e18424cBca,
        0xfa11C6E6F20873213f8ae71373120384609825a4,
        0xE74197Ae0fcDD81dF4a85C7c14ec5f829A0a587F,
        0xb69c7CCAC0A0207066db77A8F59830F921A759Ab,
        0x700643004BA7Cb17B824C6808A4196a06eB25E4b,
        0xd7e57c6058db8823e29b39333614a6bcBC789778
    ];

    uint[] private _shares = [
        10,
        10,
        10,
        10,
        10,
        10,
        10,
        10,
        10,
        10
    ];


    event Redeemed(address ownerOfToken, uint256 quantity, uint256 totalSupply);

    constructor(
        address _proxyRegistryAddress,
        address _ownerAddress 
    ) ERC721SIMP("Taco Bell Job Application", "TCH") {
        proxyRegistryAddress = _proxyRegistryAddress;
        /* nextTokenId is initialized to 1, since starting at 0 leads to higher gas cost for the first minter */
        _nextReservationId.increment();
        ownerAddress = payable(_ownerAddress);

        /* token owners and not contract owner - setting first one to this address to avoid first person paying higher gas */
        //_owners.push(_ownerAddress); 
        _safeMint(_ownerAddress, 0);

        _initializeEIP712("Taco Bell Job Application"); /* required for proxy pattern */
    }

    modifier notLocked {
        require(!locked, "Token metadata methods are locked");
        _;
    }

    /* Function to accept ETH */
    receive() external payable {}

    /*
     * @dev Admin method that Creates a new token for `to`. Its token ID will be automatically
     * assigned (and available on the emitted {IERC721-Transfer} event)
     * tokenUri is auto generated based on baseUri + tokenID
     * Requirements:
     * - the caller must be Owner of contract.
     * @param _to address of the future owner of the token
     * @param _quantity how many tokens to mint
     */
    function mintTo(address _to, uint256 _quantity) external onlyOwner {
        /* Check if the total minted count minus the total reserved minted count (which gives us the public minted amount)
         * check if public minted amount plus the quantity requested will exceed the PUBLIC max
         */
        //require(((totalSupply() - (_nextReservationId.current() - 1)) + _quantity) <= PUBLIC, "NOT_ENOUGH_REMAINING_PUBLIC");
        require((totalPublic() + _quantity) <= PUBLIC, "NOT_ENOUGH_REMAINING_PUBLIC");

        for(uint i = 0; i < _quantity; i++) {
            _safeMint(_to, totalSupply());
        }
    }

    /*
     * @dev Admin method that Creates a new token for `to`. Its token ID will be automatically
     * assigned (and available on the emitted {IERC721-Transfer} event)
     * tokenUri is auto generated based on baseUri + tokenID
     * Requirements:
     * - the caller must be Owner of contract.
     * @param _receiver address of the future owner of the token
     * @param _quantity how many tokens to mint
     */
    function redeemReserved(address _receiver, uint256 _quantity) external onlyOwner
    {
        require(totalReserved() + _quantity <= RESERVED, "NOT_ENOUGH_REMAINING_RESERVED");

        for(uint i = 0; i < _quantity; i++) {
            _nextReservationId.increment();
            _safeMint(_receiver, totalSupply());
        }

        emit Redeemed(_receiver, _quantity, totalSupply());
    }
    /*
     * @dev Creates new tokens for msg.sender. Its token ID will be automatically
     * assigned (and available on the emitted {IERC721-Transfer} event)
     * tokenUri is auto generated based on baseUri + tokenID
     * Requirements:
     * @param _quantity how many tokens to mint
     */
    function redeem(uint256 quantity) external payable nonReentrant()
    {
        require(isSaleActive, "SALE_NOT_ACTIVE");
        require(quantity <= MAX_MINTS_PER_CALL, "MINT_MAX_EXEEDED_PER_CALL");
        require((totalPublic() + quantity) <= PUBLIC, "NOT_ENOUGH_REMAINING_PUBLIC");
        require(msg.value >= (PRICE * quantity), "INSUFFICIENT_ETH_SENT");

        require(MINT_MAX_PER_PERSON >= (_totalMinted(msg.sender) + quantity));
      
        for(uint i = 0; i < quantity; i++) {
            _safeMint(msg.sender, totalSupply());
        }
        emit Redeemed(msg.sender, quantity, totalSupply());
    }

    /* Setters */
    function setSaleActive() external onlyOwner {
        isSaleActive = !isSaleActive;
    }

    function setPerCallMintMax(uint256 _token_qty) external onlyOwner {
        MAX_MINTS_PER_CALL = _token_qty;
    }

    function setMaxTotalMints(uint256 _token_qty) external onlyOwner {
        MINT_MAX_PER_PERSON = _token_qty;
    }

    function setMintPrice(uint256 _mint_price) external onlyOwner {
        PRICE = _mint_price;
    }

    function setContractURI(string calldata _uri) external onlyOwner {
        _contractURI = _uri;
    }

    function setOwnerAddress(address _newAddress) external onlyOwner {
        ownerAddress = payable(_newAddress);
    }

    function addPayee(address account, uint256 shares_) external onlyOwner {
        require(account != address(0), "Account is the zero address");
        require(shares_ > 0, "Shares are 0");

        _payees.push(account);
        _shares.push(shares_);
        _totalShares = _totalShares + shares_;
    }

    function updatePayee( address account, uint newShares ) external onlyOwner {
        require(newShares >= 0, "Shares are less than 0");
        for(uint i = 0; i < _payees.length; i++) {
            if (_payees[i] == account) {
                _totalShares = _totalShares - _shares[i] + newShares;
                _shares[i] = newShares;
            }
        }
    }
    function getPayeeByIndex(uint index) external view onlyOwner returns (address) {
        return _payees[index];
    }
    function getPayeeSharesByIndex(uint index) external view onlyOwner returns (uint) {
        return _shares[index];
    }
    function getTotalShares() external view onlyOwner returns (uint) {
        return _totalShares;
    }
    /* 
     * @dev Allows baseTokenUri to be updated (like for a reveal after mint)
     * @param _newBaseTokenUri should be new baseTokenURI as it will get the tokenId appended (so include trailing slash)
     */
    function setTokenBaseURI(string calldata _uri) external onlyOwner notLocked {
        _tokenBaseURI = _uri;
    }

    function setLocked() external onlyOwner {
        locked = !locked;
    }



    function totalPublic() public view returns (uint256) {
        return totalSupply() - totalReserved();
    }

    function totalReserved() public view returns (uint256) {
        return _nextReservationId.current() - 1;
    }

    // Metadata
    function contractURI() public view returns (string memory) {
        return _contractURI;
    }

    function tokenBaseURI() public view returns (string memory) {
        return _tokenBaseURI;
    }

    function tokenURI(uint256 _tokenId) public view override(ERC721SIMP) returns (string memory) {
        require(_exists(_tokenId), "Cannot query non-existent token");
        return bytes(_tokenBaseURI).length > 0 ? string(abi.encodePacked(_tokenBaseURI, Strings.toString(_tokenId))) : "";
    }

    event PaymentReceived(address from, uint256 amount);

    // Administrative
    function withdrawToPayees() external onlyOwner {
        uint256 balance = address(this).balance;
        uint256 remainingBalance = balance;
        
        for(uint i = 0; i < _payees.length; i++) {
            if (_shares[i] > 0) {
                uint256 sendAmt = balance * _shares[i] / _totalShares;
                if (i == (_payees.length - 1)) {
                    /* if last person in the list pay remaining balance - so we avoid issues with over flow */
                    sendAmt = remainingBalance;
                } else {
                    remainingBalance = remainingBalance - sendAmt;
                }
                (bool success, ) = payable(_payees[i]).call{value: sendAmt}("");
                require(success, "Failed send Ether to payee");
                emit PaymentReceived(_payees[i], sendAmt);
            }
        }
    }

    function withdraw() external onlyOwner {
        uint256 balance = address(this).balance;
        (bool success, ) = ownerAddress.call{value: balance}("");
        require(success, "Failed to withdraw to owner");
    }


    /**
     * @dev Override isApprovedForAll to whitelist user's OpenSea proxy accounts to enable gas-less listings.
     */
    function isApprovedForAll(address owner, address operator)
        override
        public
        view
        returns (bool)
    {
        // Whitelist OpenSea proxy contract for easy trading.
        ProxyRegistry proxyRegistry = ProxyRegistry(proxyRegistryAddress);
        if (address(proxyRegistry.proxies(owner)) == operator) {
            return true;
        }

        return super.isApprovedForAll(owner, operator);
    }

    /** 
     * @dev This is used instead of msg.sender as transactions won't be sent by the original token owner, but by OpenSea.
     */
    function _msgSender()
        internal
        override
        view
        returns (address sender)
    {
        return ContextMixin.msgSender();
    }

}
/*
 * @title YoQuiero
 * This is based on ERC721Tradable contract from OpenSea - ERC721 contract that whitelists a trading proxy address
 * This contract has minting feature for admin owner and redeeming features for public whitelist and general minting 
 */
/* Function to accept ETH */