contract DeveryERC721MetadataToken is ERC721,Admined {

    constructor() ERC721("Devery Protocol", "EVENFT") public {
      
    }

    address[] public tokenIdToProduct;
    mapping(address => uint) public totalAllowedProducts;
    mapping(address => uint) public totalMintedProducts;
    DeveryRegistry deveryRegistry;
    IERC20 public token;

    event TokenUpdated(address indexed oldToken, address indexed newToken);


    function setToken(address _token) public onlyAdmin   {
        TokenUpdated(address(token), _token);
        token = IERC20(_token);
    }

    /**
      * @dev modifier to enforce that only the brand that created a given product can change it
      * this modifier will check the core devery registry to fetch the brand address.
      */
    modifier brandOwnerOnly(address _productAddress){
        address productBrandAddress;
        (,productBrandAddress,,,,,) = deveryRegistry.products(_productAddress);
        require(productBrandAddress == msg.sender);
        _;
    }

    /**
      * @dev Allow contract admins to set the address of Core Devery Registry contract
      */
    function setDeveryRegistryAddress(address _deveryRegistryAddress) external onlyAdmin  {
        deveryRegistry = DeveryRegistry(_deveryRegistryAddress);
    }

    /**
      * @dev adjusts the maximum mintable amount of a certain product
      */
    function setMaximumMintableQuantity(address _productAddress, uint _quantity) public brandOwnerOnly(_productAddress) {
        require(_quantity >= totalMintedProducts[_productAddress] || _quantity == 0);
        totalAllowedProducts[_productAddress] = _quantity;
    }

    /**
     * @dev mint a new ERC721 token for a given product and assing it to the original product brand;
     */
    function claimProduct(address _productAddress,uint _quantity) public  brandOwnerOnly(_productAddress) {
        require(totalAllowedProducts[_productAddress] == 0 || totalAllowedProducts[_productAddress] >= totalMintedProducts[_productAddress] + _quantity);
        //********************************************************************charges the fee****************************************
        address productBrandAddress;
        address appAccountAddress;
        address appFeeAccount;
        address deveryFeeAccount;
        uint appFee;
        uint deveryFee;
        (,productBrandAddress,,,,,) = deveryRegistry.products(_productAddress);
        (,appAccountAddress,,) = deveryRegistry.brands(productBrandAddress);
        (,,appFeeAccount,appFee,) = deveryRegistry.apps(appAccountAddress);
        deveryFee = deveryRegistry.fee();
        deveryFeeAccount = deveryRegistry.feeAccount();
        if (appFee > 0) {
            token.transferFrom(productBrandAddress, appFeeAccount, appFee*_quantity);
        }
        if (deveryFee > 0) {
            token.transferFrom(productBrandAddress, deveryFeeAccount, deveryFee*_quantity);
        }
        //********************************************************************charges the fee****************************************
        for(uint i = 0;i<_quantity;i++){
            uint nextId = tokenIdToProduct.length;
            tokenIdToProduct.push(_productAddress);
            _mint(msg.sender,nextId);
        }

        totalMintedProducts[_productAddress]+=_quantity;
    }

    /**
      * @dev returns the products owned by a given ethereum address
      */
    function getProductsByOwner(address _owner) external view returns (address[] memory){
        address[] memory products = new address[](balanceOf(_owner));
        uint counter = 0;
        for(uint i = 0; i < tokenIdToProduct.length;i++){
            if(ownerOf(i) == _owner){
                products[counter] = tokenIdToProduct[i];
                counter++;
            }
        }
        return products;
    }
    
    function setTokenUri(uint tokenId,string memory tokenUri) public {
        //"only the token owner is allowed to do this"
        require(ownerOf(tokenId) == msg.sender);
        _setTokenURI(tokenId, tokenUri);
    }
}
function setMaximumMintableQuantity(address _productAddress, uint _quantity) public brandOwnerOnly(_productAddress) {
    require(_quantity >= totalMintedProducts[_productAddress] || _quantity == 0);
    totalAllowedProducts[_productAddress] = _quantity;
}

/**
 * @dev adjusts the maximum mintable amount of a certain product
 */