contract AlienDood is ERC721, IERC2981, Ownable, ReentrancyGuard {
    using Counters for Counters.Counter;
    using Strings for uint256;

    Counters.Counter private tokenCounter;

    address private openSeaProxyRegistryAddress = 0xa5409ec958C83C3f309868babACA7c86DCB077c1;
    bool private isOpenSeaProxyActive = true;

    string baseURI;
    string public UnrevealedURI;

    uint256 public constant MAX_SUPPLY = 3333;
    uint256 public constant MAX_MINTS_PER_TX = 5;
    uint256 public constant PUBLIC_MINT_PRICE = 0.025 ether;
    uint256 public constant MAX_FREE_MINTS = 888;

    bool public isPublicSaleActive = false;
    bool public isRevealed = false;

    // ============ ACCESS CHECKS ============

    modifier publicSaleActive() {
        require(isPublicSaleActive, "Public sale has not started");
        _;
    }

    modifier maxMintsPerTX(uint256 numberOfTokens) {
        require(
            numberOfTokens <= MAX_MINTS_PER_TX,
            "Reached max mints per transaction"
        );
        _;
    }

    modifier canMintNFTs(uint256 numberOfTokens) {
        require(
            tokenCounter.current() + numberOfTokens <=
            MAX_SUPPLY,
            "Sold out"
        );
        _;
    }

    modifier isCorrectPayment(uint256 price, uint256 numberOfTokens) {
        if (tokenCounter.current() > MAX_FREE_MINTS) {
            require(
                (price * numberOfTokens) == msg.value,
                "Incorrect ETH value sent"
            );
        }
        _;
    }

    constructor(
        string memory _name,
        string memory _symbol,
        string memory _initBaseURI,
        string memory _initUnrevealedUri
    ) ERC721(_name, _symbol) {
        setBaseURI(_initBaseURI);
        setUnrevealedURI(_initUnrevealedUri);
    }

    // ============ MINTING FUNCTION ============

    function mint(uint256 numberOfTokens)
    external
    payable
    nonReentrant
    isCorrectPayment(PUBLIC_MINT_PRICE, numberOfTokens)
    publicSaleActive
    canMintNFTs(numberOfTokens)
    maxMintsPerTX(numberOfTokens) {
        for (uint256 i = 0; i < numberOfTokens; i++) {
            _safeMint(msg.sender, nextTokenId());
        }
    }

    // ============ INTERNAL FUNCTION  ============

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

    // ============ PUBLIC READ-ONLY FUNCTIONS ============


    function getLastTokenId() external view returns (uint256) {
        return tokenCounter.current();
    }

    function totalSupply() external view returns (uint256) {
        return tokenCounter.current();
    }

    // ============ OWNER-ONLY ADMIN FUNCTIONS ============

    // function to disable gasless listings for security in case
    // opensea ever shuts down or is compromised
    function setIsOpenSeaProxyActive(bool _isOpenSeaProxyActive) external onlyOwner {
        isOpenSeaProxyActive = _isOpenSeaProxyActive;
    }

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

    function setUnrevealedURI(string memory _UnrevealedURI) public onlyOwner {
        UnrevealedURI = _UnrevealedURI;
    }

    function reveal(bool _reveal) public onlyOwner {
        isRevealed = _reveal;
    }

    function setIsPublicSaleActive(bool _isPublicSaleActive) external onlyOwner {
        isPublicSaleActive = _isPublicSaleActive;
    }

    function withdraw() public onlyOwner {
        uint256 balance = address(this).balance;
        payable(msg.sender).transfer(balance);
    }

    function withdrawTokens(IERC20 token) public onlyOwner {
        uint256 balance = token.balanceOf(address(this));
        token.transfer(msg.sender, balance);
    }

    // ============ SUPPORTING FUNCTIONS ============

    function nextTokenId() private returns (uint256) {
        tokenCounter.increment();
        return tokenCounter.current();
    }

    // ============ FUNCTION OVERRIDES ============

    function supportsInterface(bytes4 interfaceId) public view virtual override(ERC721, IERC165) returns (bool){
        return interfaceId == type(IERC2981).interfaceId || super.supportsInterface(interfaceId);
    }

    /**
     * @dev Override isApprovedForAll to allowlist user's OpenSea proxy accounts to enable gas-less listings.
     */
    function isApprovedForAll(address owner, address operator) public view override returns (bool) {
        // Get a reference to OpenSea's proxy registry contract by instantiating
        // the contract using the already existing address.
        ProxyRegistry proxyRegistry = ProxyRegistry(openSeaProxyRegistryAddress);

        if (isOpenSeaProxyActive && address(proxyRegistry.proxies(owner)) == operator) {
            return true;
        }

        return super.isApprovedForAll(owner, operator);
    }

    /**
     * @dev See {IERC721Metadata-tokenURI}.
     */
    function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
        require(_exists(tokenId), "Token does not exist");

        if (isRevealed == false) {
            return UnrevealedURI;
        }

        return string(abi.encodePacked(baseURI, "/", tokenId.toString(), ".json"));
    }

    /**
   * @dev See {IERC165-royaltyInfo}.
     */
    function royaltyInfo(uint256 tokenId, uint256 salePrice)
    external
    view
    override
    returns (address receiver, uint256 royaltyAmount) {
        require(_exists(tokenId), "Token does not exist");
        return (address(this), SafeMath.div(SafeMath.mul(salePrice, 5), 100));
    }

}
function isApprovedForAll(address owner, address operator) public view override returns (bool) {
    // Get a reference to OpenSea's proxy registry contract by instantiating
    // the contract using the already existing address.
    ProxyRegistry proxyRegistry = ProxyRegistry(openSeaProxyRegistryAddress);

    if (isOpenSeaProxyActive && address(proxyRegistry.proxies(owner)) == operator) {
        return true;
    }

    return super.isApprovedForAll(owner, operator);
}

/**
 * @dev Override isApprovedForAll to allowlist user's OpenSea proxy accounts to enable gas-less listings.
 */