contract BadBabies is ERC721Enumerable, Ownable{
    using Counters for Counters.Counter;
    using EnumerableSet for EnumerableSet.UintSet;
    using Strings for uint256;

    uint256 private _price = 0.04 ether;
    uint256 private _supply = 10000;
    uint256 private _whitelistPrice = 0.03 ether;
    uint256 private _whitelistDate;
    uint256 private _publicSaleDate;
    string private _uri;
    address private _authorizer = 0x7ACa8b1e3D01F52DF81bDD154319DB3D445cbD35;
    mapping (address => EnumerableSet.UintSet) private _ownerBabies;
    Counters.Counter private _babiesId;
    
    /**
        @dev Modifier check if address can buy, if public sale is open allows everyone if not checks whitelist
    */    
    constructor (string memory uri_) ERC721("Bad Babies", "BAD")  {
        _uri = uri_;
        _whitelistDate = 1639771200;
        _publicSaleDate = 1639771200 + 24 hours;
        _babiesId.increment();
    }
    
    /**
        @dev Buy and mint a new token
    */    
    function mintOnWhitelist(bytes memory signature) external payable {
        require(recov(keccak256(abi.encodePacked(msg.sender)), signature) == _authorizer, "Bad Babies: Invalid whitelist singature");
        require(msg.value >= _whitelistPrice, "Bad Babies: msg.value less than price");
        require(_whitelistDate <= block.timestamp, "Bad Babies: Mintin not allowed");
        require(_publicSaleDate > block.timestamp, "Bad Babies: Only public sale");
        mint_(msg.sender);
    }

    function recov(bytes32 hash, bytes memory signature) internal pure returns (address) {
        return ECDSA.recover(hash, signature);
    }

    function mint() external payable {
        require(_publicSaleDate <= block.timestamp, "Bad Babies: Only whitelist");
        require(msg.value >= _price, "Bad Babies: msg.value less than price");
        mint_(msg.sender);
    }

    function transfer(address to, uint256 tokenId) external {
        _transfer(msg.sender, to, tokenId);
    }

    /**
        @dev Mint new token and maps to receiver
        @param to Address of the receiver
    */      
    function mint_(address to) private {
        require(_babiesId.current() <= _supply, "Bad Babies: All NFTs minted");
        _ownerBabies[to].add(_babiesId.current());
        _mint(to, _babiesId.current());
        _babiesId.increment();
    }
    

    /**
        @dev Get token ID by owner index, useful for view owner tokens
        @param index Index in the owner mapping
        @return Token ID
    */     
    function getTokenByOwnerIndex(address owner, uint256 index) public view returns(uint256) {
        return _ownerBabies[owner].at(index);
    }


    /**
        @dev Overrides ERC721 function to change owner on _ownerBabies map
        @param from Param gived by transfer function
        @param to Param gived by transfer function
        @param tokenId Param gived by transfer function
    */     
    function _beforeTokenTransfer(
        address from,
        address to,
        uint256 tokenId
    ) override internal virtual {
        _ownerBabies[from].remove(tokenId);
        _ownerBabies[to].add(tokenId);
    }


    /**
        @dev Base URI getter
        @return Base URI
    */     
    function _baseURI() override internal view virtual returns (string memory) {
        return _uri;
    }

    /**
        @dev Get token URI by token ID, adds .json extension
        @return URI of the token metadata 
    */     
    function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
        require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token");
        string memory baseURI = _baseURI();
        return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, tokenId.toString(), ".json")) : "";
    }

    /*
     ██████╗ ██╗    ██╗███╗   ██╗███████╗██████╗ 
    ██╔═══██╗██║    ██║████╗  ██║██╔════╝██╔══██╗
    ██║   ██║██║ █╗ ██║██╔██╗ ██║█████╗  ██████╔╝
    ██║   ██║██║███╗██║██║╚██╗██║██╔══╝  ██╔══██╗
    ╚██████╔╝╚███╔███╔╝██║ ╚████║███████╗██║  ██║
     ╚═════╝  ╚══╝╚══╝ ╚═╝  ╚═══╝╚══════╝╚═╝  ╚═╝                               
    */

    /**
    @dev Mint a new token and gift it
    @param to Address that will receive the token
     */
    function gift(address to) external onlyOwner {
        mint_(to);
    }

    /**
    @dev Burn a token
    @param tokenId ID of the token to be burned
     */
    function burn(uint256 tokenId) external onlyOwner {
        require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721Burnable: caller is not owner nor approved");
        _burn(tokenId);
    }

    /**
    @dev Change Base URI can be used to reveal NFTs
    @param uri_ String of the new uri
     */
    function changeBaseURI(string memory uri_) external onlyOwner {
        _uri = uri_;
    }

    /**
    @dev Withdraw ETH balance to the owner
     */
    function withdraw() external onlyOwner {
        payable(owner()).transfer(address(this).balance);
    }

    /**
    @dev Decreases NFT supply.
    @param amount Amount to be reduced.
     */
    function decreaseSupply(uint256 amount) external onlyOwner {
        require((_supply-=amount) >= _babiesId.current());
        _supply-=amount;
    }

    /**
    @dev Change token price
    @param price_ New price for tokens (1e18)
     */
    function changePrice(uint256 price_) external onlyOwner {
        _price = price_;
    }
}
function gift(address to) external onlyOwner {
    mint_(to);
}

/**
 @dev Mint a new token and gift it
 @param to Address that will receive the token
 */