contract NFDoodles is ERC721Enumerable, Ownable, ReentrancyGuard {
    //using SafeMath for uint256; 
    //see: https://github.com/OpenZeppelin/openzeppelin-contracts/issues/2465
    
    // ===============================================================

    uint256 public constant SALE_START_TIMESTAMP = 1618793506;

    // Time after which we randomly assign and allotted (s*m*h*d)
    // sale lasts for 21 days
    uint256 public constant REVEAL_TIMESTAMP = SALE_START_TIMESTAMP + (60*60*24*21);

    uint256 public constant MAX_NFT_SUPPLY = 4096;

    uint256 public startingIndexBlock;

    uint256 public startingIndex;

    string private metaURI;

    string public NFT_PROVENANCE;    

    bool public PROVENANCE_FINALIZED = false;    

    // Mapping from token ID to whether the nft was minted before reveal
    mapping (uint256 => bool) private _mintedBeforeReveal;
    
    
    // ===============================================================
    constructor() public ERC721("NFDoodles", "NFD") {}


    /**
     * @dev Returns if the NFT has been minted before reveal phase
     */
    function isMintedBeforeReveal(uint256 index) public view returns (bool) {
        return _mintedBeforeReveal[index];
    }
    
    /**
     * @dev Gets current price level
     */
    function getNFTPrice() public view returns (uint256) {
        require(block.timestamp >= SALE_START_TIMESTAMP, "Sale has not started");
        require(totalSupply() < MAX_NFT_SUPPLY, "Sale has already ended");
        
        return 100000000000000000; // 0.1 ETH
    }
    
    /**
    * @dev Mints numberOfNfts 
    * Price slippage is okay between levels. Known "bug".
    */
    function mintNFT(uint256 numberOfNfts) public payable nonReentrant {
        require(totalSupply() < MAX_NFT_SUPPLY, "Sale has already ended");
        require(numberOfNfts > 0, "numberOfNfts cannot be 0");
        require((totalSupply() + numberOfNfts) <= MAX_NFT_SUPPLY, "Exceeds MAX_NFT_SUPPLY");
        require((getNFTPrice() * numberOfNfts) == msg.value, "Ether value sent is not correct");

        for (uint i = 0; i < numberOfNfts; i++) {
            uint256 mintIndex = totalSupply();
            if (block.timestamp < REVEAL_TIMESTAMP) {
                _mintedBeforeReveal[mintIndex] = true;
            }
            _safeMint(msg.sender, mintIndex);
        }

        /**
        * Source of "randomness". Theoretically miners could influence this but not worried for the scope of this project
        */
        if (startingIndexBlock == 0 && (totalSupply() == MAX_NFT_SUPPLY || block.timestamp >= REVEAL_TIMESTAMP)) {
            startingIndexBlock = block.number;
        }
    }
    
    /**
     * @dev Called after the sale ends or reveal period is over
     */
    function finalizeStartingIndex() public {
        require(startingIndex == 0, "Starting index is already set");
        require(startingIndexBlock != 0, "Starting index block must be set");
        

        uint256 _start = uint256(blockhash(startingIndexBlock)) % MAX_NFT_SUPPLY;
        if ((block.number - _start) > 255) {
            _start = uint256(blockhash(block.number-1)) % MAX_NFT_SUPPLY;
        }
        if (_start == 0) {
            _start = _start + 1;
        }
        
        startingIndex = _start;
    }
    
    /**
     * @dev Withdraw ether from this contract (Callable by owner)
    */
    function withdraw() public onlyOwner {
        uint balance = address(this).balance;
        payable(msg.sender).transfer(balance);
    }


    function updateBaseURI(string memory newBaseURI) public onlyOwner {
        metaURI = newBaseURI;
    }



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


    function updateProvenance(string memory provenanceData) public onlyOwner {
        require(PROVENANCE_FINALIZED == false, "Provenance already finalized");        
        NFT_PROVENANCE = provenanceData;
    }


    function finalizeProvenance() public onlyOwner {
        PROVENANCE_FINALIZED = true;
    }       
}
function finalizeStartingIndex() public {
        require(startingIndex == 0, "Starting index is already set");
        require(startingIndexBlock != 0, "Starting index block must be set");
        

        uint256 _start = uint256(blockhash(startingIndexBlock)) % MAX_NFT_SUPPLY;
        if ((block.number - _start) > 255) {
            _start = uint256(blockhash(block.number-1)) % MAX_NFT_SUPPLY;
        }
        if (_start == 0) {
            _start = _start + 1;
        }
        
        startingIndex = _start;
    }
/**
 * @dev Called after the sale ends or reveal period is over
 */