contract ERC1155Base is IERC1155, IERC165, ERC1155Metadata, Ownable, ReentrancyGuard {
  using Address for address;

  /***********************************|
  |        Variables and Events       |
  |__________________________________*/

  // onReceive function signatures
  bytes4 constant internal ERC1155_RECEIVED_VALUE = 0xf23a6e61;
  bytes4 constant internal ERC1155_BATCH_RECEIVED_VALUE = 0xbc197c81;

  address constant internal NULL_ADDR = address(0);

  string private constant ERR = "ERC1155Base";

  // Contract name
  string public name;

  // Contract symbol
  string public symbol;

  // ProxyRegistry
  address private immutable _proxyRegistry;

  // initializer
  address private immutable _initializer;

  // Objects balances
  mapping (address => mapping(uint256 => uint256)) internal balances;

  // Operator Functions
  mapping (address => mapping(address => bool)) internal operators;

    // Max mints per transaction
  uint256 private _maxTxMint;

  // The CAP of mintable tokenIds
  uint256 private _cap;

  // ETH price of one tokenIds
  uint256 private _tokenPrice;

  // TokenId counter, 1 minted in ctor
  uint256 private _currentTokenId;

  // MintAllowed
  uint256 private mintAllowed;

  // ERC20 mint token
  address private _erc20MintAddress;

  // ERC20 mint token price per NFT 
  uint256 private _erc20MintPrice;

  /***********************************|
  |             Initialization        |
  |__________________________________*/

  constructor(address initializer_, address proxyRegistry) {
    _proxyRegistry = proxyRegistry;
    _initializer = initializer_;
  }

  function initialize(
    address owner_,
    string memory name_,
    string memory symbol_
   ) external
  {
    require(msg.sender == _initializer, ERR);

    _transferOwnership(owner_);
    name = name_;
    symbol = symbol_;

    // Mint our first token
    balances[owner_][0] = 1;
    emit TransferSingle(msg.sender, NULL_ADDR, owner(), 0, 1);
  }

  /**
   * @dev Clone Initialization.
   */
  function initialize(
    address owner_,
    string memory name_,
    string memory symbol_,
    uint256 cap_,
    uint256 maxPerTx_,
    uint256 price_) external
  {
    require(msg.sender == _initializer, ERR);

    _transferOwnership(owner_);

    name = name_;
    symbol = symbol_;
    _cap = cap_;
    _maxTxMint = maxPerTx_;
    _tokenPrice = price_;
    // Mint our first token
    balances[owner_][0] = 1;
    _currentTokenId = 1;
    mintAllowed = 1;
    emit TransferSingle(msg.sender, NULL_ADDR, owner_, 0, 1);
  }

  /***********************************|
  |     Public Transfer Functions     |
  |__________________________________*/

  /**
   * @notice Transfers amount amount of an _id from the _from address to the _to address specified
   * @param _from    Source address
   * @param _to      Target address
   * @param _id      ID of the token type
   * @param _amount  Transfered amount
   * @param _data    Additional data with no specified format, sent in call to `_to`
   */
  function safeTransferFrom(address _from, address _to, uint256 _id, uint256 _amount, bytes memory _data)
    external override
  {
    require((msg.sender == _from) || isApprovedForAll(_from, msg.sender), ERR);
    require(_to != address(0), ERR);
 
    _safeTransferFrom(_from, _to, _id, _amount);
    _callonERC1155Received(_from, _to, _id, _amount, _data);
  }

  /**
   * @notice Send multiple types of Tokens from the _from address to the _to address (with safety call)
   * @param _from     Source addresses
   * @param _to       Target addresses
   * @param _ids      IDs of each token type
   * @param _amounts  Transfer amounts per token type
   * @param _data     Additional data with no specified format, sent in call to `_to`
   */
  function safeBatchTransferFrom(address _from, address _to, uint256[] memory _ids, uint256[] memory _amounts, bytes memory _data)
    external override
  {
    // Requirements
    require((msg.sender == _from) || isApprovedForAll(_from, msg.sender), ERR);
    require(_to != address(0), ERR);

    _safeBatchTransferFrom(_from, _to, _ids, _amounts);
    _callonERC1155BatchReceived(_from, _to, _ids, _amounts, _data);
  }

  function unsafeBatchMint(address[] calldata _tos, uint256[] calldata _counts, uint256[] calldata _ids) external onlyOwner {
    uint256 idOffset = 0;
    for (uint256 i = 0; i < _tos.length; ++i) {
      uint256 idOffsetEnd = idOffset + _counts[i];
      require (idOffsetEnd <= _ids.length, ERR);
      {
        uint256 curE = _ids[idOffset] >> 8;
        uint256 mask = 0;
        for (; idOffset < idOffsetEnd; ++idOffset) {
          // Update storage balance of previous bin
          uint256 elem = _ids[idOffset] >> 8;
          uint256 id = uint256(1) << (_ids[idOffset] & 0xFF);
          if (elem != curE) {
            balances[_tos[i]][curE] |= mask;
            curE = elem;
            mask = 0;
          }
          mask |= id;
          emit TransferSingle(_tos[i], NULL_ADDR, _tos[i], _ids[idOffset], 1);
        }
        balances[_tos[i]][curE] |= mask;
      }

      uint256[] memory amounts = new uint256[](_counts[i]);
      for (uint pos = 0; pos < _counts[i]; ++pos)
        amounts[pos] = 1;
      _callonERC1155BatchReceived(address(0), _tos[i], _ids[idOffsetEnd - _counts[i]:idOffsetEnd], amounts, '');
    }
  }

  function unsafeBatchMessage(address[] calldata _tos, uint256[] calldata _counts, uint256[] calldata _ids) external onlyOwner {
    uint256 idOffset = 0;
    for (uint256 i = 0; i < _tos.length; ++i) {
      uint256 idOffsetEnd = idOffset + _counts[i];
      require (idOffsetEnd <= _ids.length, ERR);
      for (; idOffset < idOffsetEnd; ++idOffset) {
        emit TransferSingle(_tos[i], NULL_ADDR, _tos[i], _ids[idOffset], 1);
      }
    }
  }

  /**
   * @dev mint
   */
  function mint(address to, uint256 numMint, bool erc20) external payable nonReentrant {
    uint256 tid = _currentTokenId;
    uint256 tidEnd = _currentTokenId + numMint;

    require(mintAllowed != 0 &&
      numMint > 0 &&
      numMint <= _maxTxMint &&
      tidEnd <= _cap, ERR
    );

    if (erc20) {
      require(msg.value == 0 &&
        _erc20MintAddress != address(0) &&
        IERC20(_erc20MintAddress).transferFrom(msg.sender, address(this), numMint * _erc20MintPrice),
        ERR
      );
    } else {
      require(msg.value >= numMint * _tokenPrice, ERR);
    }

    {
      uint256 mask = 0;
      uint256 curE = tid >> 8;
      for (; tid < tidEnd; ++tid) {
        // Update storage balance of previous bin
        uint256 elem = tid >> 8;
        uint256 id = uint256(1) << (tid & 0xFF);
        if (elem != curE) {
          balances[to][curE] |= mask;
          curE = elem;
          mask = 0;
        }
        mask |= id;
        emit TransferSingle(msg.sender, NULL_ADDR, to, tid, 1);
      }
      balances[to][curE] |= mask;
      _currentTokenId += numMint;
    }

    if (!erc20) {
      uint256 dust = msg.value - (numMint * _tokenPrice);
      if (dust > 0) payable(msg.sender).transfer(dust);
    }

    { 
      uint256[] memory ids = new uint256[](numMint);
      uint256[] memory amounts = new uint256[](numMint);
      for (uint256 i = 0; i < numMint; ++i) {
        ids[i] = tid - numMint--;
        amounts[i] = 1;
      }
      _callonERC1155BatchReceived(address(0), to, ids, amounts, '');
    }
  }

  /**
   * @dev Distribute rewards
   */
  function withdraw(bool erc20) external onlyOwner {
    if (erc20) {
      uint256 availableAmount = IERC20(_erc20MintAddress).balanceOf(address(this));
      if (availableAmount > 0) {
        IERC20(_erc20MintAddress).transferFrom(address(this), msg.sender, availableAmount);
      }
    } else {
      uint256 availableAmount = address(this).balance;
      if (availableAmount > 0) {
        payable(msg.sender).transfer(availableAmount);
      }
    }
  }

  function setBaseMetadataURI(string memory _newBaseMetadataURI) external onlyOwner {
    _setBaseMetadataURI(_newBaseMetadataURI);
  }

  function setErc20MintData(address erc20Address, uint256 pricePerNft) external onlyOwner {
    _erc20MintAddress = erc20Address;
    _erc20MintPrice = pricePerNft;
  }

  /***********************************|
  |    Internal Transfer Functions    |
  |__________________________________*/

  /**
   * @notice Transfers amount amount of an _id from the _from address to the _to address specified
   * @param _from    Source address
   * @param _to      Target address
   * @param _id      ID of the token type
   * @param _amount  Transfered amount
   */
  function _safeTransferFrom(address _from, address _to, uint256 _id, uint256 _amount)
    internal
  {
    require(_amount == 1, ERR);

    // Update balances
    _transferOwner(_from, _to, _id);

    // Emit event
    emit TransferSingle(msg.sender, _from, _to, _id, _amount);
  }

  /**
   * @notice Verifies if receiver is contract and if so, calls (_to).onERC1155Received(...)
   */
  function _callonERC1155Received(address _from, address _to, uint256 _id, uint256 _amount, bytes memory _data)
    internal
  {
    // Check if recipient is contract
    if (_to.isContract()) {
      bytes4 retval = IERC1155TokenReceiver(_to).onERC1155Received(msg.sender, _from, _id, _amount, _data);
      require(retval == ERC1155_RECEIVED_VALUE, ERR);
    }
  }

  /**
   * @notice Send multiple types of Tokens from the _from address to the _to address (with safety call)
   * @param _from     Source addresses
   * @param _to       Target addresses
   * @param _ids      IDs of each token type
   * @param _amounts  Transfer amounts per token type
   */
  function _safeBatchTransferFrom(address _from, address _to, uint256[] memory _ids, uint256[] memory _amounts)
    internal
  {
    uint256 len = _ids.length;

    require(len == _amounts.length, ERR);

    // Executing all transfers
    for (uint256 i = 0; i < len; ++i) {
      require(_amounts[i] == 1, ERR);
      // Update storage balance of previous bin
      _transferOwner(_from, _to, _ids[i]);
    }

    // Emit event
    emit TransferBatch(msg.sender, _from, _to, _ids, _amounts);
  }

  /**
   * @notice Verifies if receiver is contract and if so, calls (_to).onERC1155BatchReceived(...)
   */
  function _callonERC1155BatchReceived(address _from, address _to, uint256[] memory _ids, uint256[] memory _amounts, bytes memory _data)
    internal
  {
    // Pass data if recipient is contract
    if (_to.isContract()) {
      bytes4 retval = IERC1155TokenReceiver(_to).onERC1155BatchReceived(msg.sender, _from, _ids, _amounts, _data);
      require(retval == ERC1155_BATCH_RECEIVED_VALUE, ERR);
    }
  }


  /***********************************|
  |         Operator Functions        |
  |__________________________________*/

  /**
   * @notice Enable or disable approval for a third party ("operator") to manage all of caller's tokens
   * @param _operator  Address to add to the set of authorized operators
   * @param _approved  True if the operator is approved, false to revoke approval
   */
  function setApprovalForAll(address _operator, bool _approved)
    external override
  {
    // Update operator status
    operators[msg.sender][_operator] = _approved;
    emit ApprovalForAll(msg.sender, _operator, _approved);
  }

  /**
   * @notice Queries the approval status of an operator for a given owner
   * @param _owner     The owner of the Tokens
   * @param _operator  Address of authorized operator
   * @return isOperator if the operator is approved, false if not
   */
  function isApprovedForAll(address _owner, address _operator)
    public view override returns (bool isOperator)
  {
    // Whitelist OpenSea proxy contract for easy trading.
    ProxyRegistry proxyRegistry = ProxyRegistry(_proxyRegistry);
    if (address(proxyRegistry.proxies(_owner)) == _operator) {
      return true;
    }

    return operators[_owner][_operator];
  }


  /***********************************|
  |         Balance Functions         |
  |__________________________________*/

  /**
   * @notice Get the balance of an account's Tokens
   * @param _owner  The address of the token holder
   * @param _id     ID of the Token
   * @return The _owner's balance of the Token type requested
   */
  function balanceOf(address _owner, uint256 _id)
    public view override returns (uint256)
  {
    return _isOwner(_owner, _id) ? 1 : 0;
  }

  /**
   * @notice Get the balance of multiple account/token pairs
   * @param _owners The addresses of the token holders
   * @param _ids    ID of the Tokens
   * @return        The _owner's balance of the Token types requested (i.e. balance for each (owner, id) pair)
   */
  function balanceOfBatch(address[] memory _owners, uint256[] memory _ids)
    public view override returns (uint256[] memory)
  {
    require(_owners.length == _ids.length, ERR);

    // Variables
    uint256[] memory batchBalances = new uint256[](_owners.length);

    // Iterate over each owner and token ID
    for (uint256 i = 0; i < _owners.length; i++) {
      batchBalances[i] = _isOwner(_owners[i], _ids[i]) ? 1 : 0;
    }

    return batchBalances;
  }


  /***********************************|
  |          ERC165 Functions         |
  |__________________________________*/

  /**
   * @dev INTERFACE_SIGNATURE_ERC165 = bytes4(keccak256("supportsInterface(bytes4)"));
   */
  bytes4 constant private INTERFACE_SIGNATURE_ERC165 = 0x01ffc9a7;

  /**
   *  @dev INTERFACE_SIGNATURE_ERC1155 =
   * bytes4(keccak256("safeTransferFrom(address,address,uint256,uint256,bytes)")) ^
   * bytes4(keccak256("safeBatchTransferFrom(address,address,uint256[],uint256[],bytes)")) ^
   * bytes4(keccak256("balanceOf(address,uint256)")) ^
   * bytes4(keccak256("balanceOfBatch(address[],uint256[])")) ^
   * bytes4(keccak256("setApprovalForAll(address,bool)")) ^
   * bytes4(keccak256("isApprovedForAll(address,address)"));
   */
  bytes4 constant private INTERFACE_SIGNATURE_ERC1155 = 0xd9b67a26;

  /**
   * @notice Query if a contract implements an interface
   * @param _interfaceID  The interface identifier, as specified in ERC-165
   * @return `true` if the contract implements `_interfaceID` and
   */
  function supportsInterface(bytes4 _interfaceID) external pure override returns (bool) {
    if (_interfaceID == INTERFACE_SIGNATURE_ERC165 ||
        _interfaceID == INTERFACE_SIGNATURE_ERC1155) {
      return true;
    }
    return false;
  }

  /***********************************|
  |         balance Functions         |
  |__________________________________*/

  function _isOwner(address _from, uint256 _id) internal view returns (bool) {
    return (balances[_from][_id >> 8] & (uint256(1) << (_id & 0xFF))) != 0;
  }

  function _transferOwner(address _from, address _to, uint256 _id) internal {
    uint256 elem = _id >> 8;
    uint256 id = uint256(1) << (_id & 0xFF);

    if (_from != NULL_ADDR) {
      require((balances[_from][elem] & id) != 0, ERR);
      balances[_from][elem] &=~id;
    }
    
    if (_to != NULL_ADDR) {
      balances[_to][elem] |= id;
    }
  }
}
/**
 * @dev Implementation of Multi-Token Standard contract
 */
function balanceOf(address _owner, uint256 _id)
  public view override returns (uint256)
{
  return _isOwner(_owner, _id) ? 1 : 0;
}

/**
 * @notice Get the balance of an account's Tokens
 * @param _owner  The address of the token holder
 * @param _id     ID of the Token
 * @return The _owner's balance of the Token type requested
 */