// Contract: WickedWitches
// Target function: function setSaleIsActive() public onlyOwner
 refers to a smart contract, it must implement {IERC1155Receiver-onERC1155BatchReceived} and return the
     * acceptance magic value.
     */
    function _safeBatchTransferFrom(
        address from,
        address to,
        uint256[] memory ids,
        uint256[] memory amounts,
        bytes memory data
    ) internal virtual {
        require(ids.length == amounts.length, "ERC1155: ids and amounts length mismatch");
        require(to != address(0), "ERC1155: transfer to the zero address");

        address operator = _msgSender();

        _beforeTokenTransfer(operator, from, to, ids, amounts, data);

        for (uint256 i = 0; i < ids.length; ++i) {
            uint256 id = ids[i];
            uint256 amount = amounts[i];

            uint256 fromBalance = _balances[id][from];
            require(fromBalance >= amount, "ERC1155: insufficient balance for transfer");
        unchecked {
            _balances[id][from] = fromBalance - amount;
        }
            _balances[id][to] += amount;
        }

        emit TransferBatch(operator, from, to, ids, amounts);

        _doSafeBatchTransferAcceptanceCheck(operator, from, to, ids, amounts, data);
    }

    /**
     * @dev Sets a new URI for all token types, by relying on the token type ID
     * substitution mechanism
     * https://eips.ethereum.org/EIPS/eip-1155#metadata[defined in the EIP].
     *
     * By this mechanism, any occurrence of the `\{id\}` substring in either the
     * URI or any of the amounts in the JSON file at said URI will be replaced by
     * clients with the token type ID.
     *
     * For example, the `https://token-cdn-domain/\{id\}.json` URI would be
     * interpreted by clients as
     * `https://token-cdn-domain/000000000000000000000000000000000000000000000000000000000004cce0.json`
     * for token type ID 0x4cce0.
     *
     * See {uri}.
     *
     * Because these URIs cannot be meaningfully represented by the {URI} event,
     * this function emits no events.
     */
    function _setURI(string memory newuri) internal virtual {
        _uri = newuri;
    }

    /**
     * @dev Creates `amount` tokens of token type `id`, and assigns them to `account`.
     *
     * Emits a {TransferSingle} event.
     *
     * Requirements:
     *
     * - `account` cannot be the zero address.
     * - If `account` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the
     * acceptance magic value.
     */
    function _mint(
        address account,
        uint256 id,
        uint256 amount,
        bytes memory data
    ) internal virtual {
        require(account != address(0), "ERC1155: mint to the zero address");

        address operator = _msgSender();

        _beforeTokenTransfer(operator, address(0), account, _asSingletonArray(id), _asSingletonArray(amount), data);

        _balances[id][account] += amount;
        emit TransferSingle(operator, address(0), account, id, amount);

        _doSafeTransferAcceptanceCheck(operator, address(0), account, id, amount, data);
    }

    /**
     * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {_mint}.
     *
     * Requirements:
     *
     * - `ids` and `amounts` must have the same length.
     * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155BatchReceived} and return the
     * acceptance magic value.
     */
    function _mintBatch(
        address to,
        uint256[] memory ids,
        uint256[] memory amounts,
        bytes memory data
    ) internal virtual {
        require(to != address(0), "ERC1155: mint to the zero address");
        require(ids.length == amounts.length, "ERC1155: ids and amounts length mismatch");

        address operator = _msgSender();

        _beforeTokenTransfer(operator, address(0), to, ids, amounts, data);

        for (uint256 i = 0; i < ids.length; i++) {
            _balances[ids[i]][to] += amounts[i];
        }

        emit TransferBatch(operator, address(0), to, ids, amounts);

        _doSafeBatchTransferAcceptanceCheck(operator, address(0), to, ids, amounts, data);
    }

    /**
     * @dev Destroys `amount` tokens of token type `id` from `account`
     *
     * Requirements:
     *
     * - `account` cannot be the zero address.
     * - `account` must have at least `amount` tokens of token type `id`.
     */
    function _burn(
        address account,
        uint256 id,
        uint256 amount
    ) internal virtual {
        require(account != address(0), "ERC1155: burn from the zero address");

        address operator = _msgSender();

        _beforeTokenTransfer(operator, account, address(0), _asSingletonArray(id), _asSingletonArray(amount), "");

        uint256 accountBalance = _balances[id][account];
        require(accountBalance >= amount, "ERC1155: burn amount exceeds balance");
    unchecked {
        _balances[id][account] = accountBalance - amount;
    }

        emit TransferSingle(operator, account, address(0), id, amount);
    }

    /**
     * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {_burn}.
     *
     * Requirements:
     *
     * - `ids` and `amounts` must have the same length.
     */
    function _burnBatch(
        address account,
        uint256[] memory ids,
        uint256[] memory amounts
    ) internal virtual {
        require(account != address(0), "ERC1155: burn from the zero address");
        require(ids.length == amounts.length, "ERC1155: ids and amounts length mismatch");

        address operator = _msgSender();

        _beforeTokenTransfer(operator, account, address(0), ids, amounts, "");

        for (uint256 i = 0; i < ids.length; i++) {
            uint256 id = ids[i];
            uint256 amount = amounts[i];

            uint256 accountBalance = _balances[id][account];
            require(accountBalance >= amount, "ERC1155: burn amount exceeds balance");
        unchecked {
            _balances[id][account] = accountBalance - amount;
        }
        }

        emit TransferBatch(operator, account, address(0), ids, amounts);
    }

    /**
     * @dev Hook that is called before any token transfer. This includes minting
     * and burning, as well as batched variants.
     *
     * The same hook is called on both single and batched variants. For single
     * transfers, the length of the `id` and `amount` arrays will be 1.
     *
     * Calling conditions (for each `id` and `amount` pair):
     *
     * - When `from` and `to` are both non-zero, `amount` of ``from``'s tokens
     * of token type `id` will be  transferred to `to`.
     * - When `from` is zero, `amount` tokens of token type `id` will be minted
     * for `to`.
     * - when `to` is zero, `amount` of ``from``'s tokens of token type `id`
     * will be burned.
     * - `from` and `to` are never both zero.
     * - `ids` and `amounts` have the same, non-zero length.
     *
     * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
     */
    function _beforeTokenTransfer(
        address operator,
        address from,
        address to,
        uint256[] memory ids,
        uint256[] memory amounts,
        bytes memory data
    ) internal virtual {}

    function _doSafeTransferAcceptanceCheck(
        address operator,
        address from,
        address to,
        uint256 id,
        uint256 amount,
        bytes memory data
    ) private {
        if (to.isContract()) {
            try IERC1155Receiver(to).onERC1155Received(operator, from, id, amount, data) returns (bytes4 response) {
                if (response != IERC1155Receiver.onERC1155Received.selector) {
                    revert("ERC1155: ERC1155Receiver rejected tokens");
                }
            } catch Error(string memory reason) {
                revert(reason);
            } catch {
                revert("ERC1155: transfer to non ERC1155Receiver implementer");
            }
        }
    }

    function _doSafeBatchTransferAcceptanceCheck(
        address operator,
        address from,
        address to,
        uint256[] memory ids,
        uint256[] memory amounts,
        bytes memory data
    ) private {
        if (to.isContract()) {
            try IERC1155Receiver(to).onERC1155BatchReceived(operator, from, ids, amounts, data) returns (
                bytes4 response
            ) {
                if (response != IERC1155Receiver.onERC1155BatchReceived.selector) {
                    revert("ERC1155: ERC1155Receiver rejected tokens");
                }
            } catch Error(string memory reason) {
                revert(reason);
            } catch {
                revert("ERC1155: transfer to non ERC1155Receiver implementer");
            }
        }
    }

    function _asSingletonArray(uint256 element) private pure returns (uint256[] memory) {
        uint256[] memory array = new uint256[](1);
        array[0] = element;

        return array;
    }
}


