contract ERC721Matcha is ERC721Full, ReentrancyGuard {

    using SafeMath for uint256;

    using Address for address payable;

    // admin address, the owner of the marketplace
    address payable admin;

    address public contract_owner;

    // commission rate is a value from 0 to 100
    uint256 commissionRate;

    // last price sold or auctioned
    mapping(uint256 => uint256) public soldFor;
    
    // Mapping from token ID to sell price in Ether or to bid price, depending if it is an auction or not
    mapping(uint256 => uint256) public sellBidPrice;

    // Mapping payment address for tokenId 
    mapping(uint256 => address payable) private _wallets;

    event Sale(uint256 indexed tokenId, address indexed from, address indexed to, uint256 value);
    event Commission(uint256 indexed tokenId, address indexed to, uint256 value, uint256 rate, uint256 total);

    /*

    index   _isAuction  _sellBidPrice   Meaning
    0       true        0               Item 0 is on auction and no bids so far
    1       true        10              Item 1 is on auction and the last bid is for 10 Ethers
    2       false       0               Item 2 is not on auction nor for sell
    3       false       10              Item 3 is on sale for 10 Ethers

    */

    // Auction data
    struct Auction {

        // Parameters of the auction. Times are either
        // absolute unix timestamps (seconds since 1970-01-01)
        // or time periods in seconds.
        address payable beneficiary;
        uint auctionEnd;

        // Current state of the auction.
        address payable highestBidder;
        uint highestBid;

        // Set to true at the end, disallows any change
        bool open;

        // minimum reserve price in wei
        uint256 reserve;

    }

    // mapping auctions for each tokenId
    mapping(uint256 => Auction) public auctions;

    // Events that will be fired on changes.
    event Refund(address bidder, uint amount);
    event HighestBidIncreased(address indexed bidder, uint amount, uint256 tokenId);
    event AuctionEnded(address winner, uint amount);

    event LimitSell(address indexed from, address indexed to, uint256 amount);
    event LimitBuy(address indexed from, address indexed to, uint256 amount);
    event MarketSell(address indexed from, address indexed to, uint256 amount);
    event MarketBuy(address indexed from, address indexed to, uint256 amount);


    constructor(address _owner, address payable _admin, uint256 _commissionRate, string memory name, string memory symbol, bool _anyoneCanMint) public 
        ERC721Full(name, symbol, _anyoneCanMint) {
        admin = _admin;
        contract_owner = _owner;
        require(_commissionRate<=100, "ERC721Matcha: Commission rate has to be between 0 and 100");
        commissionRate = _commissionRate;
    }

    function canSell(uint256 tokenId) public view returns (bool) {
        return (ownerOf(tokenId)==msg.sender && !auctions[tokenId].open);
    }

    // Sell option for a fixed price
    function sell(uint256 tokenId, uint256 price, address payable wallet) public {

        // onlyOwner
        require(ownerOf(tokenId)==msg.sender, "ERC721Matcha: Only owner can sell this item");

        // cannot set a price if auction is activated
        require(!auctions[tokenId].open, "ERC721Matcha: Cannot sell an item which has an active auction");

        // set sell price for index
        sellBidPrice[tokenId] = price;

        // If price is zero, means not for sale
        if (price>0) {

            // approve the Index to the current contract
            approve(address(this), tokenId);
            
            // set wallet payment
            _wallets[tokenId] = wallet;
            
        }

    }

    // simple function to return the price of a tokenId
    // returns: sell price, bid price, sold price, only one can be non zero
    function getPrice(uint256 tokenId) public view returns (uint256, uint256, uint256) {
        if (sellBidPrice[tokenId]>0) return (sellBidPrice[tokenId], 0, 0);
        if (auctions[tokenId].highestBid>0) return (0, auctions[tokenId].highestBid, 0);
        return (0, 0, soldFor[tokenId]);
    }

    function canBuy(uint256 tokenId) public view returns (uint256) {
        if (!auctions[tokenId].open && sellBidPrice[tokenId]>0 && sellBidPrice[tokenId]>0 && getApproved(tokenId) == address(this)) {
            return sellBidPrice[tokenId];
        } else {
            return 0;
        }
    }

    // Buy option
    function buy(uint256 tokenId) public payable nonReentrant {

        // is on sale
        require(!auctions[tokenId].open && sellBidPrice[tokenId]>0, "ERC721Matcha: The collectible is not for sale");

        // transfer funds
        require(msg.value >= sellBidPrice[tokenId], "ERC721Matcha: Not enough funds");

        // transfer ownership
        address owner = ownerOf(tokenId);

        require(msg.sender!=owner, "ERC721Matcha: The seller cannot buy his own collectible");

        // we need to call a transferFrom from this contract, which is the one with permission to sell the NFT
        callOptionalReturn(this, abi.encodeWithSelector(this.transferFrom.selector, owner, msg.sender, tokenId));

        // calculate amounts
        uint256 amount4admin = msg.value.mul(commissionRate).div(100);
        uint256 amount4owner = msg.value.sub(amount4admin);

        // to owner
        (bool success, ) = _wallets[tokenId].call.value(amount4owner)("");
        require(success, "Transfer failed.");

        // to admin
        (bool success2, ) = admin.call.value(amount4admin)("");
        require(success2, "Transfer failed.");

        // close the sell
        sellBidPrice[tokenId] = 0;
        _wallets[tokenId] = address(0);

        soldFor[tokenId] = msg.value;

        emit Sale(tokenId, owner, msg.sender, msg.value);
        emit Commission(tokenId, owner, msg.value, commissionRate, amount4admin);

    }

    function canAuction(uint256 tokenId) public view returns (bool) {
        return (ownerOf(tokenId)==msg.sender && !auctions[tokenId].open && sellBidPrice[tokenId]==0);
    }

    // Instantiate an auction contract for a tokenId
    function createAuction(uint256 tokenId, uint _closingTime, address payable _beneficiary, uint256 _reservePrice) public {

        require(sellBidPrice[tokenId]==0, "ERC721Matcha: The selected NFT is open for sale, cannot be auctioned");
        require(!auctions[tokenId].open, "ERC721Matcha: The selected NFT already has an auction");
        require(ownerOf(tokenId)==msg.sender, "ERC721Matcha: Only owner can auction this item");

        auctions[tokenId].beneficiary = _beneficiary;
        auctions[tokenId].auctionEnd = _closingTime;
        auctions[tokenId].reserve = _reservePrice;
        auctions[tokenId].open = true;

        // approve the Index to the current contract
        approve(address(this), tokenId);

    }

    function canBid(uint256 tokenId) public view returns (bool) {
        if (!msg.sender.isContract() &&
            auctions[tokenId].open &&
            now <= auctions[tokenId].auctionEnd &&
            msg.sender != ownerOf(tokenId) &&
            getApproved(tokenId) == address(this)
        ) {
            return true;
        } else {
            return false;
        }
    }

    /// Bid on the auction with the value sent
    /// together with this transaction.
    /// The value will only be refunded if the
    /// auction is not won.
    function bid(uint256 tokenId) public payable nonReentrant {
        // No arguments are necessary, all
        // information is already part of
        // the transaction. The keyword payable
        // is required for the function to
        // be able to receive Ether.

        // Contracts cannot bid, because they can block the auction with a reentrant attack
        require(!msg.sender.isContract(), "No script kiddies");

        // auction has to be opened
        require(auctions[tokenId].open, "No opened auction found");

        // approve was lost
        require(getApproved(tokenId) == address(this), "Cannot complete the auction");

        // Revert the call if the bidding
        // period is over.
        require(
            now <= auctions[tokenId].auctionEnd,
            "Auction already ended."
        );

        // If the bid is not higher, send the
        // money back.
        require(
            msg.value > auctions[tokenId].highestBid,
            "There already is a higher bid."
        );

        address owner = ownerOf(tokenId);
        require(msg.sender!=owner, "ERC721Matcha: The owner cannot bid his own collectible");

        // return the funds to the previous bidder, if there is one
        if (auctions[tokenId].highestBid>0) {
            (bool success, ) = auctions[tokenId].highestBidder.call.value(auctions[tokenId].highestBid)("");
            require(success, "Transfer failed.");
            emit Refund(auctions[tokenId].highestBidder, auctions[tokenId].highestBid);
        }

        // now store the bid data
        auctions[tokenId].highestBidder = msg.sender;
        auctions[tokenId].highestBid = msg.value;
        emit HighestBidIncreased(msg.sender, msg.value, tokenId);

    }

    // anyone can execute withdraw if auction is opened and 
    // the bid time expired and the reserve was not met
    // or
    // the auction is openen but the contract is unable to transfer
    function canWithdraw(uint256 tokenId) public view returns (bool) {
        if (auctions[tokenId].open && 
            (
                (
                    now >= auctions[tokenId].auctionEnd &&
                    auctions[tokenId].highestBid > 0 &&
                    auctions[tokenId].highestBid<auctions[tokenId].reserve
                ) || 
                getApproved(tokenId) != address(this)
            )
        ) {
            return true;
        } else {
            return false;
        }
    }

    /// Withdraw a bid when the auction is not finalized
    function withdraw(uint256 tokenId) public nonReentrant returns (bool) {

        require(canWithdraw(tokenId), "Conditions to withdraw are not met");

        // transfer funds to highest bidder always
        if (auctions[tokenId].highestBid > 0) {
            (bool success, ) = auctions[tokenId].highestBidder.call.value(auctions[tokenId].highestBid)("");
            require(success, "Transfer failed.");
        }

        // finalize the auction
        delete auctions[tokenId];

    }

    function canFinalize(uint256 tokenId) public view returns (bool) {
        if (auctions[tokenId].open && 
            now >= auctions[tokenId].auctionEnd &&
            (
                auctions[tokenId].highestBid>=auctions[tokenId].reserve || 
                auctions[tokenId].highestBid==0
            )
        ) {
            return true;
        } else {
            return false;
        }
    }

    // implement the auctionFinalize including the NFT transfer logic
    function auctionFinalize(uint256 tokenId) public nonReentrant {

        require(canFinalize(tokenId), "Cannot finalize");

        if (auctions[tokenId].highestBid>0) {

            // transfer the ownership of token to the highest bidder
            address payable highestBidder = auctions[tokenId].highestBidder;

            // calculate payment amounts
            uint256 amount4admin = auctions[tokenId].highestBid.mul(commissionRate).div(100);
            uint256 amount4owner = auctions[tokenId].highestBid.sub(amount4admin);

            // to owner
            (bool success, ) = auctions[tokenId].beneficiary.call.value(amount4owner)("");
            require(success, "Transfer failed.");

            // to admin
            (bool success2, ) = admin.call.value(amount4admin)("");
            require(success2, "Transfer failed.");

            emit Sale(tokenId, auctions[tokenId].beneficiary, highestBidder, auctions[tokenId].highestBid);
            emit Commission(tokenId, auctions[tokenId].beneficiary, auctions[tokenId].highestBid, commissionRate, amount4admin);

            // transfer ownership
            address owner = ownerOf(tokenId);

            // we need to call a transferFrom from this contract, which is the one with permission to sell the NFT
            // transfer the NFT to the auction's highest bidder
            callOptionalReturn(this, abi.encodeWithSelector(this.transferFrom.selector, owner, highestBidder, tokenId));

            soldFor[tokenId] = auctions[tokenId].highestBid;

        }

        emit AuctionEnded(auctions[tokenId].highestBidder, auctions[tokenId].highestBid);

        // finalize the auction
        delete auctions[tokenId];

    }

    // Bid query functions
    function highestBidder(uint256 tokenId) public view returns (address payable) {
        return auctions[tokenId].highestBidder;
    }

    function highestBid(uint256 tokenId) public view returns (uint256) {
        return auctions[tokenId].highestBid;
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     */
    function callOptionalReturn(IERC721 token, bytes memory data) private {
        // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
        // we're implementing it ourselves.

        // A Solidity high level call has three parts:
        //  1. The target address is checked to verify it contains contract code
        //  2. The call itself is made, and success asserted
        //  3. The return value is decoded, which in turn checks the size of the returned data.
        // solhint-disable-next-line max-line-length
        require(address(token).isContract(), "SafeERC721: call to non-contract");

        // solhint-disable-next-line avoid-low-level-calls
        (bool success, bytes memory returndata) = address(token).call(data);
        require(success, "SafeERC721: low-level call failed");

        if (returndata.length > 0) { // Return data is optional
            // solhint-disable-next-line max-line-length
            require(abi.decode(returndata, (bool)), "SafeERC721: ERC20 operation did not succeed");
        }
    }

    // update contract fields
    function updateAdmin(address payable _admin, uint256 _commissionRate, bool _anyoneCanMint) public {
        require(msg.sender==contract_owner, "Only contract owner can do this");
        admin=_admin;
        commissionRate=_commissionRate;
        anyoneCanMint=_anyoneCanMint;
    }

}
/**
 * @title ERC721Matcha
 * ERC-721 Marketplace
 */
function updateAdmin(address payable _admin, uint256 _commissionRate, bool _anyoneCanMint) public {
        require(msg.sender==contract_owner, "Only contract owner can do this");
        admin=_admin;
        commissionRate=_commissionRate;
        anyoneCanMint=_anyoneCanMint;
    }
// update contract fields