contract TribeProducts is ERC721Enumerable , Ownable{
    using SafeMath for uint256;
    // PRODUCT DEFINITIONS
    struct Product {
        address payable owner;
        string name;
        uint256 price;
        uint256[] tokens;
        uint256 amount;
        address payable publisher;
    }

    uint256 public idCounter;
    string public tokenURIPrefix = "https://us-central1-tribe-staging.cloudfunctions.net/app/getTokenMetadata/";
    string public contractName;
    mapping(string => Product) internal products;
    // Optional mapping for token URIs
    mapping(uint256 => string) private _tokenURIs;
    // Map tribes and tokens to products
    // Products themselves have tribe and tokens information. So both ways to query are possible.
    // See "tribeTokens" and "tokenTribe"
    mapping(uint256 => string) public tokenProduct; // ID => name

    uint256 public tribeFeePercent;

    address payable public TribeAccount;

    event NewProduct(address owner, string name, uint256 price, uint256 amount);
    event PublisherFeePaid(address publisher, uint256 amount);
    event OwnerSharePaid1(address owner, uint256 amount, uint256 eventId);
    event OwnerSharePaid2(address owner, uint256 amount, uint256 eventId);

    event ChangedTribeFeePercent(uint256 tribeFeePercent);
    event ChangedTribeAccount(address tribeAccount);

    constructor() public {
        contractName = "Tribe Product";
    }

    /**
     * @dev Sets the share cut for Tribe company of the contract that's
     *  charged to the seller on a successful sale
     * @param _tribeFeePercent - Share amount, from 0 to 100
     */
    function setTribeFeePercent(uint256 _tribeFeePercent) external onlyOwner {

        require(
            _tribeFeePercent < 100,
            "The Tribe cut should be between 0 and 100"
        );

        tribeFeePercent = _tribeFeePercent;
        emit ChangedTribeFeePercent(_tribeFeePercent);
    }

    /**
     * @dev Sets the share cut for Tribe company of the contract that's
     *  charged to the seller on a successful sale
     * @param _tribeAccount - Share amount, from 0 to 100
     */
    function setTribeAccount(address payable _tribeAccount) external onlyOwner {

        require(
            _tribeAccount != address(0x0),
            "The Tribe account should be not 0 address"
        );

        TribeAccount = _tribeAccount;
        emit ChangedTribeAccount(_tribeAccount);
    }

    /**
     * @dev Returns an URI for a given token ID.
     * Throws if the token ID does not exist. May return an empty string.
     * @param tokenId uint256 ID of the token to query
     */
    function tokenURI(uint256 tokenId) external view returns (string memory) {
        require(
            _exists(tokenId),
            "ERC721Metadata: URI query for nonexistent token"
        );
        return _tokenURIs[tokenId];
    }

    /**
     * @dev Internal function to set the token URI for a given token.
     * Reverts if the token ID does not exist.
     * @param tokenId uint256 ID of the token to set its URI
     * @param uri string URI to assign
     */
    function _setTokenURI(uint256 tokenId, string memory uri) internal {
        require(
            _exists(tokenId),
            "ERC721Metadata: URI set of nonexistent token"
        );
        _tokenURIs[tokenId] = uri;
    }

    /**
     * @dev Gets the total amount of tokens stored by the contract
     * @return uint256 representing the total amount of tokens
     */
    function name() public view returns (string memory) {
        return (contractName);
    }

    function uint2str(uint256 _i)
        internal
        pure
        returns (string memory _uintAsString)
    {
        if (_i == 0) {
            return "0";
        }
        uint256 j = _i;
        uint256 len;
        while (j != 0) {
            len++;
            j /= 10;
        }
        bytes memory bstr = new bytes(len);
        uint256 k = len - 1;
        while (_i != 0) {
            bstr[k--] = bytes1(uint8(48 + (_i % 10)));
            _i /= 10;
        }
        return string(bstr);
    }

    /**
     * @dev  Create a product to sell
     * @param _name name of the product
     * @param _price  price of the product
     * @param _amount  of the ERC721 tokens we want to sell
     * @param _publisher  if there is no specific company than the artist himself
     * @return bool true if the transaction was successful
     */
    function createProduct(
        string memory _name,
        uint256 _price,
        uint256 _amount,
        address payable _publisher
    ) public returns (bool) {
        require(products[_name].owner == address(0x0)); //product must not exist already

        // create new product
        products[_name] = Product({
            owner: msg.sender,
            name: _name,
            price: _price,
            tokens: new uint256[](0),
            amount: _amount,
            publisher: _publisher
        });
        emit NewProduct(msg.sender, _name, _price, _amount);
        return true;
    }

    /**
     * @dev Payable function to buy ERC721 tokens of a product
     * @param _name name of the product to buy
     * @return bool true if the transaction was successful
     */
    function buyProduct(string memory _name) public payable returns (bool) {
        //returns(bool){
        Product storage p = products[_name];
        require(p.owner != address(0x0), "product not exist"); // product must exist
        require(p.price <= msg.value, "not enough eth"); // cost of product must be met
        require(p.amount > 0, "not enough product");

        // buy as many products as value can buy
        uint256 v = msg.value;
        while (v >= p.price && p.amount > 0) {
            idCounter += 1;
            uint256 newTokenId = idCounter;

            // generate new product token
            _mint(msg.sender, newTokenId);
            // CONNECT TOKEN TO PRODUCT
            tokenProduct[newTokenId] = _name;
            products[_name].tokens.push(newTokenId);
            string memory newTokenIdString = uint2str(newTokenId);
            string memory uriOfNewToken = string(
                abi.encodePacked(tokenURIPrefix, newTokenIdString)
            );
            _setTokenURI(newTokenId, uriOfNewToken);
            //
            v -= p.price;
            p.amount -= 1;
        }

        uint256 spentValue = msg.value - v;
        uint saleShareAmount = 0;

         if (tribeFeePercent > 0){
            saleShareAmount = saleShareAmount + ((spentValue / 100) * tribeFeePercent);
            spentValue -= saleShareAmount;
            TribeAccount.transfer(saleShareAmount);
        }

        p.owner.transfer(spentValue);
        emit OwnerSharePaid2(p.owner, spentValue, 2);
        //transfer the left amount of the sent value that has not been used to mint products back to buyer
        msg.sender.transfer(v);
        return true;
    }

    /**
     * @dev CHeck if the msg.sender own of the tokens of the product
     * @param _name name of the product to check for
     * @return bool true if the msg.sender is the owner of at least one token of the product
     */
    function checkProduct(string memory _name) public view returns (bool) {
        Product storage p = products[_name];
        bool x = false;
        uint256 i;
        while (i < p.tokens.length && x == false) {
            if (ownerOf(p.tokens[i]) == msg.sender) {
                x = true;
            } else {
                x = false;
                i++;
            }
        }
        return x;
    }
    function mintForCreator(string memory _name) public {
         Product storage p = products[_name];
         require(msg.sender == p.owner, "Must be creator");
         while(p.amount > 0) {
            idCounter += 1;
            uint256 newTokenId = idCounter;
            _mint(p.owner, newTokenId);
             // CONNECT TOKEN TO PRODUCT
            tokenProduct[newTokenId] = _name;
            products[_name].tokens.push(newTokenId);
            string memory newTokenIdString = uint2str(newTokenId);
            string memory uriOfNewToken = string(
                abi.encodePacked(tokenURIPrefix, newTokenIdString)
            );
            _setTokenURI(newTokenId, uriOfNewToken);
            p.amount -= 1;
         }
    }

    /**
     * @dev Gets the owner of the product
     * @param _name name of the product to check for
     * @return address representing the owner
     */
    function productOwner(string memory _name) public view returns (address) {
        return products[_name].owner;
    }
//
    /**
     * @dev Gets the amount of the product
     * @param _name name of the product to check for
     * @return left amount of tokens that can be created of a product
     */
    function productAmount(string memory _name) public view returns (uint256) {
        return products[_name].amount;
    }

    /**
     * @dev Gets the price of the product
     * @param _name name of the product to check for
     * @return uint256 representing the price of the product
     */
    function productPrice(string memory _name) public view returns (uint256) {
        return products[_name].price;
    }

    /**
     * @dev Gets all the tokens of the product
     * @param _name name of the product to check for
     * @return uint256[] representing the IDs of the tokens
     */
    function productTokens(string memory _name)
        public
        view
        returns (uint256[] memory)
    {
        return products[_name].tokens;
    }

    /**
     * @dev Gets tribe's address which the product is created for
     * @param _name name of the product to check for
     * @return address representing the tribe
     */
    function productPublisher(string memory _name)
        public
        view
        returns (address)
    {
        return products[_name].publisher;
    }
}
/**
 * define new products, which can be bought
 * Products will be minted on demand
 */
function name() public view returns (string memory) {
    return (contractName);
}

/**
 * @dev Gets the total amount of tokens stored by the contract
 * @return uint256 representing the total amount of tokens
 */