contract YoungBullClub is ERC721, Ownable, ReentrancyGuard  {
    using Counters for Counters.Counter;
    using Strings for uint256;

    Counters.Counter private _tokenSupply;

    string private baseURI;
    string public notRevealedUri;
    uint256 public preSaleCost = 0.08 ether;
    uint256 public cost = 0.16 ether;
    uint256 public maxSupply = 8888;
    uint256 public preSaleMaxSupply = 2222;
    uint256 public maxMintAmountPresale = 20;
    uint256 public maxMintAmount = 15;
    uint256 public nftPerAddressLimitPresale = 20;
    uint256 public nftPerAddressLimit = 50;
    bool public paused = false;
    bool public revealed = false;
    mapping(address => uint256) public addressMintedBalance;


    constructor() ERC721("YoungBullClub", "YBC") {
        _tokenSupply.increment();
        setNotRevealedURI("someurl/hidden.json");
    }

    function totalSupply() public view returns (uint256){
        return _tokenSupply.current();
    }
    
    //MODIFIERS
    modifier notPaused {
         require(!paused, "the contract is paused");
         _;
    }

    // INTERNAL
    function _baseURI() internal view virtual override returns (string memory) {
        return baseURI;
    }

    function mint(uint256 _mintAmount) public payable nonReentrant{
        uint256 supply = _tokenSupply.current();
        uint256 ownerMintedCount = addressMintedBalance[msg.sender];

        require(_mintAmount > 0, "need to mint at least 1 NFT");
        require(ownerMintedCount + _mintAmount <= nftPerAddressLimit,"max NFT per address exceeded");
        require(msg.value >= cost * _mintAmount, "insufficient funds for public sale");
        require(_mintAmount <= maxMintAmount,"max mint amount per transaction exceeded");
        require(supply + _mintAmount <= maxSupply, "max NFT limit exceeded");

        _safeMintYoungBull(_mintAmount, address(0));
    }

    function presalemint(uint256 _mintAmount) public payable nonReentrant returns (uint256){
        uint256 supply = _tokenSupply.current();
        uint256 ownerMintedCount = addressMintedBalance[msg.sender];

        require(_mintAmount > 0, "need to mint at least 1 NFT");
        require(ownerMintedCount + _mintAmount <= nftPerAddressLimitPresale, "max NFT per address exceeded for presale");
        require(msg.value >= preSaleCost * _mintAmount, "insufficient funds for presale");
        require(_mintAmount <= maxMintAmountPresale,"max mint amount per transaction exceeded");
        require(supply + _mintAmount <= preSaleMaxSupply,"max NFT presale limit exceeded");

        _safeMintYoungBull(_mintAmount, address(0));
        return _tokenSupply.current();
    }
    
    function gift(uint256 _mintAmount, address destination) public onlyOwner {
        uint256 supply = _tokenSupply.current();
        require(_mintAmount > 0, "need to mint at least 1 NFT");
        require(supply + _mintAmount <= maxSupply, "max NFT limit exceeded");
        _safeMintYoungBull(_mintAmount, destination);
    }

    function _safeMintYoungBull(uint256 _quantity, address destination) internal {
        require(_quantity > 0, "You must mint at least 1 Young Bull");
        require(_tokenSupply.current() + _quantity <= maxSupply, "This purchase would exceed max supply of Young Bulls");

        for (uint256 i = 0; i < _quantity; i++) {
            uint256 mintIndex = _tokenSupply.current();

            if (mintIndex < maxSupply) {
                _tokenSupply.increment();
                if(destination == address(0)){
                    _safeMint(msg.sender, mintIndex);
                }else{
                     _safeMint(destination, mintIndex);
                }
            }
        }
  } 

    function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
        require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token");
        if (!revealed) {
            return notRevealedUri;
        } else {
            string memory currentBaseURI = _baseURI();
            return string(abi.encodePacked(currentBaseURI,tokenId.toString(), ".json"));
        }
    }

    //ONLY OWNER VIEWS
    function getBaseURI() public view onlyOwner returns (string memory) {
        return baseURI;
    }

    //ONLY OWNER SETTERS
    function reveal() public onlyOwner {
        revealed = true;
    }

    function unreveal() public onlyOwner {
        revealed = false;
    }

    function pause(bool _state) public onlyOwner {
        paused = _state;
    }

    function setCost(uint256 _newCost) public onlyOwner {
        cost = _newCost;
    }
    
    function setmaxMintSupply(uint256 _newmaxMintSupply) public onlyOwner {
        maxSupply = _newmaxMintSupply;
    }

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

    function setNotRevealedURI(string memory _notRevealedURI) public onlyOwner {
        notRevealedUri = _notRevealedURI;
    }

    function withdraw() public payable onlyOwner {
        (bool success, ) = payable(msg.sender).call{value: address(this).balance}("");
        require(success);
    }
}
function reveal() public onlyOwner {
    revealed = true;
}

//ONLY OWNER SETTERS