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

  Counters.Counter private supply;

  string public baseURI;
  string public baseExtension = ".json";
  string public notRevealedUri;
  uint256 public preSaleCost = 0.08 ether;
  uint256 public cost = 0.08 ether;
  uint256 public maxSupply = 6666;
  uint256 public preSaleMaxSupply = 6000;
  uint256 public maxMintAmountPresale = 3;
  uint256 public maxMintAmount = 3;
  uint256 public nftPerAddressLimitPresale = 100;
  uint256 public nftPerAddressLimit = 100;
  uint256 public preSaleDate = 1651161600;
  uint256 public preSaleEndDate = 1651247999;
  uint256 public publicSaleDate = 1651248000;
  bool public paused = false;
  bool public revealed = false;
  bool public onlyWhitelisted = true;
  mapping(address => bool) whitelistedAddresses;
  mapping(address => uint256) public addressMintedBalance;

 address p1 = 0x347aC2Fb138844865fA33377c063cACd5668E1F1; // A
 address p2 = 0x6E6ea9d4BA16d71D02271E4B48F9242fCf35570b; // E
 address p3 = 0x1f147EdCb514647D02f05aEA621952642388b2D2; // D
 address p4 = 0x33c2DE3650536a67bD225F176Bb5b0CE5b6F9478; // J2
 address p5 = 0xd2c7B648897cC83820bFb6770Cad4007e9867fdC; // J
 address p6 = 0x9a1dae29Aa74EdFb6c132C8825D3933938aa8a8F; // F
 address p7 = 0xABD50f4803D7AB638d360b76b5aa7978f9B5cF6C; // P
 address p8 = 0xf5BC68f2C29122Fb4Fa746378a679c4197968Db8; // M
  
  constructor(
    string memory _initBaseURI,
    string memory _initNotRevealedUri
  ) ERC721("Malaya Ape Club", "MAC") {
    setBaseURI(_initBaseURI);
    setNotRevealedURI(_initNotRevealedUri);
  }

  //MODIFIERS
  modifier notPaused {
    require(!paused, "the contract is paused");
    _;
  }

  modifier saleStarted {
    require(block.timestamp >= preSaleDate, "Sale has not started yet");
    _;
  }

  modifier minimumMintAmount(uint256 _mintAmount) {
    require(_mintAmount > 0, "need to mint at least 1 NFT");
    _;
  }

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

  function presaleValidations(
    uint256 ownerTokenCount,
    uint256 _mintAmount,
    uint256 _supply
  ) internal {
    uint256 actualCost;
    block.timestamp < preSaleEndDate
      ? actualCost = preSaleCost
      : actualCost = cost;
      if (onlyWhitelisted == true) {
    require(isWhitelisted(msg.sender), "user is not whitelisted");
    require(
      ownerTokenCount + _mintAmount <= nftPerAddressLimitPresale,
      "max NFT per address exceeded for presale"
    );
    require(msg.value >= actualCost * _mintAmount, "insufficient funds");
    require(
      _mintAmount <= maxMintAmountPresale,
      "max mint amount per transaction exceeded"
    );
    require(
      supply.current() + _mintAmount <= preSaleMaxSupply,
      "max NFT presale limit exceeded"
    );}
    else {
    require(
      ownerTokenCount + _mintAmount <= nftPerAddressLimitPresale,
      "max NFT per address exceeded for presale"
    );
    require(msg.value >= actualCost * _mintAmount, "insufficient funds");
    require(
      _mintAmount <= maxMintAmountPresale,
      "max mint amount per transaction exceeded"
    );
    require(
      supply.current() + _mintAmount <= preSaleMaxSupply,
      "max NFT presale limit exceeded"
    );
    }
  }

  function publicsaleValidations(uint256 ownerTokenCount, uint256 _mintAmount)
    internal
  {
    require(
      ownerTokenCount + _mintAmount <= nftPerAddressLimit,
      "max NFT per address exceeded"
    );
    require(msg.value >= cost * _mintAmount, "insufficient funds");
    require(
      _mintAmount <= maxMintAmount,
      "max mint amount per transaction exceeded"
    );
  }

  //MINT
  function mint(uint256 _mintAmount)
    public
    payable
    notPaused
    saleStarted
    minimumMintAmount(_mintAmount)
  {
    uint256 ownerTokenCount = addressMintedBalance[msg.sender];

    //Do some validations depending on which step of the sale we are in
    block.timestamp < publicSaleDate
      ? presaleValidations(ownerTokenCount, _mintAmount, supply.current())
      : publicsaleValidations(ownerTokenCount, _mintAmount);

    require(supply.current() + _mintAmount <= maxSupply, "max NFT limit exceeded");

     _mintLoop(msg.sender, _mintAmount);
  }

   function mintForOwner(uint256 _mintAmount) public onlyOwner {
    require(!paused);
    require(_mintAmount > 0);
    require(_mintAmount <= maxMintAmount);
    require(supply.current() + _mintAmount <= maxSupply);

    _mintLoop(msg.sender, _mintAmount);
  }
  
    function _mintLoop(address _receiver, uint256 _mintAmount) internal {
    for (uint256 i = 0; i < _mintAmount; i++) {
      supply.increment();
      _safeMint(_receiver, supply.current());
    }
  }
 
  
  function gift(uint256 _mintAmount, address destination) public onlyOwner {
    require(_mintAmount > 0, "need to mint at least 1 NFT");
    require(supply.current() + _mintAmount <= maxSupply, "max NFT limit exceeded");

   _mintLoop(msg.sender, _mintAmount);
  }

  //PUBLIC VIEWS
  function isWhitelisted(address _user) public view returns (bool) {
    return whitelistedAddresses[_user];
  }

   function walletOfOwner(address _owner)
    public
    view
    returns (uint256[] memory)
  {
    uint256 ownerTokenCount = balanceOf(_owner);
    uint256[] memory ownedTokenIds = new uint256[](ownerTokenCount);
    uint256 currentTokenId = 1;
    uint256 ownedTokenIndex = 0;

    while (ownedTokenIndex < ownerTokenCount && currentTokenId <= maxSupply) {
      address currentTokenOwner = ownerOf(currentTokenId);

      if (currentTokenOwner == _owner) {
        ownedTokenIds[ownedTokenIndex] = currentTokenId;

        ownedTokenIndex++;
      }

      currentTokenId++;
    }

    return ownedTokenIds;
  }

  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
        bytes(currentBaseURI).length > 0
          ? string(
            abi.encodePacked(currentBaseURI, tokenId.toString(), baseExtension)
          )
          : "";
    }
  }
  
    function totalSupply() public view returns (uint256) {
    return supply.current();
  }

  function getCurrentCost() public view returns (uint256) {
    if (block.timestamp < preSaleEndDate) {
      return preSaleCost;
    } else {
      return cost;
    }
  }

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

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

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

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

  function setNftPerAddressLimitPreSale(uint256 _limit) public onlyOwner {
    nftPerAddressLimitPresale = _limit;
  }

  function setNftPerAddressLimit(uint256 _limit) public onlyOwner {
    nftPerAddressLimit = _limit;
  }

  function setPresaleCost(uint256 _newCost) public onlyOwner {
    preSaleCost = _newCost;
  }

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

  function setmaxMintAmountPreSale(uint256 _newmaxMintAmount) public onlyOwner {
    maxMintAmountPresale = _newmaxMintAmount;
  }

  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 setNotRevealedURI(string memory _notRevealedURI) public onlyOwner {
    notRevealedUri = _notRevealedURI;
  }

  function setPresaleMaxSupply(uint256 _newPresaleMaxSupply) public onlyOwner {
    preSaleMaxSupply = _newPresaleMaxSupply;
  }

  function setMaxSupply(uint256 _maxSupply) public onlyOwner {
    maxSupply = _maxSupply;
  }

  function setPreSaleDate(uint256 _preSaleDate) public onlyOwner {
    preSaleDate = _preSaleDate;
  }

  function setPreSaleEndDate(uint256 _preSaleEndDate) public onlyOwner {
    preSaleEndDate = _preSaleEndDate;
  }

  function setPublicSaleDate(uint256 _publicSaleDate) public onlyOwner {
    publicSaleDate = _publicSaleDate;
  }

    function setOnlyWhitelisted(bool _state) public onlyOwner {
    onlyWhitelisted = _state;
  }

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

  function withdrawAll() public payable onlyOwner {

  uint256 _p1share = address(this).balance * 43350 /100000;

  uint256 _p2share = address(this).balance * 21675 /100000;

  uint256 _p3share = address(this).balance * 3400 /100000;

  uint256 _p4share = address(this).balance * 3400 /100000;

  uint256 _p5share = address(this).balance * 1700 /100000;

  uint256 _p6share = address(this).balance * 2125 /100000;

  uint256 _p7share = address(this).balance * 850 /100000;

  uint256 _p8share = address(this).balance * 15000 /100000;

  uint256 _ownershare = address(this).balance * 8500 /100000;


        require(payable(p1).send(_p1share));
        require(payable(p2).send(_p2share));
        require(payable(p3).send(_p3share));
        require(payable(p4).send(_p4share));
        require(payable(p5).send(_p5share));
        require(payable(p6).send(_p6share));
        require(payable(p7).send(_p7share));
        require(payable(p8).send(_p8share));

        require(payable(msg.sender).send(_ownershare));
  }
    
}
function _baseURI() internal view virtual override returns (string memory) {
  return baseURI;
}

// INTERNAL