contract Doodapes is ERC721Enumerable, Ownable {
  bool public paused = false;
  bool public revealed = false;
  uint256 public price = 0.1 ether;
  uint256 public supply = 9999;
  uint256 public transactionLimit = 10;
  uint256 public startPublic = 1644710400;
  uint256 public presalePrice = 0.07 ether;
  uint256 public transactionLimitPresale = 2;
  uint256 public limitPresale = 2;
  uint256 public startPresale = 1644613200;
  uint256 public endPresale = 1644699600;
  using Strings for uint256;
  string private baseURI;
  string public notRevealedUri;
  mapping(address => bool) WLAddresses;
  mapping(address => uint256) public mintedBalanceByAddress;

  constructor(
    string memory _name,
    string memory _symbol,
    string memory _notRevealedUri
  ) ERC721(_name, _symbol) {
    setNotRevealedURI(_notRevealedUri);
  }

  function send(uint256 _amount, address to) public onlyOwner {
    uint256 currentSupply = totalSupply();

    require(_amount > 0, "need to mint at least 1 NFT");
    require(currentSupply + _amount <= supply, "limit reached");

    for (uint256 i = 1; i <= _amount; i++) {
      _safeMint(to, currentSupply + i);
    }
  }

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

  function validationsPublic(uint256 _amount) internal {
    require(msg.value >= price * _amount, "not enough eth");
    require(_amount <= transactionLimit, "can't mint that amount");
  }

  function validationsPresale(uint256 _minted, uint256 _amount) internal {
    uint256 actualCost;
    block.timestamp < endPresale
      ? actualCost = presalePrice
      : actualCost = price;
    require(isWL(msg.sender), "not whitelisted");
    require(msg.value >= actualCost * _amount, "not enough eth");
    require(
      _amount <= transactionLimitPresale,
      "limit per transaction reached"
    );
    require(
      _minted + _amount <= limitPresale,
      "presale limit per address reached"
    );
  }

  function mint(uint256 _amount) public payable {
    require(!paused, "paused");
    require(_amount > 0, "mint minimum 1 NFT");
    require(block.timestamp >= startPresale, "not started");
    uint256 minted = mintedBalanceByAddress[msg.sender];
    uint256 currentSupply = totalSupply();

    block.timestamp < startPublic
      ? validationsPresale(minted, _amount)
      : validationsPublic(_amount);

    require(currentSupply + _amount <= supply, "limit reached");

    for (uint256 i = 1; i <= _amount; i++) {
      mintedBalanceByAddress[msg.sender]++;
      _safeMint(msg.sender, currentSupply + i);
    }
  }

  //Owner functions
  function setPrice(uint256 _price) public onlyOwner {
    price = _price;
  }

  function setLimitPresale(uint256 _newLimit) public onlyOwner {
    limitPresale = _newLimit;
  }

  function reveal() public onlyOwner {
    revealed = true;
  }

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

  function setPresalePrice(uint256 _newPrice) public onlyOwner {
    presalePrice = _newPrice;
  }

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

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

  function setTransactionLimit(uint256 _newTransactionLimit) public onlyOwner {
    transactionLimit = _newTransactionLimit;
  }

  function setTransactionLimitPresale(uint256 _newLimit) public onlyOwner {
    transactionLimitPresale = _newLimit;
  }

  function setSupply(uint256 _newSupply) public onlyOwner {
    supply = _newSupply;
  }

  function setEndPresale(uint256 _endPresale) public onlyOwner {
    endPresale = _endPresale;
  }

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

  function setStartPresale(uint256 _startPresale) public onlyOwner {
    startPresale = _startPresale;
  }

  function addToWL(address[] memory addresses) public onlyOwner {
    for (uint256 i = 0; i < addresses.length; i++) {
      WLAddresses[addresses[i]] = true;
    }
  }

  function setStartPublic(uint256 _startPublic) public onlyOwner {
    startPublic = _startPublic;
  }

  //Public
  function tokenURI(uint256 _Id)
    public
    view
    virtual
    override
    returns (string memory)
  {
    require(_exists(_Id), "token does not exist");

    if (!revealed) {
      return notRevealedUri;
    } else {
      string memory currentBaseURI = _baseURI();
      return
        bytes(currentBaseURI).length > 0
          ? string(abi.encodePacked(currentBaseURI, _Id.toString(), ".json"))
          : "";
    }
  }

  function walletOfAddress(address _address)
    public
    view
    returns (uint256[] memory)
  {
    uint256 ownerTokenCount = balanceOf(_address);
    uint256[] memory tokenIds = new uint256[](ownerTokenCount);
    for (uint256 i; i < ownerTokenCount; i++) {
      tokenIds[i] = tokenOfOwnerByIndex(_address, i);
    }
    return tokenIds;
  }

  function isWL(address _address) public view returns (bool) {
    return WLAddresses[_address];
  }

  function getCost() public view returns (uint256) {
    if (block.timestamp < endPresale) {
      return presalePrice;
    } else {
      return price;
    }
  }

  //Owner
  function contractBalance() public view onlyOwner returns (uint256) {
    return address(this).balance;
  }

  function getBaseURI() public view onlyOwner returns (string memory) {
    return baseURI;
  }
}
function tokenURI(uint256 _Id)
  public
  view
  virtual
  override
  returns (string memory)
{
  require(_exists(_Id), "token does not exist");

  if (!revealed) {
    return notRevealedUri;
  } else {
    string memory currentBaseURI = _baseURI();
    return
      bytes(currentBaseURI).length > 0
        ? string(abi.encodePacked(currentBaseURI, _Id.toString(), ".json"))
        : "";
  }
}

//Public