contract ERC1155Tradable is ERC1155, ERC1155MintBurn, Ownable {

  address proxyRegistryAddress;
  uint256 private _currentTokenID = 0;
  mapping (uint256 => address) public creators;
  mapping (uint256 => uint256) public tokenSupply;
  mapping (uint256 => string) public URIs;
  mapping (address => bool) public minters;

  // Contract name
  string public name;
  // Contract symbol
  string public symbol;

  /**
   * @dev Require msg.sender to be the creator of the token id
   */
  modifier creatorOnly(uint256 _id) {
    require(creators[_id] == msg.sender, "ERC1155Tradable#creatorOnly: ONLY_CREATOR_ALLOWED");
    _;
  }

  /**
   * @dev Require msg.sender to own more than 0 of the token id
   */
  modifier ownersOnly(uint256 _id) {
    require(balances[msg.sender][_id] > 0, "ERC1155Tradable#ownersOnly: ONLY_OWNERS_ALLOWED");
    _;
  }

  constructor(
    string memory _name,
    string memory _symbol,
    address _proxyRegistryAddress
  ) public {
    name = _name;
    symbol = _symbol;
    proxyRegistryAddress = _proxyRegistryAddress;
    minters[msg.sender] = true;
  }

  function uri(
    uint256 _id
  ) public view returns (string memory) {
    require(_exists(_id), "ERC721Tradable#uri: NONEXISTENT_TOKEN");
    return URIs[_id];
  }


  function totalSupply(
    uint256 _id
  ) public view returns (uint256) {
    return tokenSupply[_id];
  }

  function create(
    address _to,
    address _creator,
    uint256 _id,
    uint256 _initialSupply,
    string calldata _uri,
    bytes calldata _data
  ) external returns (uint256) {
    require(minters[msg.sender], "Minter role is required.");

    creators[_id] = _creator;
    URIs[_id] = _uri;

    _mint(_to, _id, _initialSupply, _data);
    tokenSupply[_id] = _initialSupply;
    return _id;
  }

  function batchCreate(
    address[] calldata _tos,
    address[] calldata _creators,
    uint256[] calldata _ids,
    uint256[] calldata _initialSupplys,
    string[] calldata _uris,
    bytes calldata _data
  ) external {
    require(minters[msg.sender], "Minter role is required.");
    require(_tos.length == _ids.length, "Invalid array length.");
    for(uint i=0; i<_tos.length; i++) {
        creators[_ids[i]] = _creators[i];
        URIs[_ids[i]] = _uris[i];

        _mint(_tos[i], _ids[i], _initialSupplys[i], _data);
        tokenSupply[_ids[i]] = _initialSupplys[i];
    }

  }


  function mint(
    address _to,
    uint256 _id,
    uint256 _quantity,
    bytes memory _data
  ) public {
    require(minters[msg.sender], "Minter role is required.");
    _mint(_to, _id, _quantity, _data);
    tokenSupply[_id] = tokenSupply[_id] + _quantity;
  }

  function batchMint(
    address _to,
    uint256[] memory _ids,
    uint256[] memory _quantities,
    bytes memory _data
  ) public {
    require(minters[msg.sender], "Minter role is required.");
    for (uint256 i = 0; i < _ids.length; i++) {
      uint256 _id = _ids[i];
      uint256 quantity = _quantities[i];
      tokenSupply[_id] = tokenSupply[_id] + quantity;
    }
    _batchMint(_to, _ids, _quantities, _data);
  }

  function setCreator(
    address _to,
    uint256[] memory _ids
  ) public {
    require(_to != address(0), "ERC1155Tradable#setCreator: INVALID_ADDRESS.");
    for (uint256 i = 0; i < _ids.length; i++) {
      uint256 id = _ids[i];
      _setCreator(_to, id);
    }
  }

  /**
   * Override isApprovedForAll to whitelist user's OpenSea proxy accounts to enable gas-free listings.
   */
  function isApprovedForAll(
    address _owner,
    address _operator
  ) public view override returns (bool isOperator) {
    // Whitelist OpenSea proxy contract for easy trading.
    ProxyRegistry proxyRegistry = ProxyRegistry(proxyRegistryAddress);
    if (address(proxyRegistry.proxies(_owner)) == _operator) {
      return true;
    }

    return ERC1155.isApprovedForAll(_owner, _operator);
  }

  function _setCreator(address _to, uint256 _id) internal creatorOnly(_id)
  {
      creators[_id] = _to;
  }

  function _exists(
    uint256 _id
  ) internal view returns (bool) {
    return creators[_id] != address(0);
  }



  function setMinterRole(address _address, bool _permission) external onlyOwner {
    minters[_address] = _permission;
  }

  function setBatchURI(uint256[] calldata _ids, string calldata _uri) external {
    require(minters[msg.sender], "Minter role is required.");
    for(uint i=0; i<_ids.length; i++) {
        URIs[_ids[i]] = _uri;
    }
  }

  function setURI(uint256 _id, string calldata _uri) external {
    require(minters[msg.sender], "Minter role is required.");
    URIs[_id] = _uri;
  }

}
/**
 * @title ERC1155Tradable
 * ERC1155Tradable - ERC1155 contract that whitelists an operator address, has create and mint functionality, and supports useful standards from OpenZeppelin,
 like _exists(), name(), symbol(), and totalSupply()
 */
function isApprovedForAll(
  address _owner,
  address _operator
) public view override returns (bool isOperator) {
  // Whitelist OpenSea proxy contract for easy trading.
  ProxyRegistry proxyRegistry = ProxyRegistry(proxyRegistryAddress);
  if (address(proxyRegistry.proxies(_owner)) == _operator) {
    return true;
  }

  return ERC1155.isApprovedForAll(_owner, _operator);
}

/**
 * Override isApprovedForAll to whitelist user's OpenSea proxy accounts to enable gas-free listings.
 */