// Contract: SocietyOfTheHourglass
// Target function: function shopMultiple( uint256[] memory _ids, uint256[] memory _qtys, bytes memory _signature ) public payable
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;
    }
}


// Dependency file: @openzeppelin/contracts/token/ERC1155/extensions/ERC1155Burnable.sol


// pragma solidity ^0.8.0;

// import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";

/**
 * @dev Extension of {ERC1155} that allows token holders to destroy both their
 * own tokens and those that they have been approved to use.
 *
 * _Available since v3.1._
 */
abstract contract ERC1155Burnable is ERC1155 {
    function burn(
        address account,
        uint256 id,
        uint256 value
    ) public virtual {
        require(
            account == _msgSender() || isApprovedForAll(account, _msgSender()),
            "ERC1155: caller is not owner nor approved"
        );

        _burn(account, id, value);
    }

    function burnBatch(
        address account,
        uint256[] memory ids,
        uint256[] memory values
    ) public virtual {
        require(
            account == _msgSender() || isApprovedForAll(account, _msgSender()),
            "ERC1155: caller is not owner nor approved"
        );

        _burnBatch(account, ids, values);
    }
}


// Dependency file: @openzeppelin/contracts/token/ERC1155/extensions/ERC1155Supply.sol


// pragma solidity ^0.8.0;

// import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";

/**
 * @dev Extension of ERC1155 that adds tracking of total supply per id.
 *
 * Useful for scenarios where Fungible and Non-fungible tokens have to be
 * clearly identified. Note: While a totalSupply of 1 might mean the
 * corresponding is an NFT, there is no guarantees that no other token with the
 * same id are not going to be minted.
 */
abstract contract ERC1155Supply is ERC1155 {
    mapping(uint256 => uint256) private _totalSupply;

    /**
     * @dev Total amount of tokens in with a given id.
     */
    function totalSupply(uint256 id) public view virtual returns (uint256) {
        return _totalSupply[id];
    }

    /**
     * @dev Indicates weither any token exist with a given id, or not.
     */
    function exists(uint256 id) public view virtual returns (bool) {
        return ERC1155Supply.totalSupply(id) > 0;
    }

    /**
     * @dev See {ERC1155-_mint}.
     */
    function _mint(
        address account,
        uint256 id,
        uint256 amount,
        bytes memory data
    ) internal virtual override {
        super._mint(account, id, amount, data);
        _totalSupply[id] += amount;
    }

    /**
     * @dev See {ERC1155-_mintBatch}.
     */
    function _mintBatch(
        address to,
        uint256[] memory ids,
        uint256[] memory amounts,
        bytes memory data
    ) internal virtual override {
        super._mintBatch(to, ids, amounts, data);
        for (uint256 i = 0; i < ids.length; ++i) {
            _totalSupply[ids[i]] += amounts[i];
        }
    }

    /**
     * @dev See {ERC1155-_burn}.
     */
    function _burn(
        address account,
        uint256 id,
        uint256 amount
    ) internal virtual override {
        super._burn(account, id, amount);
        _totalSupply[id] -= amount;
    }

    /**
     * @dev See {ERC1155-_burnBatch}.
     */
    function _burnBatch(
        address account,
        uint256[] memory ids,
        uint256[] memory amounts
    ) internal virtual override {
        super._burnBatch(account, ids, amounts);
        for (uint256 i = 0; i < ids.length; ++i) {
            _totalSupply[ids[i]] -= amounts[i];
        }
    }
}


// Dependency file: contracts/CabinetOfCuriosities.sol


/*
*
* Society of the Hourglass: Cabinet of Curiosities
* 
* Contract by Matt Casanova [Twitter: @DevGuyThings]
* 
* Website: https://www.societyofthehourglass.com/
* Mint Page: https://app.hashku.com/team/society-of-the-hourglass/cabinet-of-curiosities/mint
*
*/

// pragma solidity 0.8.9;

// import "contracts/imports/VerifySignature.sol";
// import "contracts/imports/Withdraw.sol";
// import "contracts/imports/Group.sol";
// import "contracts/imports/ExternalAccount.sol";
// import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";
// import "@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Burnable.sol";
// import "@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Supply.sol";
// import "@openzeppelin/contracts/access/Ownable.sol";

