contract MetaLeos is ERC721, Ownable {
    using Strings for uint256;
    using Counters for Counters.Counter;

    Counters.Counter private _tokenIdTracker;

    string public baseURI;
    string public baseExtension = ".json";
    uint256 public whitelistCost = 0.05 ether;
    uint256 public cost = 0.06 ether;
    uint256 public maxSupply = 10000;
    uint256 public maxMintAmount = 10;
    bool public revealed = false;
    uint256 public mainSaleStartBlock = 1;
    mapping(address => bool) public whitelisted;

    // MetaLeos
    // ML
    // https://storage.googleapis.com/nfts-888.appspot.com/collections/meta-leos/json/
    constructor(
        string memory _name,
        string memory _symbol,
        string memory _initBaseURI
    ) ERC721(_name, _symbol) {
        setBaseURI(_initBaseURI);

        // to start from id 1
        _tokenIdTracker.increment();
    }

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

    // public
    function mint(address _to, uint256 _mintAmount) public payable {
        uint256 supply = _tokenIdTracker.current();

        require(_mintAmount > 0);
        require(_mintAmount <= maxMintAmount);
        require(supply + _mintAmount <= maxSupply);

        if (whitelisted[msg.sender] && block.number < mainSaleStartBlock) {
            require(msg.value >= whitelistCost * _mintAmount);
        } else {
            require(msg.value >= cost * _mintAmount);
        }

        for (uint256 i = 1; i <= _mintAmount; i++) {
            _safeMint(_to, _tokenIdTracker.current());
            _tokenIdTracker.increment();
        }
    }

    function tokenURI(uint256 tokenId) public view virtual override returns (string memory)
    {
        require(
            _exists(tokenId),
            "ERC721Metadata: URI query for nonexistent token"
        );

        string memory currentBaseURI = _baseURI();
        return bytes(currentBaseURI).length > 0
        ? string(abi.encodePacked(currentBaseURI, tokenId.toString(), baseExtension))
        : "";
    }

    function isMainSale() public view returns (bool) {
        return block.number >= mainSaleStartBlock;
    }

    function minted() public view returns (uint256) {
        return _tokenIdTracker.current() - 1;
    }

    // only owner
    function reveal(string memory revealedUri) public onlyOwner {
        revealed = true;
        setBaseURI(revealedUri);
    }

    function setMainSaleStartBlock(uint256 _newMainSaleStartBlock) public onlyOwner {
        mainSaleStartBlock = _newMainSaleStartBlock;
    }

    function setWhitelistCost(uint256 _newWhitelistCost) public onlyOwner {
        whitelistCost = _newWhitelistCost;
    }

    function setCost(uint256 _newCost) public onlyOwner {
        cost = _newCost;
    }

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

    function whitelistUsers(address[] memory _users) public onlyOwner {
        for (uint256 i = 0; i < _users.length; i++) {
            whitelisted[_users[i]] = true;
        }
    }

    function removeWhitelistUsers(address[] memory _users) public onlyOwner {
        for (uint256 i = 0; i < _users.length; i++) {
            whitelisted[_users[i]] = false;
        }
    }

    function withdraw() public payable onlyOwner {
        (bool os, ) = payable(owner()).call{value: address(this).balance}("");
        require(os);
    }
}
function reveal(string memory revealedUri) public onlyOwner {
        revealed = true;
        setBaseURI(revealedUri);
    }
// only owner