contract CBERC1155Token is ERC1155Mintable {
    using SafeMath for uint256;
    bytes4 constant private INTERFACE_SIGNATURE_URI = 0x0e89341c;

    // from => operator => id => allowance
    mapping(address => mapping(address => mapping(uint256 => uint256))) allowances;
    string constant private _name = "ColorBlock's NFT";
    string constant private _symbol = "NFT@ColorBlock";
    /*
    constructor() public {
    }
    */
    function name() public view virtual returns (string memory) {
        return _name;
    }

    /**
     * @dev See {IERC721Metadata-symbol}.
     */
    function symbol() public view virtual returns (string memory) {
        return _symbol;
    }
    /**
        @dev MUST emit on any successful call to approve(address _spender, uint256 _id, uint256 _currentValue, uint256 _value)
    */
    event Approval(address indexed _owner, address indexed _spender, uint256 indexed _id, uint256 _oldValue, uint256 _value);

    /**
        @notice Allow other accounts/contracts to spend tokens on behalf of msg.sender
        @dev MUST emit Approval event on success.
        To minimize the risk of the approve/transferFrom attack vector (see https://docs.google.com/document/d/1YLPtQxZu1UAvO9cZ1O2RPXBbT0mooh4DYKjA_jp-RLM/), this function will throw if the current approved allowance does not equal the expected _currentValue, unless _value is 0.
        @param _spender      Address to approve
        @param _id           ID of the Token
        @param _currentValue Expected current value of approved allowance.
        @param _value        Allowance amount
    */
    function approve(address _spender, uint256 _id, uint256 _currentValue, uint256 _value) external {
        address real_owner = msg.sender;
        uint256 balance = super._balanceOf(real_owner,_id);
        require(balance >= _value,"No enough balance to approve.");
        require(allowances[real_owner][_spender][_id] == _currentValue);
        allowances[real_owner][_spender][_id] = _value;
        
        super._setApprovalForAll(_spender,true);
        emit Approval(real_owner, _spender, _id, _currentValue, _value);
    }

    /**
        @notice Queries the spending limit approved for an account
        @param _id       ID of the Token
        @param _owner    The owner allowing the spending
        @param _spender  The address allowed to spend.
        @return          The _spender's allowed spending balance of the Token requested
     */
    function allowance(address _owner, address _spender, uint256 _id) external view returns (uint256){
        return allowances[_owner][_spender][_id];
    }

    // ERC1155
    /*
    function supportsInterface(bytes4 _interfaceId)
    external virtual override(ERC1155Mintable)
    view
    returns (bool) {
         ERC165(address(this)).supportsInterface(_interfaceId);
    }
    */
    function supportsInterface(bytes4 _interfaceId)
    public virtual override
    view
    returns (bool) {
        if (_interfaceId == INTERFACE_SIGNATURE_URI) {
            return true;
        } else {
            return super.supportsInterface(_interfaceId);
        }
    }

    function checkTransfer(address _from, uint256 _id, uint256 _value) 
        internal {
        if (msg.sender != _from) {
            require(allowances[_from][msg.sender][_id] >= _value, "insufficient sender right.");
            allowances[_from][msg.sender][_id] = allowances[_from][msg.sender][_id].sub(_value);       
            //super._setApprovalForAll(_from,true);   
        }
    }

    function postTransfer(address _from)
        internal view {
        if (msg.sender != _from) {
            /*
            mapping(uint256 => uint256) storage ids_right = allowances[_from][msg.sender];
            uint256 len = ids_right.length;
            bool has_allowance = false;
            for(uint256 i=0;i<len;i++)
			{
                if(ids_right[i] > 0)
                {
                    has_allowance = true;
                }
            }
            if(!has_allowance){
                super.setApprovalForAll(_from,false);
            }
            */
        }
    }
    /**
        @notice Transfers value amount of an _id from the _from address to the _to addresses specified. Each parameter array should be the same length, with each index correlating.
        @dev MUST emit Transfer event on success.
        Caller must have sufficient allowance by _from for the _id/_value pair, or isApprovedForAll must be true.
        Throws if `_to` is the zero address.
        Throws if `_id` is not a valid token ID.
        Once allowance is checked the target contract's safeTransferFrom function is called.
        @param _from    source addresses
        @param _to      target addresses
        @param _id      ID of the Token
        @param _value   transfer amounts
        @param _data    Additional data with no specified format, sent in call to `_to`
    */
    function safeTransferFrom(address _from, address _to, uint256 _id, uint256 _value, bytes calldata _data) external override /*payable*/  {
        checkTransfer(_from,_id,_value);
        super._safeTransferFrom(_from, _to, _id, _value, _data);
        postTransfer(_from);
    }

    /**
        @notice Send multiple types of Tokens from a 3rd party in one transfer (with safety call)
        @dev MUST emit Transfer event per id on success.
        Caller must have a sufficient allowance by _from for each of the id/value pairs.
        Throws on any error rather than return a false flag to minimize user errors.
        Once allowances are checked the target contract's safeBatchTransferFrom function is called.
        @param _from    Source address
        @param _to      Target address
        @param _ids     Types of Tokens
        @param _values  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[] calldata _ids, uint256[] calldata _values, bytes calldata _data) external override /*payable*/ {
        if (msg.sender != _from) {
            for (uint256 i = 0; i < _ids.length; ++i) {
                checkTransfer(_from,_ids[i],_values[i]);
            }
        }
        super._safeBatchTransferFrom(_from, _to, _ids, _values, _data);
        postTransfer(_from);
    }

  function isCBNftToken(address tokenAddress) public view 
  returns(bool) {
      if( !Address.isContract(tokenAddress) ) 
      {
        return false;
      }
    
      bool result = false;
      string memory ex_name = CBERC1155Token(tokenAddress).name();
      string memory ex_symbol = CBERC1155Token(tokenAddress).symbol();   
              
      result = (keccak256(abi.encodePacked(ex_name)) == keccak256(abi.encodePacked(_name))) 
            && (keccak256(abi.encodePacked(ex_symbol)) == keccak256(abi.encodePacked(_symbol))) 
            && (supportsInterface(INTERFACE_SIGNATURE_URI) );     
      return result;
  }

  function isApprovedOrOwner(address owner, address spender, uint256 tokenId,uint256 value) external view returns (bool) {
    if(!_exists(tokenId) || (address(0) == spender))
    {
      return(false);      
    }
    uint256 balance = super._balanceOf(spender,tokenId);
    if(balance >= value)
    {
        return(true);
    }
    //return false;
    return (allowances[owner][spender][tokenId] >= value);
    //return (allowance(owner,spender,tokenId) >= value);
  }  

  function exists(uint256 tokenId) external view returns (bool) {
    
    return(_exists(tokenId));    
  }

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

  function isOwner(address owner, uint256 tokenId) external view returns (bool){
    if(!_exists(tokenId) || (address(0) == owner))
    {
      return(false);      
    }
    uint256 balance = super._balanceOf(owner,tokenId);
    return balance != 0 ;
  }
}
/**
 * @title CBERC1155Token
 * 
 */
function symbol() public view virtual returns (string memory) {
    return _symbol;
}

/**
 * @dev See {IERC721Metadata-symbol}.
 */