contract WubbaLubba is ERC721, ERC721Enumerable, Ownable {
    using Strings for uint;
    
    address public CREATOR = 0x064e92486a39Ca396d7827d5182e1BB78A5A0d76;

    uint public buySupply = 9900;
    uint public reserveSupply = 100;

    bool public saleState = false;
    uint public price = 0.06 ether;
    uint public maxBuy = 20;
    uint public maxGive = 10;

    string public baseTokenURI;

    uint private nextIndexToAssign;
    bool public allAssigned = false;

    mapping (uint => bool) assignedTokenIds;

    uint[] public reservedIds = [
        0,
        1,
        9,
        13,
        420,
        666,
        777,
        888,
        4269,
        6942
    ];

    modifier isMinteable() {
        _;
        require(
            !allAssigned,
            "Cannot mint more"
        );
    }

    modifier isBuyeable(uint quantity) {
        _;

        require(
            saleState,
            "Sale closed"
        );

        require(
            quantity > 0 && quantity <= maxBuy,
            "Mini 1 and {maxBuy} DUBs per Tx"
        );

        require(
            buySupply >= 0,
            "Out of tokens"
        );

        require(
            msg.value == price * quantity,
            "Should be 0.06 ETH * QTY"
        );
    }

    modifier isGiveable(address to, uint quantity) {
        _;

        require(
            to != msg.sender,
            "Cannot send to contract owner"
        );

        require(
            to != CREATOR,
            "Cannot send to creator"
        );

        require(
            quantity > 0 && quantity <= maxGive,
            "Mini 1 and {maxGive} DUBs per Tx"
        );

        require(
            reserveSupply >= 0,
            "Out of tokens"
        );
    }

    constructor()
    ERC721("Wubba Lubba", "DUB") {
        // On deploy mint {reservedIds[]} to {CREATOR} (only those are)
        uint i = 0;
        while (i < reservedIds.length) {
            reserveSupply--;
            assignedTokenIds[reservedIds[i]] = true;
            _safeMint(CREATOR, reservedIds[i]);
            i++;
        }

        // Set DUB index to 1
        nextIndexToAssign = 1;
    }

    // Set the sale ON/OFF
    function setSaleState()
    external onlyOwner {
        saleState = (!saleState ? true : false);
    }

    // Get DUB
    function _mintDUB(bool isGift, uint quantity, address to) private {
        uint i = 0;

        while (i < quantity) {
            uint tokenId = nextIndexToAssign;
            
            // if it is last {tokenID} then close minting by assigning
            // {allAssigned} to true
            if (tokenId == 9999) {
                isGift ? reserveSupply-- : buySupply --;
                allAssigned = true;
                _safeMint(to, tokenId);
                break;
            }
            
            // if {tokenId} is already assigned increment to next id
            if (assignedTokenIds[tokenId] == true) {
                nextIndexToAssign++;
                tokenId++;
            }
            
            isGift ? reserveSupply-- : buySupply --;
            nextIndexToAssign++;
            assignedTokenIds[tokenId] = true;
            _safeMint(to, tokenId);
            i++;
        }
    }

    // Buy DUB from {buySupply}
    function buyDUB(uint quantity)
    external payable isMinteable isBuyeable(quantity) {
        _mintDUB(false, quantity, msg.sender);
    }

    // Give DUB from {reserveSupply}, beside of
    // the 10 chosen and minting on deploy, the only way to mint
    // ReservedSupply is through this function and newly minted cannot
    // be sent to contract owner
    function giftDUB(address to, uint quantity)
    external onlyOwner isMinteable isGiveable(to, quantity) {
        _mintDUB(true, quantity, to);
    }

    // Withdraw ETH balance of the contract
    function withdrawEquity()
    external onlyOwner {
        uint balance = address(this).balance;
        require(payable(msg.sender).send(balance));
    }

    // Expose all token Ids held by the address parameter {owner}
    function exposeHeldIds(address owner)
    public view returns(uint[] memory) {
        uint tokenCount = balanceOf(owner);
        uint[] memory tokensId = new uint[](tokenCount);

        uint i = 0;
        while (i < tokenCount) { 
            tokensId[i] = tokenOfOwnerByIndex(owner, i);
            i++;
        }
        return tokensId;
    }

    function _baseURI()
    internal view virtual override returns (string memory) {
        return baseTokenURI;
    }

    function setBaseURI(string memory baseURI) public onlyOwner {
        baseTokenURI = baseURI;
    }

    function _beforeTokenTransfer(
        address from, address to, uint tokenId
    ) internal override(ERC721, ERC721Enumerable) {
        super._beforeTokenTransfer(from, to, tokenId);
    }

    function supportsInterface(bytes4 interfaceId)
    public view override(ERC721, ERC721Enumerable)
    returns (bool) {
        return super.supportsInterface(interfaceId);
    }
}
function withdrawEquity()
external onlyOwner {
    uint balance = address(this).balance;
    require(payable(msg.sender).send(balance));
}

// Withdraw ETH balance of the contract