// Contract: Surge
// Target function: function safeTransferFrom(
21: approve to caller");
        _operatorApprovals[owner][operator] = approved;
        emit ApprovalForAll(owner, operator, approved);
    }

    /**
     * @dev Reverts if the `tokenId` has not been minted yet.
     */
    function _requireMinted(uint256 tokenId) internal view virtual {
        require(_exists(tokenId), "ERC721: invalid token ID");
    }

    /**
     * @dev Internal function to invoke {IERC721Receiver-onERC721Received} on a target address.
     * The call is not executed if the target address is not a contract.
     *
     * @param from address representing the previous owner of the given token ID
     * @param to target address that will receive the tokens
     * @param tokenId uint256 ID of the token to be transferred
     * @param data bytes optional data to send along with the call
     * @return bool whether the call correctly returned the expected magic value
     */
    function _checkOnERC721Received(
        address from,
        address to,
        uint256 tokenId,
        bytes memory data
    ) private returns (bool) {
        if (to.isContract()) {
            try
                IERC721Receiver(to).onERC721Received(
                    _msgSender(),
                    from,
                    tokenId,
                    data
                )
            returns (bytes4 retval) {
                return retval == IERC721Receiver.onERC721Received.selector;
            } catch (bytes memory reason) {
                if (reason.length == 0) {
                    revert(
                        "ERC721: transfer to non ERC721Receiver implementer"
                    );
                } else {
                    /// @solidity memory-safe-assembly
                    assembly {
                        revert(add(32, reason), mload(reason))
                    }
                }
            }
        } else {
            return true;
        }
    }

    /**
     * @dev Hook that is called before any token transfer. This includes minting
     * and burning.
     *
     * Calling conditions:
     *
     * - When `from` and `to` are both non-zero, ``from``'s `tokenId` will be
     * transferred to `to`.
     * - When `from` is zero, `tokenId` will be minted for `to`.
     * - When `to` is zero, ``from``'s `tokenId` will be burned.
     * - `from` and `to` are never both zero.
     *
     * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
     */
    function _beforeTokenTransfer(
        address from,
        address to,
        uint256 tokenId
    ) internal virtual {}

    /**
     * @dev Hook that is called after any transfer of tokens. This includes
     * minting and burning.
     *
     * Calling conditions:
     *
     * - when `from` and `to` are both non-zero.
     * - `from` and `to` are never both zero.
     *
     * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
     */
    function _afterTokenTransfer(
        address from,
        address to,
        uint256 tokenId
    ) internal virtual {}
}

/**
 * @dev Contract module which allows children to implement an emergency stop
 * mechanism that can be triggered by an authorized account.
 *
 * This module is used through inheritance. It will make available the
 * modifiers `whenNotPaused` and `whenPaused`, which can be applied to
 * the functions of your contract. Note that they will not be pausable by
 * simply including this module, only once the modifiers are put in place.
 */
abstract contract Pausable is Context {
    /**
     * @dev Emitted when the pause is triggered by `account`.
     */
    event Paused(address account);

    /**
     * @dev Emitted when the pause is lifted by `account`.
     */
    event Unpaused(address account);

    bool private _paused;

    /**
     * @dev Initializes the contract in unpaused state.
     */
    constructor() {
        _paused = false;
    }

    /**
     * @dev Modifier to make a function callable only when the contract is not paused.
     *
     * Requirements:
     *
     * - The contract must not be paused.
     */
    modifier whenNotPaused() {
        _requireNotPaused();
        _;
    }

    /**
     * @dev Modifier to make a function callable only when the contract is paused.
     *
     * Requirements:
     *
     * - The contract must be paused.
     */
    modifier whenPaused() {
        _requirePaused();
        _;
    }

    /**
     * @dev Returns true if the contract is paused, and false otherwise.
     */
    function paused() public view virtual returns (bool) {
        return _paused;
    }

    /**
     * @dev Throws if the contract is paused.
     */
    function _requireNotPaused() internal view virtual {
        require(!paused(), "Pausable: paused");
    }

    /**
     * @dev Throws if the contract is not paused.
     */
    function _requirePaused() internal view virtual {
        require(paused(), "Pausable: not paused");
    }

    /**
     * @dev Triggers stopped state.
     *
     * Requirements:
     *
     * - The contract must not be paused.
     */
    function _pause() internal virtual whenNotPaused {
        _paused = true;
        emit Paused(_msgSender());
    }

    /**
     * @dev Returns to normal state.
     *
     * Requirements:
     *
     * - The contract must be paused.
     */
    function _unpause() internal virtual whenPaused {
        _paused = false;
        emit Unpaused(_msgSender());
    }
}

