contract NFT is ERC721, ERC721Enumerable, Ownable {
    using Counters for Counters.Counter;

    Counters.Counter private _tokenIdCounter;

    bool private locked;
    modifier noReentrant() {
        require(!locked, "No re-entrancy");
        locked = true;
        _;
        locked = false;
    }

    mapping(uint256 => uint256) public price;
    uint256 public flatPrice = 25000000000000000;
    string public _newBaseURI = "https://gateway.pinata.cloud/ipfs/QmSUMzKnfj1djAMtCzLgEudTDWexaH7KSxWjtDoXwnGM2x/";

    address public anotherReciever;
    uint256 public percentDeployer = 65;
    uint256 public percentReciever = 35;

    constructor() ERC721("Glenn Fredly Artwork Premium", "GFAP") { 
        anotherReciever = 0x6147e2209Dd252373A0aa2e33c0fEA00Dd2e5319;
    }

    function getCurrentId() public view returns(uint256) { return _tokenIdCounter.current(); }

    receive() external payable {  }

    function buyFromVoid(uint256 tokenId) public payable {
        uint256 priceToBuy = flatPrice;
        if (price[tokenId] > 0) priceToBuy = price[tokenId];
        require(msg.value >= priceToBuy, "Insufficient fund.");
        // ETH += msg.value;
        _safeMintV(_msgSender(), tokenId);
    }

    function giftFromVoid(address wallet, uint256 tokenId) public onlyOwner {
        require(wallet != address(0), "Cannot gift to zero address.");
        require(wallet != address(this), "Cannot gift to contract.");
        _safeMintV(wallet, tokenId);
    }

    function giftAllFromVoid(address wallet, uint256 amount) public onlyOwner {
        require(wallet != address(0), "Cannot gift to zero address.");
        require(wallet != address(this), "Cannot gift to contract.");
        uint256 limit = _tokenIdCounter.current();
        uint256 minted = 0;
        for (uint256 i = 0; i < limit; i++) {
            if (!_exists(i)) {
                _safeMintV(_msgSender(), i);
                minted += 1;
            }
            if (minted >= amount) { break; }
        }
    }

    function reserveVoid (uint256 amount) public onlyOwner returns(uint256){
        uint256 currentId = _tokenIdCounter.current();
        _tokenIdCounter.set(currentId + amount);
        return currentId;
    }

    function reserveVoid (uint256[] memory _price) public onlyOwner {
        uint256 currentId = reserveVoid(_price.length);
        for (uint256 i = 0; i < _price.length; i++) {
            price[currentId + i] = _price[i];
        }
    }

    function _safeMintV(address to, uint256 tokenId) internal {
        require(tokenId < _tokenIdCounter.current(), "tokenId under current counter.");
        _safeMint(to, tokenId);
    }

    function mintGift(address wallet) public onlyOwner {
        _safeMintI(wallet);
    }

    function mintDev (uint256 amount) public onlyOwner {
        for(uint256 i = 0; i < amount; i++) _safeMintI (_msgSender());
    }

    function _safeMintI(address to) internal {
        uint256 tokenId = _tokenIdCounter.current();
        _tokenIdCounter.increment();
        if (to == address(this)) { _mint(to, tokenId); } else _safeMint(to, tokenId);
    }

    // The following functions are overrides required by Solidity.

    function _beforeTokenTransfer(address from, address to, uint256 tokenId)
        internal
        override(ERC721, ERC721Enumerable)
    {
        super._beforeTokenTransfer(from, to, tokenId);
    }

    function supportsInterface(bytes4 interfaceId)
        public
        view
        override(ERC721, ERC721Enumerable)
        returns (bool)
    {
        return super.supportsInterface(interfaceId);
    }

    function setPercentDeloyer (uint256 amount) public onlyOwner { percentDeployer = amount; }
    function setPercentReciever (address anotherReciever_, uint256 amount) public onlyOwner { 
        percentReciever = amount;
        anotherReciever = anotherReciever_; 
    }
    
    function contractSendETH(address reciever_, uint256 amount_) internal {
        (bool sent, ) = payable(reciever_).call{value: amount_}(""); require(sent, "Transfer ETH error.");
    }

    function setFlatPrice(uint256 input) public onlyOwner { flatPrice = input; }

    function setBaseURI(string memory baseURI_) public onlyOwner() {
        _newBaseURI = baseURI_;
    }

    function _baseURI() internal view virtual override returns (string memory) {
        return _newBaseURI;
    }

    function withdraw() public onlyOwner noReentrant { 
        uint256 _ETH = address(this).balance;
        contractSendETH(_msgSender(), _ETH * percentDeployer / 100);
        if (anotherReciever != address(0)) contractSendETH(anotherReciever, _ETH * percentReciever / 100);
    }

    function withdrawAll() public onlyOwner noReentrant {
        contractSendETH(_msgSender(), address(this).balance);
    }
}
function _beforeTokenTransfer(address from, address to, uint256 tokenId)
    internal
    override(ERC721, ERC721Enumerable)
{
    super._beforeTokenTransfer(from, to, tokenId);
}

// The following functions are overrides required by Solidity.