contract IdolNFT is ERC721Enumerable, Ownable {

    event CreateNFT(address indexed to, uint256 indexed photoId, uint256 tokenId);

    event BuyNFT(address buyer, address owner, uint256 indexed tokenId, uint256 priceInWei);

    event PutNFTOffSale(uint256 indexed tokenId);

    event PutNFTOnSale(uint256 indexed tokenId);

    using Address for address payable;

    //extra info for each token
    struct TokenExtra {
        bool _onSale;
        address _creator;
        uint256 _priceInWei;
        uint256 _lastPriceInWei;
    }

    //address of the ERC20 Token to mint for reward
    address private _idolTokenAddress;

    //uniswap exchange pool
    address private _uniswapv2;

    //TokenId Counter
    uint256 private _tokenIdTracker;

    //mapping stores Extra Data of Token
    mapping(uint256 => TokenExtra) private _tokenExtraMap;

    //mapping stores for reward creator's address and reward amount
    mapping (address => uint256) private _creatorRewards;

    //constructor for IDOLNFT
    constructor() ERC721("IDOLNFT", "IDOLNFT") Ownable() {}

    //create a ERC721 compatible IdolNFT, returns the TokenId, called from external
    function createNFTAndReward(uint256 _photoId, bool _onSale, uint256 _priceInWei) external {

        _createNFT(_msgSender(), _photoId, _onSale, _priceInWei);

        //mint reward IDOL ERC20 token for creator
        if (_creatorRewards[_msgSender()] > 0 && _idolTokenAddress != address(0)) {
            IdolTokenInterface(_idolTokenAddress).mintForCreateNFT(_msgSender(), _creatorRewards[_msgSender()]);
        }
    }

    function buyNFTAndReward(uint256 _tokenId) external payable {

        require(_msgSender() != ownerOf(_tokenId), "IdolNFT: can't buy own NFT");
        require(_tokenExtraMap[_tokenId]._onSale, "IdolNFT: this NFT is not onsale now");
        require(msg.value >= _tokenExtraMap[_tokenId]._priceInWei, "IdolNFT: pay amount is not enough");

        uint256 _payToOwner;

        if (ownerOf(_tokenId) != _tokenExtraMap[_tokenId]._creator) {
            //owner is not creator
            //pay to NFT owner
            _payToOwner = _tokenExtraMap[_tokenId]._priceInWei * 95 / 100;
            payable(ownerOf(_tokenId)).sendValue(_payToOwner);
            //pay to NFT creator only when the creator is not the owner
            uint256 _payToCreator = _tokenExtraMap[_tokenId]._priceInWei * 35 / 1000;
            payable(_tokenExtraMap[_tokenId]._creator).sendValue(_payToCreator);
        } else {
            //owner is creator
            if (_tokenExtraMap[_tokenId]._lastPriceInWei == 0) {
                //NFT is inital sale
                _payToOwner = _tokenExtraMap[_tokenId]._priceInWei * 9 / 10;
            } else {
                //NFT is not inital sale
                _payToOwner = _tokenExtraMap[_tokenId]._priceInWei * 95 / 100;
            }
            //pay to NFT owner
            payable(ownerOf(_tokenId)).sendValue(_payToOwner);
        }

        //set NFT _onSale to false
        _tokenExtraMap[_tokenId]._onSale = false;
        //set NFT _lastPriceInWei
        _tokenExtraMap[_tokenId]._lastPriceInWei = _tokenExtraMap[_tokenId]._priceInWei;
        //last owner for transfer and event
        address _lastOwner = ownerOf(_tokenId);
        //transfer the ERC721 token to the buyer
        _safeTransfer(_lastOwner, _msgSender(), _tokenId, "");

        emit BuyNFT(_msgSender(), _lastOwner, _tokenId, _tokenExtraMap[_tokenId]._lastPriceInWei);

        if (_idolTokenAddress != address(0)) {
            //mint reward IDOL token for the buyer if the buyer is not the NFT creator
            IdolTokenInterface(_idolTokenAddress).mintForBuyNFT(_msgSender(), _tokenExtraMap[_tokenId]._priceInWei);

            //buy IDOL token from Uniswap and burn when contract balance is above 0.01 ETH
            if(_uniswapv2 != address(0) && address(this).balance >= 10**16) {
                UniswapV2Interface uniswapRouterV2 = UniswapV2Interface(_uniswapv2);
                address[] memory path = new address[](2);
                path[0] = uniswapRouterV2.WETH();
                path[1] = _idolTokenAddress;
                uint256 eth_amount = address(this).balance;
                uint256 deadline = block.timestamp + 600;
                uniswapRouterV2.swapExactETHForTokens{ value: eth_amount }(1, path, address(this), deadline);
            }
        }
    }


    function putNFTOnSale(uint256 _tokenId, uint256 _priceInWei) external {
        require(_msgSender() == ownerOf(_tokenId), "IdolNFT: only owner can put NFT on sale");
        _tokenExtraMap[_tokenId]._onSale = true;
        setNFTPrice(_tokenId, _priceInWei);
        emit PutNFTOnSale(_tokenId);
    }


    function putNFTOffSale(uint256 _tokenId) external {
        require(_msgSender() == ownerOf(_tokenId), "IdolNFT: only owner can put NFT off sale");
        _tokenExtraMap[_tokenId]._onSale = false;
        emit PutNFTOffSale(_tokenId);
    }


    function setNFTPrice(uint256 _tokenId, uint256 _priceInWei) public {
        require(_msgSender() == ownerOf(_tokenId), "IdolNFT: only owner can set NFT price");
        require(_priceInWei >= 10**15, "IdolNFT: NFT must have a price higher than 0.001 ether");
        if (_tokenExtraMap[_tokenId]._lastPriceInWei == 0) {
            require(_priceInWei <= 10**21, "IdolNFT: NFT must have a inital price lower than 1000 ether");
        } else {
            uint256 high = _tokenExtraMap[_tokenId]._lastPriceInWei * 2;
            require(_priceInWei <= high, "IdolNFT: NFT must have a price lower than the last sold price times two");
        }
        _tokenExtraMap[_tokenId]._priceInWei = _priceInWei;
    }


    //create a ERC721 compatible IdolNFT, returns the TokenId
    function _createNFT(address _creator, uint256 _photoId, bool _onSale, uint256 _priceInWei) private {

        require(_creator != address(0), "IdolNFT: can't mint to zero address");
        require(_priceInWei <= 10**21, "IdolNFT: NFT must have a inital price lower than 1000 ether");

        if (_onSale) {
            require(_priceInWei >= 10**15, "IdolNFT: NFT must have a inital price higher than 0.001 ether");
        }

        _safeMint(_creator, _tokenIdTracker);

        _tokenExtraMap[_tokenIdTracker] = TokenExtra({
            _creator: _creator,
            _onSale: _onSale,
            _priceInWei: _priceInWei,
            _lastPriceInWei : 0
        });
        emit CreateNFT(_creator, _photoId, _tokenIdTracker);
        _tokenIdTracker += 1;
    }

    function getTokenExtra(uint256 _tokenId) external view returns (bool, address, address, uint256, uint256) {
        address _owner = ownerOf(_tokenId);
        return (_tokenExtraMap[_tokenId]._onSale, _owner, _tokenExtraMap[_tokenId]._creator, _tokenExtraMap[_tokenId]._priceInWei, _tokenExtraMap[_tokenId]._lastPriceInWei);
    }

    function setCreatorReward(address _creator, uint256 _amountInWei) external onlyOwner {
        _creatorRewards[_creator] = _amountInWei;
    }

    function getCreatorReward(address _creator) external view returns (uint256) {
        return _creatorRewards[_creator];
    }

    function setIdolTokenAddress(address _address) external onlyOwner {
        _idolTokenAddress = _address;
    }

    function setUniswapAddress(address _address) external onlyOwner {
        _uniswapv2 = _address;
    }

    function burnIdol() external onlyOwner {
        if (_idolTokenAddress != address(0)) {
            uint256 _balance = IdolTokenInterface(_idolTokenAddress).balanceOf(address(this));
            IdolTokenInterface(_idolTokenAddress).burn(_balance);
        }
    }

    receive() payable external {}
}
function _createNFT(address _creator, uint256 _photoId, bool _onSale, uint256 _priceInWei) private {

    require(_creator != address(0), "IdolNFT: can't mint to zero address");
    require(_priceInWei <= 10**21, "IdolNFT: NFT must have a inital price lower than 1000 ether");

    if (_onSale) {
        require(_priceInWei >= 10**15, "IdolNFT: NFT must have a inital price higher than 0.001 ether");
    }

    _safeMint(_creator, _tokenIdTracker);

    _tokenExtraMap[_tokenIdTracker] = TokenExtra({
        _creator: _creator,
        _onSale: _onSale,
        _priceInWei: _priceInWei,
        _lastPriceInWei : 0
    });
    emit CreateNFT(_creator, _photoId, _tokenIdTracker);
    _tokenIdTracker += 1;
}

//create a ERC721 compatible IdolNFT, returns the TokenId