/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * By default, the owner account will be the one that deploys the contract. This
 * can later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
abstract contract Ownable is Context {
    address private _owner;

    event OwnershipTransferred(
        address indexed previousOwner,
        address indexed newOwner
    );

    /**
     * @dev Initializes the contract setting the deployer as the initial owner.
     */
    constructor() {
        _transferOwnership(_msgSender());
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        _checkOwner();
        _;
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if the sender is not the owner.
     */
    function _checkOwner() internal view virtual {
        require(owner() == _msgSender(), "Ownable: caller is not the owner");
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions anymore. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby removing any functionality that is only available to the owner.
     */
    function renounceOwnership() public virtual onlyOwner {
        _transferOwnership(address(0));
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        require(
            newOwner != address(0),
            "Ownable: new owner is the zero address"
        );
        _transferOwnership(newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}

/*
          ______         __    __        _______          ______         ________ 
         /      \       |  \  |  \      |       \        /      \       |        \
        |  $$$$$$\      | $$  | $$      | $$$$$$$\      |  $$$$$$\      | $$$$$$$$
        | $$___\$$      | $$  | $$      | $$__| $$      | $$ __\$$      | $$__    
         \$$    \       | $$  | $$      | $$    $$      | $$|    \      | $$  \   
         _\$$$$$$\      | $$  | $$      | $$$$$$$\      | $$ \$$$$      | $$$$$   
        |  \__| $$      | $$__/ $$      | $$  | $$      | $$__| $$      | $$_____ 
         \$$    $$       \$$    $$      | $$  | $$       \$$    $$      | $$     \
          \$$$$$$         \$$$$$$        \$$   \$$        \$$$$$$        \$$$$$$$$                                                    
                                                                
*/

/**
 * @title Surge (https://surgenft.xyz/)
 * @author Exodia Studio (https://exodia.studio/)
 */
contract Surge is
    ERC721,
    Pausable,
    Ownable,
    ReentrancyGuard,
    DefaultOperatorFilterer,
    ERC2981
{
    // ------------------------------------------------------------------------------------------------------- //
    // ---------------------------------------------- INITIALIZATION ----------------------------------------- //
    // ------------------------------------------------------------------------------------------------------- //

    constructor() ERC721("Surge", "SGE") {
        _pause();
        unchecked {
            for (uint256 i; i < 20; ) {
                _mint(msg.sender, tokenIdCounter);
                ++i;
                ++tokenIdCounter;
            }
        }
    }

    // ------------------------------------------------------------------------------------------------------- //
    // ------------------------------------------------ LIBRARIES -------------------------------------------- //
    // ------------------------------------------------------------------------------------------------------- //

    using Strings for uint256;

    // ------------------------------------------------------------------------------------------------------- //
    // ------------------------------------------------ MODIFIERS -------------------------------------------- //
    // ------------------------------------------------------------------------------------------------------- //

    modifier checkRequirements(uint256 amount) {
        uint256 cost = price * amount;

        require(msg.value == cost, "Incorrect value!");
        require(amount <= maxPerTx, "Exceeds max per tx!");
        require(tokenIdCounter + amount <= MAX_SUPPLY, "Exceeds max supply!");
        _;
    }

    // ------------------------------------------------------------------------------------------------------- //
    // ------------------------------------------------ VARIABLES -------------------------------------------- //
    // ------------------------------------------------------------------------------------------------------- //

    /* ------------------------------------------------ CONSTANTS -------------------------------------------- */

    uint256 private constant MAX_SUPPLY = 2500;

    /* ------------------------------------------------ UPDATABLE -------------------------------------------- */

    bool private metadataLock;
    bool private publicMintPhase;
    uint256 private maxPerTx = 2;
    uint256 private tokenIdCounter;
    uint256 private maxPerWallet = 2;
    uint256 private price = 0.019 ether;
    string private uri =
        "ipfs://bafybeiciguidsf2iklibchqn4beptqxbtovi7ss3uvgk4daaadum3ccw3q/";
    bytes32 private merkleRoot =
        0x35f076cc6a8aac4109a7f23f6b973878f81476c48e92c2ae19088c18cf1fe0b1;

    // ------------------------------------------------------------------------------------------------------- //
    // ------------------------------------------------ MAPPINGS --------------------------------------------- //
    // ------------------------------------------------------------------------------------------------------- //

    mapping(address => uint256) private _publicMinted;
    mapping(address => uint256) private _banditsMinted;

    // ------------------------------------------------------------------------------------------------------- //
    // ------------------------------------------------- EVENTS ---------------------------------------------- //
    // ------------------------------------------------------------------------------------------------------- //

    event Reveal(string indexed uri);
    event SetPrice(uint256 indexed value);
    event SetMaxPerTx(uint256 indexed value);
    event SetMaxPerWallet(uint256 indexed value);
    event SetMerkleRoot(bytes32 indexed merkleRoot);
    event SetPublicMintPhase(bool indexed check);

    // ------------------------------------------------------------------------------------------------------- //
    // ------------------------------------------------- GETTERS --------------------------------------------- //
    // ------------------------------------------------------------------------------------------------------- //

    function totalSupply() public view returns (uint256) {
        return tokenIdCounter;
    }

    function getPrice() public view returns (uint256) {
        return price;
    }

    function getMaxPerTx() public view returns (uint256) {
        return maxPerTx;
    }

    function getMaxPerWallet() public view returns (uint256) {
        return maxPerWallet;
    }

    function checkPublicMintPhase() public view returns (bool) {
        return publicMintPhase;
    }

    function getPublicMintedByWallet(address wallet)
        public
        view
        returns (uint256)
    {
        return _publicMinted[wallet];
    }

    function getBanditsMintedByWallet(address wallet)
        public
        view
        returns (uint256)
    {
        return _banditsMinted[wallet];
    }

    function isWhitelisted(address wallet, bytes32[] calldata merkleProof)
        public
        view
        returns (bool)
    {
        bytes32 leaf = keccak256(abi.encodePacked(wallet));
        return MerkleProof.verify(merkleProof, merkleRoot, leaf);
    }

    // ------------------------------------------------------------------------------------------------------- //
    // ------------------------------------------------- SETTERS --------------------------------------------- //
    // ------------------------------------------------------------------------------------------------------- //

    /* ----------------------------------------------- CENTRALIZED ------------------------------------------- */

    function pause() external onlyOwner {
        _pause();
    }

    function unpause() external onlyOwner {
        _unpause();
    }

    function withdraw() external onlyOwner {
        payable(msg.sender).transfer(address(this).balance);
    }

    function switchMintPhase() external onlyOwner {
        publicMintPhase = !publicMintPhase;
        emit SetPublicMintPhase(publicMintPhase);
    }

    function reveal(string memory _uri) external onlyOwner {
        require(!metadataLock, "Already revealed!");
        metadataLock = true;
        uri = _uri;
        emit Reveal(_uri);
    }

    function setPrice(uint256 _price) external onlyOwner {
        price = _price;
        emit SetPrice(_price);
    }

    function setMaxPerTx(uint256 _maxPerTx) external onlyOwner {
        maxPerTx = _maxPerTx;
        emit SetMaxPerTx(_maxPerTx);
    }

    function setMerkleRoot(bytes32 _merkleRoot) external onlyOwner {
        merkleRoot = _merkleRoot;
        emit SetMerkleRoot(_merkleRoot);
    }

    function setMaxPerWallet(uint256 _maxPerWallet) external onlyOwner {
        maxPerWallet = _maxPerWallet;
        emit SetMaxPerWallet(_maxPerWallet);
    }

    function setDefaultRoyalty(address receiver, uint96 feeNumerator)
        external
        onlyOwner
    {
        _setDefaultRoyalty(receiver, feeNumerator);
    }

    /* -------------------------------------------------- PUBLIC --------------------------------------------- */

    function publicMint(uint256 amount)
        external
        payable
        nonReentrant
        checkRequirements(amount)
    {
        require(publicMintPhase, "Public sale finished!");
        require(
            _publicMinted[msg.sender] + amount <= maxPerWallet,
            "Exceeds max per wallet!"
        );
        unchecked {
            _publicMinted[msg.sender] += amount;

            for (uint256 i; i < amount; ) {
                _mint(msg.sender, tokenIdCounter);
                ++tokenIdCounter;
                ++i;
            }
        }
    }

    function banditMint(uint256 amount, bytes32[] calldata merkleProof)
        external
        payable
        nonReentrant
        checkRequirements(amount)
    {
        bytes32 leaf = keccak256(abi.encodePacked(msg.sender));
        require(!publicMintPhase, "Whitelist sale finished!");
        require(
            MerkleProof.verify(merkleProof, merkleRoot, leaf),
            "NOT_WHITELISTED!"
        );

        require(
            _banditsMinted[msg.sender] + amount <= maxPerWallet,
            "Exceeds max per wallet!"
        );
        unchecked {
            _banditsMinted[msg.sender] += amount;

            for (uint256 i; i < amount; ) {
                _mint(msg.sender, tokenIdCounter);
                ++tokenIdCounter;
                ++i;
            }
        }
    }

    // ------------------------------------------------------------------------------------------------------- //
    // ------------------------------------------------ OVERRIDES -------------------------------------------- //
    // ------------------------------------------------------------------------------------------------------- //

    function _baseURI() internal view override returns (string memory) {
        return uri;
    }

    function tokenURI(uint256 tokenId)
        public
        view
        virtual
        override
        returns (string memory)
    {
        _requireMinted(tokenId);
        string memory baseURI = _baseURI();
        return
            bytes(baseURI).length > 0
                ? string(abi.encodePacked(baseURI, tokenId.toString(), ".json"))
                : "";
    }

    /**
     * @dev See {IERC721-setApprovalForAll}.
     *      In this example the added modifier ensures that the operator is allowed by the OperatorFilterRegistry.
     */
    function setApprovalForAll(address operator, bool approved)
        public
        override
        onlyAllowedOperatorApproval(operator)
    {
        super.setApprovalForAll(operator, approved);
    }

    /**
     * @dev See {IERC721-approve}.
     *      In this example the added modifier ensures that the operator is allowed by the OperatorFilterRegistry.
     */
    function approve(address operator, uint256 tokenId)
        public
        override
        onlyAllowedOperatorApproval(operator)
    {
        super.approve(operator, tokenId);
    }

    /**
     * @dev See {IERC721-transferFrom}.
     *      In this example the added modifier ensures that the operator is allowed by the OperatorFilterRegistry.
     */
    function transferFrom(
        address from,
        address to,
        uint256 tokenId
    ) public override onlyAllowedOperator(from) {
        super.transferFrom(from, to, tokenId);
    }

    /**
     * @dev See {IERC721-safeTransferFrom}.
     *      In this example the added modifier ensures that the operator is allowed by the OperatorFilterRegistry.
     */
    function safeTransferFrom(
        address from,
        address to,
        uint256 tokenId
    ) public override onlyAllowedOperator(from) {
        super.safeTransferFrom(from, to, tokenId);
    }