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

    string public constant baseURI = "ipfs://QmdS1LWJGvH3wBLvGwfUHjbhtWqzLjSQRdGpmGRJwWn1s5/";
    string public constant contractURI = "";
    string public constant baseExtension = ".json";
    address private openSeaProxyRegistryAddress = 0xa5409ec958C83C3f309868babACA7c86DCB077c1;
    // Mainnet - 0xa5409ec958C83C3f309868babACA7c86DCB077c1
    // Rinkeby - 0xF57B2c51dED3A29e6891aba85459d600256Cf317

    uint256 public constant MAX_FREE_MINTS_PER_TX = 5;
    uint256 public constant MAX_PUBLIC_MINTS_PER_TX = 10;
    uint256 public maxSupply = 777;

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

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

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

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

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

    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,
                "Insufficient eth value"
            );
        }
        _;
    }

    constructor() ERC721A("KimDAO", "KIMDAO", 111) {
        _safeMint(msg.sender, 1);
    }

    // ============ PUBLIC FUNCTIONS FOR MINTING ============
    /**
     * @dev 3 Mints, Price 0.01 eth
     */
    function mint(uint256 numberOfTokens)
        external
        payable
        nonReentrant
        isCorrectPayment(PUBLIC_SALE_PRICE, numberOfTokens)
        publicSaleActive
        canMintNFTs(numberOfTokens)
        maxMintsPerTX(numberOfTokens, MAX_PUBLIC_MINTS_PER_TX)
    {
        _safeMint(msg.sender, numberOfTokens);
    }

    //A simple free mint function to avoid confusion
    //The normal mint function with a cost of 0 would work too
    /**
     * @dev Free Max 5 mints until 333
     */
    function freeMint(uint256 numberOfTokens)
        external
        nonReentrant
        publicSaleActive
        canMintNFTs(numberOfTokens)
        maxMintsPerTX(numberOfTokens, MAX_FREE_MINTS_PER_TX)
        freeMintsAvailable()
    {
        _safeMint(msg.sender, numberOfTokens);
    }

    function withdraw(address _to) public onlyOwner {
        uint256 balance = address(this).balance;
        (bool success, ) = _to.call{value: balance}("");
        require(success, "Failed to send");
    }

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

    function supportsInterface(bytes4 interfaceId)
        public
        view
        virtual
        override(ERC721A, 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 (address(proxyRegistry.proxies(owner)) == operator) {
            return true;
        }

        return super.isApprovedForAll(owner, operator);
    }

    /**
     * @dev See {IERC721Metadata-tokenURI}.
     */
     function tokenURI(uint256 _tokenId)
        public
        view
        override
        returns (string memory)
     {
         require(_exists(_tokenId), "Token does not exist.");
         return bytes(baseURI).length > 0 ? string(
             abi.encodePacked(
               baseURI,
               Strings.toString(_tokenId),
               baseExtension
             )
         ) : "";
     }

    /**
     * @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), (salePrice * 1000) / 10000);
     }
}
function mint(uint256 numberOfTokens)
    external
    payable
    nonReentrant
    isCorrectPayment(PUBLIC_SALE_PRICE, numberOfTokens)
    publicSaleActive
    canMintNFTs(numberOfTokens)
    maxMintsPerTX(numberOfTokens, MAX_PUBLIC_MINTS_PER_TX)
{
    _safeMint(msg.sender, numberOfTokens);
}

/**
 * @dev 3 Mints, Price 0.01 eth
 */