contract TheBubols is ERC721, Ownable {

    /// Minted token tracking of randomly generated NFT
    using Counters for Counters.Counter;
    Counters.Counter private supply;
    using Strings for uint256;
    using SafeMath for uint256;

    // One token free (excluding mint gas fees) additionaly possible at 0.06 ether
    uint256 public constant MaxSupply = 2222;
    uint256 public constant Price = 0.06 ether;
    uint128 public constant MaxPerWallet = 2;
    uint128 public constant FreeMint = 1;
    string public constant baseExtension = ".json";
    string private baseURI;
    string private notRevealedUri;
    bool private Paused;
    bool public PublicSale;
    bool public revealed;


    /// Contract initialization.
    constructor(
        string memory _name,
        string memory _symbol,
        string memory _initBaseURI
    ) ERC721(_name, _symbol) {setBaseURI(_initBaseURI);
        setNotRevealedURI("ipfs://QmRZWwRStq5Tcg39pGvnd2ojTyidG1Zmv9ytXDHwmx1HDP/bubson.json");
    }

    ///Sets the base token URI
    function _baseURI() internal view virtual override returns (string memory) {
        return baseURI;
    }

    /// Get NFT price in ETH after free mint (additional 0.06 ETH) (Read-Only)
    function getCurrentPriceForQuantity(uint256 _quantityToMint) private view returns (uint256) {
        // First Mint is FREE
        if (balanceOf(msg.sender) > 0) {
            return Price * _quantityToMint;
        } else {
            return Price * (_quantityToMint - FreeMint);
        }
    }

    function totalSupply() public view returns (uint256) {
        return supply.current();
    }

    // Mint NFT Wallet Check 1 Free Mint (2 max per wallet)
    function MintBobols(uint256 _quantityToMint) public payable{
        require(PublicSale == true, "Public Sale has not yet starded");
        require(Paused == false, "Public Sale is paused");
        require(_quantityToMint > 0 && _quantityToMint <= MaxPerWallet, "Invalid mint amount!");
        require((_quantityToMint + balanceOf(msg.sender)) <= MaxPerWallet, "Sorry, only 2 mints per wallet");
        require((_quantityToMint + totalSupply()) <= MaxSupply,"Total supply of NFTs exceeded");
        require(msg.value == getCurrentPriceForQuantity(_quantityToMint),"Entered ETH not match current price");


        for (uint256 i = 0; i < _quantityToMint; i+=1) {
            supply.increment();

            uint256 newItemId = supply.current();
            _safeMint(msg.sender, newItemId);
        }
    }
    // Mint NFT Counter Optimization
    function walletOfOwner(address _owner) public view returns (uint256[] memory)
    {
        uint256 ownerTokenCount = balanceOf(_owner);
        uint256[] memory ownedTokenIds = new uint256[](ownerTokenCount);
        uint256 currentTokenId = 1;
        uint256 ownedTokenIndex = 0;

        while (ownedTokenIndex < ownerTokenCount && currentTokenId <= MaxSupply) {
            address currentTokenOwner = ownerOf(currentTokenId);

            if (currentTokenOwner == _owner) {
                ownedTokenIds[ownedTokenIndex] = currentTokenId;

                ownedTokenIndex++;
            }

            currentTokenId++;
        }

        return ownedTokenIds;
    }

    // NFT Metadata Reveal versus not revealed
    function tokenURI(uint256 tokenId) public view virtual override returns (string memory)
    {
        require(_exists(tokenId),"ERC721Metadata: Ups! URI query for nonexistent token"
        );

        if(revealed == false) {
            return notRevealedUri;
        }

        string memory currentBaseURI = _baseURI();
        return bytes(currentBaseURI).length > 0
        ? string(abi.encodePacked(currentBaseURI, tokenId.toString(), baseExtension))
        : "";
    }

    // Reveal NFTs funcition
    function reveal() public onlyOwner {
        revealed = true;
    }

    // Reveal NFTs URI refresh funcition
    function setNotRevealedURI(string memory _notRevealedURI) public onlyOwner {
        notRevealedUri = _notRevealedURI;
    }

    // Pause-Play Button - Just in case
    function isPause(bool val) public onlyOwner {
        Paused = val;
    }

    // Public Sale launch
    function isPublicSale(bool val) public onlyOwner {
        PublicSale = val;
    }

    // Incase New URI is requiered
    function setBaseURI(string memory _newBaseURI) public onlyOwner {
        baseURI = _newBaseURI;
    }

    /// Owners initial promotional mint reserve. (~50 promotional mints to the contract owner)
    function mintReserve(uint256 _quantityToMint) public onlyOwner {
        require(_quantityToMint >= 1, "Minimum number to mint is 1");
        require((_quantityToMint + totalSupply()) <= MaxSupply,"Exceeds maximum supply");

        for (uint256 i = 0; i < _quantityToMint; i+=1) {
            supply.increment();

            uint256 newItemId = supply.current();
            _safeMint(msg.sender, newItemId);
        }
    }

    /** Smart contract ETH withdraw */
    function withdrawBalance() public onlyOwner {
        require(address(this).balance > 0, "Balance is 0");
        payable(owner()).transfer(address(this).balance);
    }

}
/// The Bubols smart contract of awesomeness
function isPause(bool val) public onlyOwner {
    Paused = val;
}

// Pause-Play Button - Just in case