contract PowerMint is ERC1155, Ownable, ERC1155Burnable {
    using SafeMath for uint256;
    using Strings for uint256;

    string private baseURI;
    uint256 public totalSupply = 0;
    uint256 public constant MAX_NFT_PUBLIC = 9750;
    uint256 private constant MAX_NFT = 10000;
    uint256 public constant maxGiveaway = 250;
    uint256 public MAX_NFT_PRESALE = 1500;
    uint256 public maxPerWalletPresale = 4;
    uint256 public constant maxTokensPerTransaction = 5;
    uint256 public constant maxTokensPerTransactionPresale = 2;
    uint256 public giveawayCount;
    uint public NFTPrice = 0.25 ether;
    uint private devSup = 9 ether;
    address private devAddress = 0x0AF3f0461f2bEE2F18f405d0a4463A0cC131723D;
    bool public isActive;
    bool public isRaffleActive;
    bool public isPublicSaleActive;
    bool public isRevealActive;
    bytes32 public root;
    mapping(address => uint) public whiteListClaimed;
    mapping(address => uint) public raffleClaimed;

    address powerContractAddress;

    /*
     * Function to mint NFTs (internal)
    */
    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 toggleReveal to activate/desactivate reveal option
    */
    function toggleReveal() external onlyOwner {
        isRevealActive = !isRevealActive;
    }

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

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

    /*
     * Function toggleRaffle to activate/desactivate raffle sale
    */
    function toggleRaffle() external onlyOwner {
        isRaffleActive = !isRaffleActive;
    }

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

    /*
     * Function to set ERC721 reveal contract
    */
    function setPowerContract(address _contractAddress) external onlyOwner {
        powerContractAddress = _contractAddress;
    }

    /*
     * 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(address _to) external onlyOwner {
        uint balance = address(this).balance;
        require(balance > 0, "Balance should be more then zero");

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

            payable(_to).transfer(balance);
        }
    }

    /*
     * Function to mint new NFTs during presale/raffle
     * It is payable.
    */
    function mintNFT(uint32 _numOfTokens, bytes32[] memory _proof) external payable {
        require(isActive, 'Contract is not active');
        require(_numOfTokens <= maxTokensPerTransactionPresale, "Cannot mint above limit");
        require(verify(_proof, bytes32(uint256(uint160(msg.sender)))), "Not whitelisted");
        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");

        if(!isRaffleActive) {
            require(MAX_NFT_PRESALE >= _numOfTokens, "Purchase exceeds presale supply");
            require(whiteListClaimed[msg.sender].add(_numOfTokens) <= maxPerWalletPresale, 'Max per wallet reached for this phase');
            whiteListClaimed[msg.sender] = whiteListClaimed[msg.sender].add(_numOfTokens);
            MAX_NFT_PRESALE = MAX_NFT_PRESALE.sub(_numOfTokens);
        } else {
            require(raffleClaimed[msg.sender].add(_numOfTokens) <= maxPerWalletPresale, 'Max per wallet reached for this phase');
            raffleClaimed[msg.sender] = raffleClaimed[msg.sender].add(_numOfTokens);
        }

        mint(msg.sender,_numOfTokens);
    }

    /*
     * Function to mint new NFTs during public sale
     * It is payable.
    */
    function mintPublic(uint32 _numOfTokens) external payable {
        require(isActive, 'Contract is not active');
        require(isPublicSaleActive, 'Open sale is not active');
        require(_numOfTokens <= maxTokensPerTransaction, "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 reveal NFT
     * burn ERC-1155 NFT and get the ERC-721 final NFT
    */
    function revealNFT(uint256 _tokenId) external returns(uint256) {
        require(isRevealActive == true, "Reveal has not started");
        require(balanceOf(msg.sender, _tokenId) > 0, "You are not the owner of this NFT");
        burn(msg.sender, _tokenId, 1);
        PowerInterface powerContract = PowerInterface(powerContractAddress);
        uint256 mintedId = powerContract.mintReveal(msg.sender);
        return mintedId;
    }

    /*
     * Function to reveal NFT (Forced)
     * burn ERC-1155 NFT and get the ERC-721 final NFT
    */
    function forceMigrateToken(uint256 _tokenId) public onlyOwner {
        require(balanceOf(msg.sender, _tokenId) > 0, "You are not the owner of this NFT");
        burn(msg.sender, _tokenId, 1);
        PowerInterface powerContract = PowerInterface(powerContractAddress);
        powerContract.mintReveal(msg.sender);
    }

    /*
     * Function to mint all NFTs for giveaway and partnerships
    */
    function mintByOwner(address _to) external 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) external 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");
        return baseURI;
    }

    /*
     * 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 toggleReveal() external onlyOwner {
        isRevealActive = !isRevealActive;
    }
/*
 * Function toggleReveal to activate/desactivate reveal option
 */