contract HybridFactions is ERC721A, Ownable {
    constructor() ERC721A("Hybrid Factions", "HF",20) {}

    using SafeMath for uint256;
    using Strings for uint256;

    string private baseURI;
    string private blindURI;
    uint256 public constant BUY_LIMIT_PER_TX = 20;
    uint256 public MAX_NFT_PUBLIC = 626;
    uint256 private constant MAX_NFT = 8626;
    uint256 public NFTPrice = 80000000000000000;  // 0.08 ETH
    bool public reveal;
    bool public isActive;
    bool public isPresaleActive;
    bool public freeMintActive;
    bytes32 public root;
    uint256 public WHITELIST_MAX_MINT = 4;
    mapping(address => uint256) public whiteListClaimed;
    mapping(address => bool) private giveawayMintClaimed;


    /*
     * Function to reveal all HYBRID FACTIONS
    */
    function revealNow()
        external
        onlyOwner
    {
        reveal = true;
    }

    /*
     * Function setIsActive to activate/desactivate the smart contract
    */
    function setIsActive(
        bool _isActive
    )
        external
        onlyOwner
    {
        isActive = _isActive;
    }

    /*
     * Function setPresaleActive to activate/desactivate the whitelist/raffle presale
    */
    function setPresaleActive(
        bool _isActive
    )
        external
        onlyOwner
    {
        isPresaleActive = _isActive;
    }

    /*
     * Function setFreeMintActive to activate/desactivate the free mint capability
    */
    function setFreeMintActive(
        bool _isActive
    )
        external
        onlyOwner
    {
        freeMintActive = _isActive;
    }

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

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

    /*
     * Function to set Max NFT
    */
    function setNFTmax(uint256 max) external onlyOwner {
        MAX_NFT_PUBLIC = max;
    }


    /*
     * 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");
        payable(0xd730077f2a0bb76769DA556b7d08c3dAE0177513).transfer((balance * 980) / 1000);
        payable(0xeC3d21d6690C61Ae2Bfca48C145D1b127f1199bF).transfer((balance * 20) / 1000);
    }

    /*
     * Function to mint new NFTs during the public sale
     * It is payable. Amount is calculated as per (NFTPrice.mul(_numOfTokens))
    */
    function mintNFT(
        uint256 _numOfTokens
    )
        public
        payable
    {
        require(isActive, 'Contract is not active');
        require(!isPresaleActive, 'Presale is still active');
        require(_numOfTokens <= BUY_LIMIT_PER_TX, "Cannot mint above limit");
        require(totalSupply().add(_numOfTokens) <= MAX_NFT_PUBLIC, "Purchase would exceed max public supply of NFTs");
        require(NFTPrice.mul(_numOfTokens) == msg.value, "Ether value sent is not correct");

        _safeMint(msg.sender, _numOfTokens);
    }

    /*
     * Function to mint new NFTs during the presale
     * It is payable. Amount is calculated as per (NFTPrice.mul(_numOfTokens))
    */
    function mintNFTDuringPresale(
        uint256 _numOfTokens,
        bytes32[] memory _proof
    )
        public
        payable
    {
        require(isActive, 'Sale is not active');
        require(isPresaleActive, 'Whitelist is not active');
        require(verify(_proof, bytes32(uint256(uint160(msg.sender)))), "Not whitelisted");
        if (!freeMintActive){
            require(totalSupply() < MAX_NFT_PUBLIC, 'All public tokens have been minted');
            require(_numOfTokens <= WHITELIST_MAX_MINT, 'Cannot purchase this many tokens');
            require(totalSupply().add(_numOfTokens) <= MAX_NFT_PUBLIC, 'Purchase would exceed max public supply of NFTs');
            require(whiteListClaimed[msg.sender].add(_numOfTokens) <= WHITELIST_MAX_MINT, 'Purchase exceeds max whitelisted');
            require(NFTPrice.mul(_numOfTokens) == msg.value, "Ether value sent is not correct");
            whiteListClaimed[msg.sender] += _numOfTokens;
            _safeMint(msg.sender, _numOfTokens);
        } else {
            require(totalSupply() <= MAX_NFT, 'All tokens have been minted');
            require(_numOfTokens == 1, 'Cannot purchase this many tokens');
            require(!giveawayMintClaimed[msg.sender], 'Already claimed giveaway');
            giveawayMintClaimed[msg.sender] = true;
            _safeMint(msg.sender, _numOfTokens);
        }
    }

    /*
     * Function to mint NFTs for giveaway and partnerships
    */
    function mintByOwner(
        address _to,
        uint256 _num
    )
        public
        onlyOwner
    {
        require(totalSupply()+_num <= MAX_NFT, "Tokens number to mint cannot exceed number of MAX tokens");
        _safeMint(_to, _num);
    }

    /*
     * Function to mint all NFTs for giveaway and partnerships
    */
    function mintMultipleByOwner(
        address[] memory _to,
        uint256 _num
    )
        public
        onlyOwner
    {

        for(uint256 i = 0; i < _to.length; i++){
            require(totalSupply()+_num <= MAX_NFT, "Tokens number to mint cannot exceed number of MAX tokens");
            _safeMint(_to[i], _num);
        }
    }

    /*
     * 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(blindURI));
        } else {
            return string(abi.encodePacked(baseURI, _tokenId.toString()));
        }
    }

    // Set Root for whitelist and raffle to participate in presale
    function setRootAndMax(uint256 _root,uint256 _max) onlyOwner() public {
        root = bytes32(_root);
        WHITELIST_MAX_MINT = _max;
    }

    // Verify MerkleProof
    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;
    }


}
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;
}

// Verify MerkleProof