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

  string public baseURI;
  string public baseExtension = '.json';

  uint256 public cost = 0.038 ether;
  uint256 public maxSupply = 8888;
  uint256 public giveawayReserve = 200;
  //only applies to mainSale
  uint256 public maxMintAmount = 15;
  //only applies to presale
  uint256 public presaleMaxPerAddress = 5;

  bool public paused = false;
  bool public presaleOpen = false;
  bool public mainSaleOpen = false;

  mapping(address => uint256) public presaleWhitelist;

  constructor(string memory _initBaseURI) ERC721('SunnySloths', 'SNS') {
    setBaseURI(_initBaseURI);
  }

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

  // public
  function mint(uint256 _mintAmount) public payable {
    require(!paused, 'Contract is paused');
    require(mainSaleOpen, 'Main sale not open');
    uint256 supply = totalSupply();
    require(_mintAmount > 0, 'Mint at least 1 NFT');
    require(supply + _mintAmount <= maxSupply - giveawayReserve, 'Sold out!');

    if (msg.sender != owner()) {
      require(_mintAmount <= maxMintAmount, 'Exceed max quantity per mint');
      require(msg.value >= cost * _mintAmount, 'Insufficient funds');
    }

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

  function mintPresale(uint256 _mintAmount) public payable {
    require(!paused, 'Contract is paused');
    require(presaleOpen, 'Presale not open');
    require(_mintAmount > 0, 'Mint at least 1 NFT');
    require(
      _mintAmount <= presaleWhitelist[msg.sender],
      'Not in whitelist / exceeded presale limit'
    );
    uint256 supply = totalSupply();
    require(supply + _mintAmount <= maxSupply - giveawayReserve, 'Sold out!');
    require(msg.value >= cost * _mintAmount, 'Insufficient funds');

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

    presaleWhitelist[msg.sender] = presaleWhitelist[msg.sender] - _mintAmount;
  }

  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'
    );

    string memory currentBaseURI = _baseURI();
    return
      bytes(currentBaseURI).length > 0
        ? string(
          abi.encodePacked(currentBaseURI, tokenId.toString(), baseExtension)
        )
        : '';
  }

  // only Owner
  function burn(uint256 _tokenId) public onlyOwner {
    _burn(_tokenId);
  }

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

  function setMaxMintAmount(uint256 _newMaxMintAmount) public onlyOwner {
    maxMintAmount = _newMaxMintAmount;
  }

  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 setPresaleOpen(bool _state) public onlyOwner {
    presaleOpen = _state;
  }

  function setMainSaleOpen(bool _state) public onlyOwner {
    mainSaleOpen = _state;
  }

  function whitelistUsers(address[] memory _users) external onlyOwner {
    for (uint256 i = 0; i < _users.length; i++) {
      presaleWhitelist[_users[i]] = presaleMaxPerAddress;
    }
  }

  function giveAway(address _to, uint256 _amount) external onlyOwner {
    require(_amount <= giveawayReserve, 'Exceeded reserved giveaway supply');

    uint256 supply = totalSupply();
    for (uint256 i = 1; i <= _amount; i++) {
      _safeMint(_to, supply + i);
    }

    giveawayReserve -= _amount;
  }

  function withdraw() public payable onlyOwner {
    (bool success, ) = payable(msg.sender).call{ value: address(this).balance }(
      ''
    );
    require(success);
  }
}
function mint(uint256 _mintAmount) public payable {
  require(!paused, 'Contract is paused');
  require(mainSaleOpen, 'Main sale not open');
  uint256 supply = totalSupply();
  require(_mintAmount > 0, 'Mint at least 1 NFT');
  require(supply + _mintAmount <= maxSupply - giveawayReserve, 'Sold out!');

  if (msg.sender != owner()) {
    require(_mintAmount <= maxMintAmount, 'Exceed max quantity per mint');
    require(msg.value >= cost * _mintAmount, 'Insufficient funds');
  }

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

// public