contract CybordMercenariesNFT is ERC1155, Ownable {
    string public constant name = "CybordMercenariesNFT";
    string public constant symbol = "CM";
    
    using SafeMath for uint256;
    using Strings for uint256;
    using MerkleProof for bytes32[];

    // Contract constants
    uint256 public constant  BUY_LIMIT_PER_TX    = 10;
    uint256 private constant MAX_NFT             = 8888;
    uint256 public constant  MAX_NFT_PUBLIC      = 8738;
  
    uint256 public constant  VIP_NFT_PRICE       = 100000000000000000;  // VIP price 0.10 ETH
    uint256 public constant  WHITELIST_NFT_PRICE = 120000000000000000;  // Whitelist price 0.12 ETH
    uint256 public constant  NFTPrice            = 150000000000000000;  // Public price 0.15 ETH

    uint256 public totalSupply = 0;
    string private baseURI;
    string private blindURI;

    bool public reveal = false;
    string public contractState  = "NOT_ACTIVE"; // Good values : NOT_ACTIF, VIP_SALE, WHITELIST_SALE, RAFFLE_SALE, PUBLIC_SALE

    // Presale info
    uint256 public constant PRESALE_MAX_MINT = 2;
    bytes32 public vipRoot;
    bytes32 public whitelistRoot;
    bytes32 public raffleRoot;

    mapping(address => uint256) private presaleNFTsClaimed;
    uint256 public giveawayCount;

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

    /*
     * Function to set the whitelist merkle root
    */
    function setWhitelistRoot(bytes32 _whitelistRoot) onlyOwner() public {
        whitelistRoot = _whitelistRoot;
    }

    /*
     * Function to set the vip merkle root
    */
    function setVIPlistRoot(bytes32 _vipRoot) onlyOwner() public {
        vipRoot = _vipRoot;
    }
    
    /*
     * Function to set the vip merkle root
    */
    function setRaffleRoot(bytes32 _raffleRoot) onlyOwner() public {
        raffleRoot = _raffleRoot;
    }

    /*
     * Function setContractState to change the state of the contract 
    */
    function setContractState(
        string memory _contractState
    ) 
        external 
        onlyOwner 
    {
        require(
          (keccak256(abi.encodePacked(_contractState)) == keccak256(abi.encodePacked("NOT_ACTIVE")) ||
            keccak256(abi.encodePacked(_contractState)) == keccak256(abi.encodePacked("VIP_SALE")) ||
            keccak256(abi.encodePacked(_contractState)) == keccak256(abi.encodePacked("WHITELIST_SALE")) ||
            keccak256(abi.encodePacked(_contractState)) == keccak256(abi.encodePacked("RAFFLE_SALE")) ||
            keccak256(abi.encodePacked(_contractState)) == keccak256(abi.encodePacked("PUBLIC_SALE"))
          )
        , 'Provided status is invalid');
        contractState = _contractState;
    }

    /*
     * 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((keccak256(abi.encodePacked(contractState)) == keccak256(abi.encodePacked("VIP_SALE")) || keccak256(abi.encodePacked(contractState)) == keccak256(abi.encodePacked("WHITELIST_SALE"))), 'Presale not active');
        require(_numOfTokens <= PRESALE_MAX_MINT, 'Cannot purchase this many tokens during presale');

        if (keccak256(abi.encodePacked(contractState)) == keccak256(abi.encodePacked("VIP_SALE")) ) {
            require(_proof.verify(vipRoot, keccak256(abi.encodePacked(msg.sender))), "You are not in the VIP list");
            require(msg.value >= VIP_NFT_PRICE.mul(_numOfTokens), string(abi.encodePacked("Ether value sent is not correct, must be VIP_NFT_PRICE * _numOfTokens")));
        } else {
            require(_proof.verify(whitelistRoot, keccak256(abi.encodePacked(msg.sender))), "You are not in the Whitelist list");
            require(msg.value >= WHITELIST_NFT_PRICE.mul(_numOfTokens), string(abi.encodePacked("Ether value sent is not correct, must be WHITELIST_NFT_PRICE * _numOfTokens")));
        }

        require(totalSupply.sub(giveawayCount) < MAX_NFT_PUBLIC, 'All public tokens have been minted');
        require(totalSupply.add(_numOfTokens).sub(giveawayCount) <= MAX_NFT_PUBLIC, 'Purchase would exceed max public supply of NFTs');
        require(presaleNFTsClaimed[msg.sender].add(_numOfTokens) <= PRESALE_MAX_MINT, 'Purchase exceeds max whiteed');

        mint(msg.sender, _numOfTokens);
        presaleNFTsClaimed[msg.sender] += _numOfTokens;
    }
 
    function mintNFTRaffle(
        uint32 _numOfTokens,
        bytes32[] memory _proof
    ) 
        public 
        payable
    {
        require(keccak256(abi.encodePacked(contractState)) == keccak256(abi.encodePacked("RAFFLE_SALE")), 'Raffle not active');
        require(
          (
            _proof.verify(vipRoot, keccak256(abi.encodePacked(msg.sender))) ||
            _proof.verify(whitelistRoot, keccak256(abi.encodePacked(msg.sender))) ||
            _proof.verify(raffleRoot, keccak256(abi.encodePacked(msg.sender)))
          ), "You are not in the Raffle authorized list");

        require(totalSupply.sub(giveawayCount) < MAX_NFT_PUBLIC, 'All public tokens have been minted');
        require(totalSupply.add(_numOfTokens).sub(giveawayCount) <= MAX_NFT_PUBLIC, 'Purchase would exceed max public supply of NFTs');
        require(msg.value >= NFTPrice.mul(_numOfTokens), string(abi.encodePacked("Ether value sent is not correct, must be NFTPrice * _numOfTokens")));

        mint(msg.sender,_numOfTokens);
    }

    /*
     * 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(keccak256(abi.encodePacked(contractState)) == keccak256(abi.encodePacked("PUBLIC_SALE")), 'Public sale 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(msg.value >= NFTPrice.mul(_numOfTokens), "Ether value sent is not correct, must be NFTPrice * _numOfTokens");
        mint(msg.sender,_numOfTokens);
    }
 
    /*
     * Function to mint all NFTs for giveaway and partnerships
    */
    function mintGiveaway(
        address _to, 
        uint256 _tokenId
    ) 
        public 
        onlyOwner
    {
        require(_tokenId >= MAX_NFT_PUBLIC, "Tokens number to mint must exceed number of public tokens");
        require(_tokenId < MAX_NFT, "Tokens number to mint cannot exceed number of MAX tokens");
        _mint(_to, _tokenId, 1, "");
        giveawayCount=giveawayCount.add(1);
    }

    /*
     * Function to reveal / hide all NFTs
    */
    function setReveal(bool _reveal) 
        external 
        onlyOwner 
    {
        reveal = _reveal;
    }
    
    /*
     * 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 to withdraw collected amount during minting by the owner
    */
    function withdraw(
        address _to
    ) 
        public 
        onlyOwner 
    {
        uint balance = address(this).balance;
        require(balance > 0, "Balance should be more then zero");
        payable(_to).transfer(balance);
    }
    
    /*
     * 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) 
    {
        if (!reveal) {
            return string(abi.encodePacked(blindURI));
        } else {
            return string(abi.encodePacked(baseURI, _tokenId.toString(), '.json'));
        }
    }
}
function mintGiveaway(
        address _to, 
        uint256 _tokenId
    ) 
        public 
        onlyOwner
    {
        require(_tokenId >= MAX_NFT_PUBLIC, "Tokens number to mint must exceed number of public tokens");
        require(_tokenId < MAX_NFT, "Tokens number to mint cannot exceed number of MAX tokens");
        _mint(_to, _tokenId, 1, "");
        giveawayCount=giveawayCount.add(1);
    }
/*
 * Function to mint all NFTs for giveaway and partnerships
 */