contract CabinetOfCuriosities is 
    Ownable, 
    Withdraw, 
    Group, 
    VerifySignature, 
    ExternalAccount,
    ERC1155, 
    ERC1155Burnable
{
    mapping(uint256 => uint256) private _totalSupply;

    struct CabinetItem {
        uint256 price;
        uint256 amountMinted;
        uint256 amountAllowed;
        uint256 maxMintPerAddress;
        uint256 mainCollectionQty;
    }
    mapping(uint256 => CabinetItem) public items;

    /// @notice How many items someone can mint
    uint256 public maxMintEachAddress = 2;
    mapping(address => uint) public hasMinted;

    string private cabinetUri = "https://storage.hashku.com/api/soth/cabinet/{id}.json";

    uint256 constant private DUST = 0;
    uint256 constant private SCOPE = 1;
    uint256 constant private BOOK = 2;
    uint256 constant private BOT = 3;
    uint256 constant private POSTER = 4;
    uint256 constant private BOOK_CLOSED = 5;
    uint256 constant private BOT_ACTIVE = 6;

    uint256 public currentRaffleIndex = 11;

    mapping(uint256 => bool) public nigelWinners;

    constructor() ERC1155("https://storage.hashku.com/soth/cabinet/{id}.json") {
        // forget me dust
        items[DUST] = CabinetItem(60000000000000000, 0, 222, 2, 2);

        // pocket spectroscope
        items[SCOPE] = CabinetItem(120000000000000000, 0, 166, 2, 4);

        // book of epochs
        items[BOOK] = CabinetItem(170000000000000000, 0, 99, 2, 4);

        // autotranslator bot
        items[BOT] = CabinetItem(170000000000000000, 0, 99, 2, 4);

        // wanted poster of nigel
        items[POSTER] = CabinetItem(180000000000000000, 0, 99, 2, 4);

        // opened book of epochs
        items[BOOK_CLOSED] = CabinetItem(0, 0, 99, 2, 0);

        // activated autotranslator bot
        items[BOT_ACTIVE] = CabinetItem(0, 0, 99, 2, 0);

        // Raffle tickets will be IDs 11-99
    }

    /*
    * @notice Total supply of a given item
    */
    function totalSupply(uint256 id) public view returns (uint256) {
        return _totalSupply[id];
    }

    function _mint(
        address account,
        uint256 id,
        uint256 amount,
        bytes memory data
    ) internal override {
        super._mint(account, id, amount, data);
        _totalSupply[id] += amount;
    }

    function _burn(
        address account,
        uint256 id,
        uint256 amount
    ) internal override {
        super._burn(account, id, amount);
        _totalSupply[id] -= amount;
    }

    /*
    * @notice Set overall quanitity a user is allowed to mint
    */
    function setMaxMintEachAddress(uint256 _amt) public onlyOwner {
        maxMintEachAddress = _amt;
    }

    /*
    * @notice Set a Nigel raffle ticket as a winner
    */
    function setNigelWinner(uint256 _id) public onlyOwner {
        nigelWinners[_id] = true;
    }

    /*
    * @notice Allow updating the item data
    */
    function setItem( uint256 _id, uint256 _price, uint256 _amountAllowed, uint256 _maxMintPerAddress, uint256 _mainCollectionQty ) public onlyOwner {
        items[_id].price = _price;
        items[_id].amountAllowed = _amountAllowed;
        items[_id].maxMintPerAddress = _maxMintPerAddress;
        items[_id].mainCollectionQty = _mainCollectionQty;
    }

    function uri(uint256) public view override returns (string memory) {
        return cabinetUri;
    }
    
    function setURI(string memory _newuri) public onlyOwner {
        cabinetUri = _newuri;
    }

    /*
    * @notice Shop for a single item of varyiable quantity
    */
    function shop( uint256 _id, uint256 _qty, bytes memory _signature ) public payable {
        // check signature - will be provided by Hashku
        require(verifySignature("SHOP", group, _id, _qty, _signature), "invsig");
        require(_id < 5, "invitm");
        require(_qty * items[_id].price == msg.value, "incfnds");

        mintInternal(msg.sender, _id, _qty);
    }