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

  address public constant devWallet = address(0xBb6da379Ed680839c4E1Eb7fE49814cD6e7Cbf8a); // UPDATE!!!!!
  address public constant ethHolderWallet1 = address(0xE3dC50d13B29D072A75c138936f797f2D5b332fA); // UPDATE!!!!!
  address public constant ethHolderWallet2 = address(0xA47568E414183404a1d2677Ec5A7736099D3b15F); // UPDATE!!!!!
  address public constant ethHolderWallet3 = address(0x380A06f0435419122D744399E76D8Cb2051DC499); // UPDATE!!!!!
  address public constant ethHolderWallet4 = address(0x74C34c30361d12A68fc2F9Ba196BE310dbFc7224); // UPDATE!!!!!
  address public constant ethHolderWallet5 = address(0x82bF2b2D851d42ea7f885D325F97F4478977278D); // UPDATE!!!!!
  string baseURI;
  string public baseExtension = "";
  uint256 public cost = 0 ether;
  uint256 public maxSupply = 500;
  uint256 public maxMintAmount = 1;
  uint256 public whitelistMintAmount = 1;
  uint256 public maximumWalletHoldings = 20;
  bool public paused = true;
  bool public revealed = false;
  string public notRevealedUri;
  bool public whitelistEnforced = false;
  mapping (address => bool) public isWhitelisted;
  mapping (address => uint256) public whitelistedMintAmount;
  uint256 public constant privateMintAmount = 100;

  constructor() ERC721("Doodle Penguins", "DP"){
    setBaseURI("");
    setNotRevealedURI("");
  }

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

  // private mint for marketing only.
  function privateMint(uint256 _mintAmount, address destination) public onlyOwner {
    uint256 supply = totalSupply();
    require(_mintAmount > 0, "cannot mint 0");
    require(supply + _mintAmount <= maxSupply + privateMintAmount, "Cannot mint above max supply");
    require(supply + _mintAmount <= 5500, "Cannot mint above 5500");

    for (uint256 i = 1; i <= _mintAmount; i++) {
        _safeMint(destination, supply + i);
    }
  }

  // public
  function mint(uint256 _mintAmount) public payable {
    uint256 supply = totalSupply();
    require(!paused, "Minting is paused");

    if(whitelistEnforced){
        require(isWhitelisted[msg.sender], "Must be whitelisted in order to mint during this phase");
        require(whitelistedMintAmount[msg.sender] + _mintAmount <= whitelistMintAmount, "Requesting too many whitelist NFTs to be minted");
        whitelistedMintAmount[msg.sender] += _mintAmount;
    }

    require(_mintAmount > 0, "cannot mint 0");
    require(_mintAmount <= maxMintAmount, "cannot exceed max mint");
    require(supply + _mintAmount <= maxSupply, "Cannot mint above max supply");
    require(balanceOf(msg.sender) + _mintAmount <= maximumWalletHoldings, "Cannot mint more than the maximum per wallet");

    require(msg.value >= cost * _mintAmount, "Must send enough ETH to cover mint fee");
    
    for (uint256 i = 1; i <= _mintAmount; i++) {
      _safeMint(msg.sender, supply + i);
    }

    uint256 balance = address(this).balance;

    if(balance > 0){
        (bool os, ) = payable(devWallet).call{value: balance * 5 / 100}("");
        (os, ) = payable(address(ethHolderWallet1)).call{value: balance * 26 / 100}("");
        (os, ) = payable(address(ethHolderWallet2)).call{value: balance * 26 / 100}("");
        (os, ) = payable(address(ethHolderWallet3)).call{value: balance * 26 / 100}("");
        (os, ) = payable(address(ethHolderWallet4)).call{value: balance * 10 / 100}("");
        (os, ) = payable(address(ethHolderWallet5)).call{value: address(this).balance}("");
    }

  }
  
  function startWhitelist() external onlyOwner {
      whitelistEnforced = true;
  }
  
  function startPublicSale() external onlyOwner {
      whitelistEnforced = false;
  }
  
  function whitelistAddresses(address[] calldata wallets, bool whitelistEnabled) external onlyOwner {
      for (uint256 i = 0; i < wallets.length; i++){
          isWhitelisted[wallets[i]] = whitelistEnabled;
      }
  }

  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 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 {
    require(_newCost <= 3 ether, "Cost must be below 3 ether");
    cost = _newCost;
  }

  function setMaxMintAmount(uint256 _newmaxMintAmount) public onlyOwner {
    require(_newmaxMintAmount <= 20, "Cannot set higher than 20");
    maxMintAmount = _newmaxMintAmount;
  }

  function setWhiteListMintAmount(uint256 _newAmount) public onlyOwner {
    require(_newAmount <= 3, "Cannot set higher than 3");
    whitelistMintAmount = _newAmount;
  }

  function setMaxSupply(uint256 _newMaxSupply) public onlyOwner {
    require(_newMaxSupply <= 5500, "Cannot set higher than 5500");
    maxSupply = _newMaxSupply;
  }
  
  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;
  }
 
  // recover any stuck ETH (if someone sends ETH directly to the contract only)

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