/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * By default, the owner account will be the one that deploys the contract. This
 * can later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
abstract contract Ownable is Context {
    address private _owner;

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Initializes the contract setting the deployer as the initial owner.
     */
    constructor () {
        address msgSender = _msgSender();
        _owner = msgSender;
        emit OwnershipTransferred(address(0), msgSender);
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        require(owner() == _msgSender(), "Ownable: caller is not the owner");
        _;
    }

}

pragma solidity ^0.8.0;

contract WickedWitches is ERC1155, Ownable {
    
    uint256 public constant WITCH_PRICE   = 40000000000000000;      //WEI 40000000000000000 = 0.04ETH
    uint256 public constant TOTAL_SUPPLY  = 10000;
    uint256 public constant MAX_PURCHASE  = 20;
    
    uint256 private constant BRONZE       = 20001;  //4E21 //1000
    uint256 private constant SILVER       = 20002;  //4E22 //420
    uint256 private constant GOLD         = 20003;  //4E23 //150
    uint256 private constant DIAMOND      = 20004;  //4E24 //30
    
    uint256 public constant CANDY         = 10401;  //28A1 //250 MAX

    uint256 private _currentWitchIdx      = 1;      //[00001 - 10000] {0001 - 2710}
    uint256 private _currentBronzePetIdx  = 10001;  //[10001 - 10100] {2711 - 2774}
    uint256 private _currentSilverPetIdx  = 10101;  //[10101 - 10160] {2775 - 27B0}
    uint256 private _currentGoldPetIdx    = 10161;  //[10161 - 10190] {27B1 - 27CE}
    uint256 private _currentDiamondPetIdx = 10191;  //[10191 - 10205] {27CF - 27DD}
    uint256 private _currentMaskIdx       = 10206;  //[10206 - 10230] {27DE - 27F6}
    
    bool public saleIsActive = false;               //RELEASE = false
    
    event TokenMinted(uint tokenId, address sender);
    
    //CONTRACT META
    string public name   = "Wicked Witches";
    string public symbol = "WW";
    
    constructor() ERC1155("http://wwitches.com/contract/{id}") {
        _mint(msg.sender, CANDY, 250, "");
        //reserve giveaway witches
        for (uint i = 0; i < 30; i++) {
            _mint(msg.sender, _currentWitchIdx, 1, "");
            _currentWitchIdx++;
        }
        //reserve giveaway pets
        for (uint i = 0; i < 5; i++) {
            _mint(msg.sender, _currentDiamondPetIdx, 1, "");
            _currentDiamondPetIdx++;
        }
    }

    function mintWitchTo(uint numberOfTokens) public payable {
        require(saleIsActive, "Mint is not available right now");
        require(numberOfTokens <= MAX_PURCHASE, "Can only mint 20 tokens at a time");
        require(currentWitches()+numberOfTokens <= TOTAL_SUPPLY, "Purchase would exceed max supply");
        require(WITCH_PRICE*numberOfTokens <= msg.value, "Ether value sent is not correct");
        
        for (uint i = 0; i < numberOfTokens; i++) {
            _mint(msg.sender, _currentWitchIdx, 1, "");
            if(_currentWitchIdx * 0x64 % 0x3E5 <= 0x63){
	            _mint(msg.sender, BRONZE, 1, "");
		    }
		    if(_currentWitchIdx * 0x64 % 0x945 <= 0x63){
	            _mint(msg.sender, SILVER, 1, "");
		    }
		    if(_currentWitchIdx * 0x64 % 0x1A0A <= 0x63){
	            _mint(msg.sender, GOLD, 1, "");
		    }
		    if(_currentWitchIdx * 0x25 % 0x247A <= 0x1B){
	            _mint(msg.sender, DIAMOND, 1, "");
		    }
            
            emit TokenMinted(_currentWitchIdx, msg.sender);
            _currentWitchIdx++;
        }
        
    }
    
    function craftBronzePetTo() public payable {
        require(balanceOf(msg.sender, BRONZE) >= 10, "Not enough bronze shards, you need at least 10");
        _burn(msg.sender, BRONZE, 10);
        _mint(msg.sender, _currentBronzePetIdx, 1, "");
        _currentBronzePetIdx++;
    }
    
    function craftSilverPetTo() public payable {
        require(balanceOf(msg.sender, SILVER) >= 7, "Not enough silver shards, you need at least 7");
        _burn(msg.sender, SILVER, 7);
        _mint(msg.sender, _currentSilverPetIdx, 1, "");
        _currentSilverPetIdx++;
    }
    
    function craftGoldPetTo() public payable {
        require(balanceOf(msg.sender, GOLD) >= 5, "Not enough gold shards, you need at least 5");
        _burn(msg.sender, GOLD, 5);
        _mint(msg.sender, _currentGoldPetIdx, 1, "");
        _currentGoldPetIdx++;
    }
    
    function craftDiamondPetTo() public payable {
        require(balanceOf(msg.sender, DIAMOND) >= 3, "Not enough diamond shards, you need at least 3");
        _burn(msg.sender, DIAMOND, 3);
        _mint(msg.sender, _currentDiamondPetIdx, 1, "");
        _currentDiamondPetIdx++;
    }
    
    function craftMaskTo() public payable {
        require(balanceOf(msg.sender, CANDY) >= 10, "Not enough candies, you need at least 10");
        _burn(msg.sender, CANDY, 10);
        _mint(msg.sender, _currentMaskIdx, 1, "");
        _currentMaskIdx++;
    }
    
    //PUBLIC
    function currentWitches() public view returns (uint256) {
        return _currentWitchIdx-1;
    }
    
    function witchesLeft() public view returns (uint256) {
        return TOTAL_SUPPLY - currentWitches();
    }