contract PartyPunksPrivateClub is ERC721Enumerable, Ownable {  
    using Address for address;
    
    // Sale state
    bool public saleIsActive = false;

    // Reserved for the team, customs, giveaways, collabs and so on.
    uint256 public reserved = 83;

    // Price of each token
    uint256 public price = 0.03 ether;

    // Maximum limit of tokens that can ever exist
    uint256 public MAX_SUPPLY = 3333;
    
    uint256 public nftPerAddressLimit = 10;
    
    uint256 public nftPerTxLimit = 5;

    // The base link that leads to the image / video of the token
    string public baseTokenURI;

    // Withdraw address
    address withdrawAddress = 0x7DDe2d86A5d110ec0e8Bd93655604B1BDF6fd6F3;
    
    mapping(address => uint256) public addressMintedBalance;

    constructor (string memory newBaseURI) ERC721 ("Party Punks Private Club", "PartyPPClub") {
        setBaseURI(newBaseURI);
    }

    // Override so the openzeppelin tokenURI() method will use this method to create the full tokenURI instead
    function _baseURI() internal view virtual override returns (string memory) {
        return baseTokenURI;
    }

    // See which address owns which tokens
    function tokensOfOwner(address addr) public view returns(uint256[] memory) {
        uint256 tokenCount = balanceOf(addr);
        uint256[] memory tokensId = new uint256[](tokenCount);
        for(uint256 i; i < tokenCount; i++){
            tokensId[i] = tokenOfOwnerByIndex(addr, i);
        }
        return tokensId;
    }

    // Standard mint function
    function mint(uint256 _amount) public payable {
        uint256 supply = totalSupply();
        require( saleIsActive,                     "Sale isn't active" );
        require( _amount > 0 && _amount <= nftPerTxLimit,     "Can only mint between 1 and 5 tokens at once" );
        require( (supply + _amount) <= (MAX_SUPPLY - reserved), "Purchase would exceed maximum supply" );
        uint256 ownerMintedCount = addressMintedBalance[msg.sender];
            require(ownerMintedCount + _amount <= nftPerAddressLimit, "max NFT per address exceeded");
        require( msg.value == price * _amount,   "Wrong amount of ETH sent" );
        for(uint256 i; i < _amount; i++){
            addressMintedBalance[msg.sender]++;
            _safeMint( msg.sender, supply + i );
        }
    }
    
    // Admin minting function to reserve tokens for the team, collabs, customs and giveaways
    function mintReserved(address _to, uint256 _amount) public onlyOwner {
        // Limited to a publicly set amount
        require( _amount <= reserved, "Can't reserve more than set amount" );
        reserved -= _amount;
        uint256 supply = totalSupply();
        for(uint256 i; i < _amount; i++){
            _safeMint( _to, supply + i );
        }
    }

    /*
     *  Sale state
     */
    function pauseSale() public onlyOwner() {
        require(saleIsActive == true, "Sale is already paused");
        saleIsActive = false;
    }

    function startSale() public onlyOwner() {
        require(saleIsActive == false, "Sale has already started");
        saleIsActive = true;
    }
    
    function setMaxSupply(uint256 _maxsupply) public onlyOwner() {
        MAX_SUPPLY = _maxsupply;
    }
    
    function setReserved(uint256 _reserved) public onlyOwner() {
        reserved = _reserved;
    }

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

    // Set a different price in case ETH changes drastically
    function setPrice(uint256 newPrice) public onlyOwner {
        price = newPrice;
    }
    
    /*
     *  Withdraw
     */
    function withdraw() public onlyOwner {
        uint balance = address(this).balance;
        require(payable(withdrawAddress).send(balance));
    }
}
function setBaseURI(string memory baseURI) public onlyOwner {
    baseTokenURI = baseURI;
}

// Set new baseURI