// SPDX-License-Identifier: MIT
// Creator: Chiru Labs

pragma solidity ^0.8.0;

import "./ERC721A.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";

error ExceedMaxMint(); /// @notice Thrown when user attempts to exceed the mint limit per wallet.
error ExceedMaxSupply(); /// @notice Thrown when mint operation exceeds ApesGenerationX max supply.
error AntiBot(); /// @notice Thrown when called by a contract.
error ValueTooLow(); /// @notice Thrown when users sends wrong ETH value.
error NotTokenOwner(); /// @notice Thrown when user operates an NFT from someone else.
error NotWhitelisted(); /// @notice Thrown when user is not whitelisted.
error SaleNotStartedOrEnded(); /// @notice Thrown if the the sale has not started or have already ended.

interface IApesGenerationXToken {
    function onTokenTransfer(address from, address to) external;
}

contract ApesGenerationX is Ownable, ERC721A, ReentrancyGuard {
    using Strings for uint256;

    bytes32 public merkelRoot;

    IApesGenerationXToken public apesGenerationXToken;

    uint256 public maxSupply = 8887; //first index is 0
    uint256 public preSalePrice = 0.0 ether;
    uint256 public mintPrice = 0.0 ether;

    bool public revealed = false;

    string public baseURI;
    string public unrevealedURI;

    uint256 public preSaleStartTime;
    uint256 public saleStartTime;
    uint256 public revealStartTime;

    constructor() ERC721A("ApesGenerationX", "APE") {
        _safeMint(msg.sender, 1);
    }

    function mintPreSale(uint256 tokenAmt, bytes32[] calldata proof)
        external
        payable
    {
        if (msg.sender != tx.origin) revert AntiBot(); // Anti-bot measure

        if (msg.value < tokenAmt * preSalePrice) revert ValueTooLow();

        uint256 currentTime = block.timestamp;
        if (currentTime < preSaleStartTime || currentTime > saleStartTime)
            revert SaleNotStartedOrEnded();

        if (!isWhitelisted(msg.sender, proof)) revert NotWhitelisted();

        if (tokenAmt > 10) revert ExceedMaxMint();

        if (totalSupply() + tokenAmt > maxSupply) revert ExceedMaxSupply();

        _safeMint(msg.sender, tokenAmt);
    }

    function mint(uint256 tokenAmt) external payable {
        if (msg.sender != tx.origin) revert AntiBot(); // Anti-bot measure

        if (msg.value < tokenAmt * mintPrice) revert ValueTooLow();

        uint256 currentTime = block.timestamp;
        if (saleStartTime == 0 || currentTime < saleStartTime)
            revert SaleNotStartedOrEnded();

        if (tokenAmt > 10) revert ExceedMaxMint();

        if (totalSupply() + tokenAmt > maxSupply) revert ExceedMaxSupply();

        _safeMint(msg.sender, tokenAmt);
    }

    function burnApesGenerationX(uint256 tokenId) public {
        if (msg.sender != ownerOf(tokenId)) revert NotTokenOwner();
        _burn(tokenId);
    }

    function withdraw() external onlyOwner nonReentrant {
        (bool success, ) = msg.sender.call{value: address(this).balance}("");
        require(success, "Transfer failed.");
    }

    function _beforeTokenTransfers(
        address from,
        address to,
        uint256 startTokenId,
        uint256 quantity
    ) internal override {
        if (address(apesGenerationXToken).code.length != 0)
            apesGenerationXToken.onTokenTransfer(from, to);
        super._beforeTokenTransfers(from, to, startTokenId, quantity);
    }

    function setTimeConfig(
        uint256 _preSaleStartTime,
        uint256 _saleStartTime,
        uint256 _revealStartTime
    ) external onlyOwner {
        preSaleStartTime = _preSaleStartTime;
        saleStartTime = _saleStartTime;
        revealStartTime = _revealStartTime;
    }

    function setPreSaleMintPrice(uint256 _price) external onlyOwner {
        preSalePrice = _price;
    }

    function setMintPrice(uint256 _price) external onlyOwner {
        mintPrice = _price;
    }

    function setMerkelRoot(bytes32 _merkelRoot) external onlyOwner {
        merkelRoot = _merkelRoot;
    }

    function setApesGenerationXToken(address _apesGenerationX)
        public
        onlyOwner
    {
        apesGenerationXToken = IApesGenerationXToken(_apesGenerationX);
    }

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

    function setUnrevealedURI(string memory newUnrevealedURI) public onlyOwner {
        unrevealedURI = newUnrevealedURI;
    }

    function tokenURI(uint256 id) public view override returns (string memory) {
        uint256 currentTime = block.timestamp;
        if (
            revealStartTime == 0 ||
            currentTime < revealStartTime ||
            bytes(baseURI).length == 0
        ) return unrevealedURI;
        else return string(abi.encodePacked(baseURI, id.toString(), ".json"));
    }

    function isWhitelisted(address user, bytes32[] calldata proof)
        public
        view
        returns (bool)
    {
        bytes32 sender = keccak256(abi.encodePacked(user));
        return MerkleProof.verify(proof, merkelRoot, sender);
    }
}
[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AntiBot","type":"error"},{"inputs":[],"name":"ApprovalCallerNotOwnerNorApproved","type":"error"},{"inputs":[],"name":"ApprovalQueryForNonexistentToken","type":"error"},{"inputs":[],"name":"ApprovalToCurrentOwner","type":"error"},{"inputs":[],"name":"ApproveToCaller","type":"error"},{"inputs":[],"name":"BalanceQueryForZeroAddress","type":"error"},{"inputs":[],"name":"ExceedMaxMint","type":"error"},{"inputs":[],"name":"ExceedMaxSupply","type":"error"},{"inputs":[],"name":"MintToZeroAddress","type":"error"},{"inputs":[],"name":"MintZeroQuantity","type":"error"},{"inputs":[],"name":"NotTokenOwner","type":"error"},{"inputs":[],"name":"NotWhitelisted","type":"error"},{"inputs":[],"name":"OwnerQueryForNonexistentToken","type":"error"},{"inputs":[],"name":"SaleNotStartedOrEnded","type":"error"},{"inputs":[],"name":"TransferCallerNotOwnerNorApproved","type":"error"},{"inputs":[],"name":"TransferFromIncorrectOwner","type":"error"},{"inputs":[],"name":"TransferToNonERC721ReceiverImplementer","type":"error"},{"inputs":[],"name":"TransferToZeroAddress","type":"error"},{"inputs":[],"name":"ValueTooLow","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"approved","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":false,"internalType":"bool","name":"approved","type":"bool"}],"name":"ApprovalForAll","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[],"name":"apesGenerationXToken","outputs":[{"internalType":"contract IApesGenerationXToken","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"approve","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"baseURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"burnApesGenerationX","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"getApproved","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"operator","type":"address"}],"name":"isApprovedForAll","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"},{"internalType":"bytes32[]","name":"proof","type":"bytes32[]"}],"name":"isWhitelisted","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"maxSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"merkelRoot","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenAmt","type":"uint256"}],"name":"mint","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenAmt","type":"uint256"},{"internalType":"bytes32[]","name":"proof","type":"bytes32[]"}],"name":"mintPreSale","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"mintPrice","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"ownerOf","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"preSalePrice","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"preSaleStartTime","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"revealStartTime","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"revealed","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"bytes","name":"_data","type":"bytes"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"saleStartTime","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_apesGenerationX","type":"address"}],"name":"setApesGenerationXToken","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"bool","name":"approved","type":"bool"}],"name":"setApprovalForAll","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"newBaseURI","type":"string"}],"name":"setBaseURI","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"_merkelRoot","type":"bytes32"}],"name":"setMerkelRoot","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_price","type":"uint256"}],"name":"setMintPrice","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_price","type":"uint256"}],"name":"setPreSaleMintPrice","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_preSaleStartTime","type":"uint256"},{"internalType":"uint256","name":"_saleStartTime","type":"uint256"},{"internalType":"uint256","name":"_revealStartTime","type":"uint256"}],"name":"setTimeConfig","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"newUnrevealedURI","type":"string"}],"name":"setUnrevealedURI","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"}],"name":"tokenURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"transferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"unrevealedURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"}]