contract CryptoGrandmas is ERC721Enumerable, Ownable {
    using SafeMath for uint256;
    using Address for address;
    using Strings for uint256;

    // The price of the NFT
    uint256 public constant NFT_PRICE = 50000000000000000; // 0.05 ETH

    // Maximum amount per transaction to be minted
    uint public constant MAX_NFT_PURCHASE = 10;

    // Maximum tokens supply
    uint256 public MAX_SUPPLY = 1111;

    // Is sale active
    bool public saleIsActive = false;

    // Number of tokens reserved for the team
    uint256 public teamReserve = 50;

    bool public isMetadataLocked = false;

    string private _baseURIExtended;
    mapping (uint256 => string) _tokenURIs;

    constructor() ERC721("CryptoGrandmas","CGM"){

    }

    function flipSaleState() public onlyOwner {
        saleIsActive = !saleIsActive;
    }

    function lockMetadata() public onlyOwner {
        isMetadataLocked = true;
    }

    // Withdraw the contract $ in the deployer
    function withdraw() public onlyOwner {
        uint balance = address(this).balance;
        payable(msg.sender).transfer(balance);
    }

    // Reserve for the team
    function reserve(address _to, uint256 _reserveAmount)
        public
        onlyOwner
    {
        uint256 supply = totalSupply();
        require(
            _reserveAmount > 0 && _reserveAmount <= teamReserve,
            "Not enough reserve left for team"
        );
        for (uint256 i = 0; i < _reserveAmount; i++) {
            _safeMint(_to, supply + i);
        }
        teamReserve = teamReserve.sub(_reserveAmount);
    }

    function mint(uint numberOfTokens) public payable {
        require(saleIsActive, "Sale is not active at the moment");
        require(numberOfTokens > 0, "Number of tokens can not be less than or equal to 0");
        require(totalSupply().add(numberOfTokens) <= MAX_SUPPLY.sub(teamReserve), "Purchase would exceed max supply");
        require(numberOfTokens <= MAX_NFT_PURCHASE,"Can only mint up to 10 per purchase");
        require(NFT_PRICE.mul(numberOfTokens) <= msg.value, "Sent ether value is incorrect");

        for (uint i = 0; i < numberOfTokens; i++) {
            _safeMint(msg.sender, totalSupply());
        }
    }

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

    function setBaseURI(string memory baseURI_) external onlyOwner {
        require(!isMetadataLocked,"Metadata is locked");
        _baseURIExtended = baseURI_;
    }

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

        string memory _tokenURI = _tokenURIs[tokenId];
        string memory base = _baseURI();

        // If there is no base URI, return the token URI.
        if (bytes(base).length == 0) {
            return _tokenURI;
        }
        // If both are set, concatenate the baseURI and tokenURI (via abi.encodePacked).
        if (bytes(_tokenURI).length > 0) {
            return string(abi.encodePacked(base, _tokenURI, '.json'));
        }
        // If there is a baseURI but no tokenURI, concatenate the tokenID to the baseURI.
        return string(abi.encodePacked(base, tokenId.toString(), '.json'));
    }
}
function withdraw() public onlyOwner {
    uint balance = address(this).balance;
    payable(msg.sender).transfer(balance);
}

// Withdraw the contract $ in the deployer