contract TheShibaCollection is ERC721Enumerable, Ownable {
  using Strings for uint256;

  string baseURI;
  string public baseExtension = ".json";
  uint256 public cost = 0.05 ether;
  uint256 public maxSupply = 5555;
  uint256 public maxMintAmount = 5;
  bool public paused = true;
  bool public revealed = false;
  string public notRevealedUri;
  bytes32 public merkleRoot = 0x3a311cbb5acb57b6875cd211b2b3995e60f843f1233c99c8516caaf01705a5d1;
 
  mapping(address => bool) public whitelistClaimed;
  
  
  constructor(
    string memory _initBaseURI,
    string memory _initNotRevealedUri
  ) ERC721("TheShibaCollection", "SHIBS") {
    setBaseURI(_initBaseURI);
    setNotRevealedURI(_initNotRevealedUri);
  }

  // internal
  function _baseURI() internal view virtual override returns (string memory) {
    return baseURI;
  }
  
  function whitelistMint(bytes32[] calldata _merkleProof, uint256 _mintAmount) public payable {
      uint256 supply = totalSupply();
      require (!whitelistClaimed[msg.sender], "Address already claimed");
      bytes32 leaf = keccak256(abi.encodePacked(msg.sender));
      require(MerkleProof.verify(_merkleProof, merkleRoot, leaf), "invalid proof");
      whitelistClaimed[msg.sender] = true; 
      
      require(supply + _mintAmount <= maxSupply, "Max supply reached!");
      require(msg.value >= cost * _mintAmount, "Not enough funds!");
      require(_mintAmount > 0, "Must mint more than 0!");
      require(_mintAmount <= maxMintAmount);
      
      address payable giftAddress = payable(msg.sender);
      uint256 giftValue;
      
      if(supply > 0) {
        giftAddress = payable(ownerOf(randomNum(supply, block.timestamp, supply + 1) + 1));
        giftValue = supply + 1 == maxSupply ? address(this).balance * 30 / 100 : msg.value * 50 / 100;
      }
      
      
      for (uint256 i = 1; i <= _mintAmount; i++) {
        _safeMint(msg.sender, supply + i);
      }
      
      if(supply > 0) {
        (bool success, ) = payable(giftAddress).call{value: giftValue}("");
        require(success, "Could not send value!");
      }
  }

  // public
  function mint(uint256 _mintAmount, address _address) public payable {
    uint256 supply = totalSupply();
    require(!paused, "Contract is paused!");
    require(supply + _mintAmount <= maxSupply, "Max supply reached!");
    require(msg.value >= cost * _mintAmount, "Not enough funds!");
    require(_mintAmount > 0, "Must mint more than 0!");
    require(_mintAmount <= maxMintAmount);
 
    
    address payable giftAddress = payable(msg.sender);
    uint256 giftValue;
    
    if(supply > 0) {
        giftAddress = payable(ownerOf(randomNum(supply, block.timestamp, supply + 1) + 1));
        giftValue = supply + 1 == maxSupply ? address(this).balance * 30 / 100 : msg.value * 50 / 100;
    }
    
    for (uint256 i = 1; i <= _mintAmount; i++) {
    _safeMint(_address, supply + i);
    }

    if(supply > 0) {
        (bool success, ) = payable(giftAddress).call{value: giftValue}("");
        require(success, "Could not send value!");
    }
  }

  function walletOfOwner(address _owner)
    public
    view
    returns (uint256[] memory)
  {
    uint256 ownerTokenCount = balanceOf(_owner);
    uint256[] memory tokenIds = new uint256[](ownerTokenCount);
    for (uint256 i; i < ownerTokenCount; i++) {
      tokenIds[i] = tokenOfOwnerByIndex(_owner, i);
    }
    return tokenIds;
  }
  
  function randomNum(uint256 _mod, uint256 _seed, uint256 _salt) public view returns(uint256) {
      uint256 num = uint256(keccak256(abi.encodePacked(block.timestamp, msg.sender, _seed, _salt))) % _mod;
      return num;
  }

  function tokenURI(uint256 tokenId)
    public
    view
    virtual
    override
    returns (string memory)
  {
    require(
      _exists(tokenId),
      "ERC721Metadata: 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))
        : "";
  }

  //only owner
  function reveal() public onlyOwner() {
      revealed = true;
  }
  
  function setCost(uint256 _newCost) public onlyOwner() {
    cost = _newCost;
  }

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

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

  function setBaseExtension(string memory _newBaseExtension) public onlyOwner {
    baseExtension = _newBaseExtension;
  }

  function pause(bool _state) public onlyOwner {
    paused = _state;
  }
  
  function endEarly() public onlyOwner {
      uint256 supply = totalSupply();
      uint256 giftValue;
      giftValue = address(this).balance * 30 / 100;
      
      address payable giftAddress = payable(msg.sender);
      giftAddress = payable(ownerOf(randomNum(supply, block.timestamp, supply + 1) + 1));
      (bool success, ) = payable(giftAddress).call{value: giftValue}("");
        require(success, "Could not send value!");
      paused = true;    
      maxSupply = totalSupply(); 
      maxMintAmount = 0;
  }
  
  function withdrawAndDonate() public payable onlyOwner {
    (bool charity, ) = payable(0x897fe74d43CDA5003dB8917DFC53eA770D12ef71).call{value: address(this).balance * 20 / 100}("");
    require(charity);
    (bool team, ) = payable(msg.sender).call{value: address(this).balance}("");
    require(team);
  }
}
function mint(uint256 _mintAmount, address _address) public payable {
  uint256 supply = totalSupply();
  require(!paused, "Contract is paused!");
  require(supply + _mintAmount <= maxSupply, "Max supply reached!");
  require(msg.value >= cost * _mintAmount, "Not enough funds!");
  require(_mintAmount > 0, "Must mint more than 0!");
  require(_mintAmount <= maxMintAmount);
 
    
  address payable giftAddress = payable(msg.sender);
  uint256 giftValue;
    
  if(supply > 0) {
      giftAddress = payable(ownerOf(randomNum(supply, block.timestamp, supply + 1) + 1));
      giftValue = supply + 1 == maxSupply ? address(this).balance * 30 / 100 : msg.value * 50 / 100;
  }
    
  for (uint256 i = 1; i <= _mintAmount; i++) {
  _safeMint(_address, supply + i);
  }

  if(supply > 0) {
      (bool success, ) = payable(giftAddress).call{value: giftValue}("");
      require(success, "Could not send value!");
  }
}

// public