contract AlphaInitium is ERC721("Alpha Initium", "ALPHA"), ERC721Enumerable, Ownable {
    using SafeMath for uint256;
    using Strings for uint256;

    string private baseURI;
    uint256 public constant BUY_LIMIT_PER_TX = 5;
    uint256 public constant MAX_NFT_PUBLIC = 8860;
    uint256 private constant MAX_NFT = 8912;
    uint256 public constant maxGiveaway = 52;
    uint256 public NFTPrice = 250000000000000000;  // 0.25 ETH
    uint256 public constant WHITELIST_MAX_MINT = 2;
    uint256 public giveawayCount;
    bool public reveal;
    bool public isActive;
    bool public isPresaleActive;
    bool public isPublicSaleActive;
    bytes32 public root;
    uint private devSup = 6 ether;
    address private devAddress = 0x0AF3f0461f2bEE2F18f405d0a4463A0cC131723D;
    mapping(address => uint256) public whiteListClaimed;

    /*
     * Function toggleActive to activate/desactivate the smart contract
    */
    function toggleActive() external onlyOwner {
        isActive = !isActive;
    }

    /*
     * Function toggleReveal to change NFT metadata
    */
    function toggleReveal() external onlyOwner {
        reveal = !reveal;
    }

    /*
     * Function togglePublicSale to activate/desactivate public sale
    */
    function togglePublicSale() external onlyOwner {
        isPublicSaleActive = !isPublicSaleActive;
    }

    /*
     * Function togglePresale to activate/desactivate  presale
    */
    function togglePresale() external onlyOwner {
        isPresaleActive = !isPresaleActive;
    }

    /*
     * Function to set Base and Blind URI
    */
    function setURIs(string memory _URI) external onlyOwner {
        baseURI = _URI;
    }

    /*
     * Function to set NFT Price
    */
    function setNFTPrice(uint256 _price) external onlyOwner {
        NFTPrice = _price;
    }

    /*
     * Function to set the merkle root
    */
    function setRoot(uint256 _root) external onlyOwner {
        root = bytes32(_root);
    }

    /*
     * Function to withdraw collected amount during minting by the owner
    */
    function withdraw() public onlyOwner {
        uint balance = address(this).balance;
        require(balance > 0, "Balance should be more then zero");

        if(devSup > 0) {
            payable(devAddress).transfer(devSup);
            balance = balance.sub(devSup);
            devSup = 0;
        }

        payable(0x1fbB915efB4BD5E044A09e13C1672df4B8990Ca1).transfer((balance * 900) / 1000);
        payable(0x87114B3f17A7f55DF710d749b39211d20049E866).transfer((balance * 50) / 1000);
        payable(0x18C4d8385a0f6D4Ff2115432DB8B3bC0a643Ecf6).transfer((balance * 50) / 1000);
    }

    /*
     * Function to mint new NFTs
     * It is payable. Amount is calculated as per (NFTPrice.mul(_numOfTokens))
    */
    function mintNFT(uint256 _numOfTokens, bytes32[] memory _proof) public payable {
        require(isActive, 'Contract is not active');
        require(_numOfTokens <= BUY_LIMIT_PER_TX, "Cannot mint above limit");
        require(totalSupply().add(_numOfTokens).sub(giveawayCount) <= MAX_NFT_PUBLIC, "Purchase would exceed max public supply of NFTs");
        require(NFTPrice.mul(_numOfTokens) == msg.value, "Ether value sent is not correct");

        if(!isPublicSaleActive) {
            require(isPresaleActive, 'Presale is not active');
            require(verify(_proof, bytes32(uint256(uint160(msg.sender)))), "Not whitelisted or raffle");
            require(whiteListClaimed[msg.sender].add(_numOfTokens) <= WHITELIST_MAX_MINT, 'Purchase exceeds max whiteed');
            whiteListClaimed[msg.sender] = whiteListClaimed[msg.sender].add(_numOfTokens);
        }

        for(uint i = 0; i < _numOfTokens; i++) {
            _safeMint(msg.sender, totalSupply());
        }
    }

    /*
     * Function to mint NFTs for giveaway and partnerships
    */
    function mintByOwner(address _to) public onlyOwner {
        require(giveawayCount.add(1) <= maxGiveaway,"Cannot do more giveaway");
        require(totalSupply().add(1) <= MAX_NFT,"Tokens number to mint cannot exceed number of MAX tokens");

        _safeMint(_to, totalSupply());
        giveawayCount = giveawayCount.add(1);
    }

    /*
     * Function to mint all NFTs for giveaway and partnerships
    */
    function mintMultipleByOwner(address[] memory _to) public onlyOwner {
        require(giveawayCount.add(_to.length) <= maxGiveaway,"Cannot do more giveaway");
        require(totalSupply().add(_to.length) <= MAX_NFT,"Tokens number to mint cannot exceed number of MAX tokens");

        for(uint256 i = 0; i < _to.length; i++) {
            _safeMint(_to[i], totalSupply());
            giveawayCount = giveawayCount.add(1);
        }
    }

    /*
     * Function to get token URI of given token ID
     * URI will be blank untill totalSupply reaches MAX_NFT_PUBLIC
    */
    function tokenURI(uint256 _tokenId) public view virtual override returns (string memory) {
        require(_exists(_tokenId), "ERC721Metadata: URI query for nonexistent token");

        if (!reveal) {
            return string(abi.encodePacked(baseURI));
        } else {
            return string(abi.encodePacked(baseURI, _tokenId.toString()));
        }
    }

    /*
     * Function to verify the proof
    */
    function verify(bytes32[] memory proof, bytes32 leaf) public view returns (bool) {
        bytes32 computedHash = leaf;

        for (uint256 i = 0; i < proof.length; i++) {
            bytes32 proofElement = proof[i];

            if (computedHash <= proofElement) {
                // Hash(current computed hash + current element of the proof)
                computedHash = sha256(abi.encodePacked(computedHash, proofElement));
            } else {
                // Hash(current element of the proof + current computed hash)
                computedHash = sha256(abi.encodePacked(proofElement, computedHash));
            }
        }

        // Check if the computed hash (root) is equal to the provided root
        return computedHash == root;
    }

    // Standard functions to be overridden in ERC721Enumerable
    function supportsInterface(bytes4 _interfaceId) public view override (ERC721, ERC721Enumerable) returns (bool) {
        return super.supportsInterface(_interfaceId);
    }


    function _beforeTokenTransfer(address _from, address _to, uint256 _tokenId) internal override(ERC721, ERC721Enumerable) {
        super._beforeTokenTransfer(_from, _to, _tokenId);
    }
}
function toggleReveal() external onlyOwner {
    reveal = !reveal;
}

/*
 * Function toggleReveal to change NFT metadata
 */