contract HoodedCultist is ERC721, Ownable {

    uint constant public MAX_SUPPLY = 6666;
    uint constant public PRICE = 0.01 ether;

    string public baseURI = "ipfs://QmbxhQscEKvLBNpG5i68w4u77F429sM9PveRXn8BHqnLT1/";

    mapping(address => bool) public projectProxy;
    address public proxyRegistryAddress = 0xa5409ec958C83C3f309868babACA7c86DCB077c1;

    uint public maxFreeNFTPerWallet = 1;
    uint public maxMintsPerWallet = 20;
    uint public mintingStartTimestamp = 1644008400;
    uint public reservedSupply = 5;

    uint public totalSupply;
    mapping(address => uint) public mintedNFTs;

    constructor() ERC721("HoodedCultist", "HC") {
        mintNFTs(msg.sender, 1);
    }

    // Setters region
    function setBaseURI(string memory _baseURIArg) external onlyOwner {
        baseURI = _baseURIArg;
    }

    function toggleProxyState(address proxyAddress) public onlyOwner {
        projectProxy[proxyAddress] = !projectProxy[proxyAddress];
    }

    function setProxyRegistryAddress(address _proxyRegistryAddress) external onlyOwner {
        proxyRegistryAddress = _proxyRegistryAddress;
    }

    function setMaxFreeNFTPerWallet(uint _maxFreeNFTPerWallet) external onlyOwner {
        maxFreeNFTPerWallet = _maxFreeNFTPerWallet;
    }

    function setMaxMintsPerWallet(uint _maxMintsPerWallet) external onlyOwner {
        maxMintsPerWallet = _maxMintsPerWallet;
    }

    function setMintingStartTimestamp(uint _mintingStartTimestamp) external onlyOwner {
        mintingStartTimestamp = _mintingStartTimestamp;
    }

    function setReservedSupply(uint _reservedSupply) external onlyOwner {
        reservedSupply = _reservedSupply;
    }

    // endregion

    function _baseURI() internal view override returns (string memory) {
        return baseURI;
    }

    // Mint and Claim functions
    modifier maxSupplyCheck(uint amount)  {
        require(totalSupply + reservedSupply + amount <= MAX_SUPPLY, "Tokens supply reached limit");
        _;
    }

    function findRemainingFreeMints() public view returns (uint) {
        uint minted = mintedNFTs[msg.sender];
        return maxFreeNFTPerWallet > minted ? maxFreeNFTPerWallet - minted : 0;
    }

    function mintPrice(uint amount) public view returns (uint) {
        uint remainingFreeMints = findRemainingFreeMints();
        if (remainingFreeMints >= amount) {
            return 0;
        } else {
            return (amount - remainingFreeMints) * PRICE;
        }
    }

    function mint(uint amount) external payable {
        require(tx.origin == msg.sender, "The caller is another contract");
        require(block.timestamp >= mintingStartTimestamp, "Minting is not available");
        require(amount > 0 && amount <= 20, "Wrong mint amount");
        require(mintedNFTs[msg.sender] + amount <= maxMintsPerWallet, "maxMintsPerWallet constraint violation");
        require(mintPrice(amount) == msg.value, "Wrong ethers value");

        mintedNFTs[msg.sender] += amount;
        mintNFTs(msg.sender, amount);
    }

    function airdrop(address[] memory addresses, uint[] memory amounts) external onlyOwner {
        for (uint i = 0; i < addresses.length; i++) {
            mintNFTs(addresses[i], amounts[i]);
        }
    }

    function mintNFTs(address to, uint amount) internal maxSupplyCheck(amount) {
        uint fromToken = totalSupply + 1;
        totalSupply += amount;
        for (uint i = 0; i < amount; i++) {
            _mint(to, fromToken + i);
        }
    }

    function isApprovedForAll(address _owner, address operator) public view override returns (bool) {
        OpenSeaProxyRegistry proxyRegistry = OpenSeaProxyRegistry(proxyRegistryAddress);
        if (address(proxyRegistry.proxies(_owner)) == operator || projectProxy[operator]) return true;
        return super.isApprovedForAll(_owner, operator);
    }

    receive() external payable {

    }

    function withdraw() external onlyOwner {
        uint balance = address(this).balance;
        payable(0x0be7C26A3730B41c4c4289078DBb564E6e64317c).transfer(balance);
    }

}
function _baseURI() internal view override returns (string memory) {
    return baseURI;
}

// endregion