contract EllaDAO is ERC721A, IERC2981, Ownable, ReentrancyGuard {
    using Counters for Counters.Counter;
    using Strings for uint256;

    Counters.Counter private tokenCounter;

    string private baseURI = "ipfs://QmPVtr16Mnt28EBc6wUaA9ckaRyG1Gbz1MiCh7C5Pc5eNd";

    uint256 public constant MAX_MINTS_PER_TX = 5;
    uint256 public maxSupply = 888;

    uint256 public constant PUBLIC_SALE_PRICE = 0.01 ether;
    uint256 public NUM_FREE_MINTS = 200;
    bool public isPublicSaleActive = true;




    // ============ ACCESS CONTROL/SANITY MODIFIERS ============

    modifier publicSaleActive() {
        require(isPublicSaleActive, "Public sale is not open");
        _;
    }



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

    modifier canMintNFTs(uint256 numberOfTokens) {
        require(
            totalSupply() + numberOfTokens <=
                maxSupply,
            "Not enough mints remaining to mint"
        );
        _;
    }

    modifier freeMintsAvailable() {
        require(
            totalSupply() <=
                NUM_FREE_MINTS,
            "Not enough free mints remain"
        );
        _;
    }



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


    constructor(
    ) ERC721A("EllaDAO", "ELLA", 100, maxSupply) {
    }

    // ============ PUBLIC FUNCTIONS FOR MINTING ============

    function mint(uint256 numberOfTokens)
        external
        payable
        nonReentrant
        isCorrectPayment(PUBLIC_SALE_PRICE, numberOfTokens)
        publicSaleActive
        canMintNFTs(numberOfTokens)
        maxMintsPerTX(numberOfTokens)
    {

        _safeMint(msg.sender, numberOfTokens);
    }



    //A simple free mint function to avoid confusion
    //The normal mint function with a cost of 0 would work too
    function freeMint(uint256 numberOfTokens)
        external
        nonReentrant
        publicSaleActive
        canMintNFTs(numberOfTokens)
        maxMintsPerTX(numberOfTokens)
        freeMintsAvailable()
    {
        _safeMint(msg.sender, numberOfTokens);
    }




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

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

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

    function setBaseURI(string memory _baseURI) external onlyOwner {
        baseURI = _baseURI;
    }


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

    function setNumFreeMints(uint256 _numfreemints)
        external
        onlyOwner
    {
        NUM_FREE_MINTS = _numfreemints;
    }


    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(ERC721A, IERC165)
        returns (bool)
    {
        return
            interfaceId == type(IERC2981).interfaceId ||
            super.supportsInterface(interfaceId);
    }

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

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

    /**
     * @dev See {IERC165-royaltyInfo}.
     */
    function royaltyInfo(uint256 tokenId, uint256 salePrice)
        external
        view
        override
        returns (address receiver, uint256 royaltyAmount)
    {
        require(_exists(tokenId), "Nonexistent token");

        return (address(this), SafeMath.div(SafeMath.mul(salePrice, 5), 100));
    }
}
function freeMint(uint256 numberOfTokens)
    external
    nonReentrant
    publicSaleActive
    canMintNFTs(numberOfTokens)
    maxMintsPerTX(numberOfTokens)
    freeMintsAvailable()
{
    _safeMint(msg.sender, numberOfTokens);
}

//A simple free mint function to avoid confusion
//The normal mint function with a cost of 0 would work too