contract BabyDoodleArt is ERC1155, Ownable {
    string public constant name = "Baby Doodle Art";
    string public constant symbol = "BDART";
    
    using SafeMath for uint256;
    using Strings for uint256;
    uint256 public totalSupply = 0;
    string private baseURI;
    string private blindURI;
    uint256 public constant BUY_LIMIT_PER_TX = 15;
    uint256 public constant MAX_NFT_PUBLIC = 8799;
    uint256 private constant MAX_NFT = 8999;
    uint256 public constant maxGiveaway=200;
    uint256 public NFTPrice = 80000000000000000;  // 0.08 ETH
    bool public reveal;
    bool public isActive;
    bool public isPresaleActive;
    bytes32 public root;
    uint256 public constant WHITELIST_MAX_MINT = 15;
    mapping(address => uint256) private whiteListClaimed;
    uint256 public giveawayCount;
    /*
     * Function to reveal all NFTs
    */
    function revealNow() 
        external 
        onlyOwner 
    {
        reveal = true;
    }
    
    /*
     * Function to mint NFTs
    */
    function mint(address to, uint32 count) internal {
        if (count > 1) {
            uint256[] memory ids = new uint256[](uint256(count));
            uint256[] memory amounts = new uint256[](uint256(count));

            for (uint32 i = 0; i < count; i++) {
                ids[i] = totalSupply + i;
                amounts[i] = 1;
            }

            _mintBatch(to, ids, amounts, "");
        } else {
            _mint(to, totalSupply, 1, "");
        }

        totalSupply += count;
    }
    
    /*
     * Function setIsActive to activate/desactivate the smart contract
    */
    function setIsActive(
        bool _isActive
    ) 
        external 
        onlyOwner 
    {
        isActive = _isActive;
    }
    
    /*
     * Function setPresaleActive to activate/desactivate the presale  
    */
    function setPresaleActive(
        bool _isActive
    ) 
        external 
        onlyOwner 
    {
        if(_isActive==true){
            NFTPrice=80000000000000000;  // 0.08 ETH
        }else{
            NFTPrice=100000000000000000;  // 0.1 ETH
        }
        isPresaleActive = _isActive;
    }
    
    /*
     * Function to set Base and Blind URI 
    */
    function setURIs(
        string memory _blindURI, 
        string memory _URI
    ) 
        external 
        onlyOwner 
    {
        blindURI = _blindURI;
        baseURI = _URI;
    }
    
    /*
     * Function to withdraw collected amount during minting by the owner
    */
    function withdraw(
    ) 
        public 
        onlyOwner 
    {
        address[3] memory addresses = [
            0xE9E7d48F3a373c00844B5412951F7fd9D9605eE6,
            0xfbc7A660e4820DE480Cfdf65CfEaE6dE317aD0ca,
            0xBF3227FdE241CF0cff652f8c4BfC99724140e7b6
        ];

        uint32[3] memory shares = [
            uint32(4750),
            uint32(4750),
            uint32(500)
        ];

        uint256 balance = address(this).balance;

        for (uint32 i = 0; i < addresses.length; i++) {
            uint256 amount = i == addresses.length - 1 ? address(this).balance : balance * shares[i] / 10000;
            payable(addresses[i]).transfer(amount);
        }
    }

    /*
     * Function to withdraw collected amount during minting by the owner
    */
    function emergencyWithdraw(
        address _to
    ) 
        public 
        onlyOwner 
    {
        uint balance = address(this).balance;
        require(balance > 0, "Balance should be more then zero");
        payable(_to).transfer(balance);
    }
    
    /*
     * Function to mint new NFTs during the public sale
     * It is payable. Amount is calculated as per (NFTPrice.mul(_numOfTokens))
    */
    function mintNFT(
        uint32 _numOfTokens
    ) 
        public 
        payable 
    {
    
        require(isActive, 'Contract is not active');
        require(!isPresaleActive, 'Presale still 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( msg.value >= NFTPrice.mul(_numOfTokens), "Ether value sent is not correct");
        mint(msg.sender,_numOfTokens);
    }
    
    /*
     * Function to mint new NFTs during the presale
     * It is payable. Amount is calculated as per (NFTPrice.mul(_numOfTokens))
    */ 
    function mintNFTDuringPresale(
        uint32 _numOfTokens,
        bytes32[] memory _proof
    ) 
        public 
        payable
    {
        require(isActive, 'Contract is not active');
        require(isPresaleActive, 'Presale not active');
        require(verify(_proof, bytes32(uint256(uint160(msg.sender)))), "Not whitelisted");
        require(totalSupply.sub(giveawayCount) < MAX_NFT_PUBLIC, 'All public tokens have been minted');
        require(_numOfTokens <= WHITELIST_MAX_MINT, 'Cannot purchase this many tokens');
        require(totalSupply.add(_numOfTokens).sub(giveawayCount) <= MAX_NFT_PUBLIC, 'Purchase would exceed max public supply of NFTs');
        require(whiteListClaimed[msg.sender].add(_numOfTokens) <= WHITELIST_MAX_MINT, 'Purchase exceeds max whiteed');
        require( msg.value >= NFTPrice.mul(_numOfTokens), "Ether value sent is not correct");
        mint(msg.sender,_numOfTokens);
        whiteListClaimed[msg.sender] += _numOfTokens;
           
    }
    
    /*
     * Function to mint all 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");
        mint(_to,1);
        giveawayCount=giveawayCount.add(1);
    }
    
    /*
     * Function to mint all NFTs for giveaway and partnerships
    */
    function mintMultipleByOwner(
        address[] memory _to
    ) 
        public 
        onlyOwner
    {
        require(totalSupply.add(_to.length) < MAX_NFT, "Tokens number to mint cannot exceed number of MAX tokens");
        require(giveawayCount.add(_to.length)<=maxGiveaway,"Cannot do that much giveaway");
        for(uint256 i = 0; i < _to.length; i++){
            mint(_to[i],1);
        }
        giveawayCount=giveawayCount.add(_to.length);
    }
    
    /*
     * Function to get token URI of given token ID
     * URI will be blank untill totalSupply reaches MAX_NFT_PUBLIC
    */
    function uri(
        uint256 _tokenId
    ) 
        public 
        view 
        virtual 
        override 
        returns (string memory) 
    {
        require(_tokenId<totalSupply, "ERC1155Metadata: URI query for nonexistent token");
        if (!reveal) {
            return string(abi.encodePacked(blindURI));
        } else {
            return string(abi.encodePacked(baseURI, _tokenId.toString()));
        }
    }
    
    /*
     * Function to set the merkle root
    */
    function setRoot(uint256 _root) onlyOwner() public {
        root = bytes32(_root);
    }
    
    /*
     * 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;
    }
}
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 to verify the proof
 */