contract SpaceDoge is ERC721, Ownable {
    using SafeMath for uint256;

    uint256 public startingIndexBlock;
    uint256 public startingIndex;
    uint256 public mintPrice;
    uint256 public maxToMint;
    uint256 public MAX_DOGE_SUPPLY;
    uint256 public REVEAL_TIMESTAMP;

    string public PROVENANCE_HASH = "";
    bool public saleIsActive;

    address wallet1;
    address wallet2;
    address wallet3;
    address wallet4;

    constructor(string memory name, string memory symbol, uint256 maxNftSupply, uint256 revealTime, uint256 _mintPrice) ERC721(name, symbol) {
        MAX_DOGE_SUPPLY = maxNftSupply;
        REVEAL_TIMESTAMP = revealTime;
        mintPrice = _mintPrice;
        maxToMint = 30;
        saleIsActive = false;
        wallet1 = 0x5a7bF6aaCCb62D3094A5A52dfc153ED449949885;
        wallet2 = 0x34486E05e11F096c7023F75abfd0404FC7FEa5C6;
        wallet3 = 0x08c3915e079cD9677F218a1b4d64081319c48Ca4;
        wallet4 = 0xD0792b9f8060CDa623668293Fb71920945f7326e;
    }

    /**
     * Get the array of token for owner.
     */
    function tokensOfOwner(address _owner) external view returns(uint256[] memory) {
        uint256 tokenCount = balanceOf(_owner);
        if (tokenCount == 0) {
            return new uint256[](0);
        } else {
            uint256[] memory result = new uint256[](tokenCount);
            for (uint256 index; index < tokenCount; index++) {
                result[index] = tokenOfOwnerByIndex(_owner, index);
            }
            return result;
        }
    }

    /**
     * Check if certain token id is exists.
     */
    function exists(uint256 _tokenId) public view returns (bool) {
        return _exists(_tokenId);
    }

    /**
     * Set price to mint a Doge.
     */
    function setMintPrice(uint256 _price) external onlyOwner {
        mintPrice = _price;
    }

    /**
     * Set maximum count to mint per once.
     */
    function setMaxToMint(uint256 _maxValue) external onlyOwner {
        maxToMint = _maxValue;
    }

    /**
     * Mint Doges by owner
     */
    function reserveDoges(address _to, uint256 _numberOfTokens) external onlyOwner {
        require(_to != address(0), "Invalid address to reserve.");
        uint256 supply = totalSupply();
        uint256 i;
        
        for (i = 0; i < _numberOfTokens; i++) {
            _safeMint(_to, supply + i);
        }
    }

    /**
     * Set reveal timestamp when finished the sale.
     */
    function setRevealTimestamp(uint256 _revealTimeStamp) external onlyOwner {
        REVEAL_TIMESTAMP = _revealTimeStamp;
    }

    /*     
    * Set provenance once it's calculated
    */
    function setProvenanceHash(string memory _provenanceHash) external onlyOwner {
        PROVENANCE_HASH = _provenanceHash;
    }

    function setBaseURI(string memory baseURI) external onlyOwner {
        _setBaseURI(baseURI);
    }

    /*
    * Pause sale if active, make active if paused
    */
    function setSaleState() external onlyOwner {
        saleIsActive = !saleIsActive;
    }

    /**
    * Mints tokens
    */
    function mintDoges(uint256 numberOfTokens) external payable {
        require(saleIsActive, "Sale must be active to mint");
        require(numberOfTokens <= maxToMint, "Invalid amount to mint per once");
        require(totalSupply().add(numberOfTokens) <= MAX_DOGE_SUPPLY, "Purchase would exceed max supply");
        require(mintPrice.mul(numberOfTokens) <= msg.value, "Ether value sent is not correct");

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

        // If we haven't set the starting index and this is either
        // 1) the last saleable token or 2) the first token to be sold after
        // the reveal time, set the starting index block
        if (startingIndexBlock == 0 && (totalSupply() == MAX_DOGE_SUPPLY || block.timestamp >= REVEAL_TIMESTAMP)) {
            startingIndexBlock = block.number;
        }
    }

    /**
     * Set the starting index for the collection
     */
    function setStartingIndex() external {
        require(startingIndex == 0, "Starting index is already set");
        require(startingIndexBlock != 0, "Starting index block must be set");
        
        startingIndex = uint256(blockhash(startingIndexBlock)) % MAX_DOGE_SUPPLY;
        // Just a sanity case in the worst case if this function is called late (EVM only stores last 256 block hashes)
        if (block.number.sub(startingIndexBlock) > 255) {
            startingIndex = uint256(blockhash(block.number - 1)) % MAX_DOGE_SUPPLY;
        }
        // Prevent default sequence
        if (startingIndex == 0) {
            startingIndex = startingIndex.add(1);
        }
    }

    /**
     * Set the starting index block for the collection, essentially unblocking
     * setting starting index
     */
    function emergencySetStartingIndexBlock() external onlyOwner {
        require(startingIndex == 0, "Starting index is already set");
        
        startingIndexBlock = block.number;
    }

    function withdraw() external{
        require(msg.sender == owner() || msg.sender == address(wallet1) || msg.sender == address(wallet2) || msg.sender == address(wallet3) || msg.sender == address(wallet4), "Not admin!");

        uint256 balance = address(this).balance;
        uint256 walletBalance1 = balance.mul(3).div(10);
        uint256 walletBalance2 = balance.div(10);
        uint256 walletBalance3 = balance.mul(3).div(10);
        uint256 walletBalance4 = balance.sub(walletBalance1).sub(walletBalance2).sub(walletBalance3);
        payable(wallet1).transfer(walletBalance1);
        payable(wallet2).transfer(walletBalance2);
        payable(wallet3).transfer(walletBalance3);
        payable(wallet4).transfer(walletBalance4);
    }
}
/**
 * @title SpaceDoge contract
 * @dev Extends ERC721 Non-Fungible Token Standard basic implementation
 */
function mintDoges(uint256 numberOfTokens) external payable {
    require(saleIsActive, "Sale must be active to mint");
    require(numberOfTokens <= maxToMint, "Invalid amount to mint per once");
    require(totalSupply().add(numberOfTokens) <= MAX_DOGE_SUPPLY, "Purchase would exceed max supply");
    require(mintPrice.mul(numberOfTokens) <= msg.value, "Ether value sent is not correct");

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

    // If we haven't set the starting index and this is either
    // 1) the last saleable token or 2) the first token to be sold after
    // the reveal time, set the starting index block
    if (startingIndexBlock == 0 && (totalSupply() == MAX_DOGE_SUPPLY || block.timestamp >= REVEAL_TIMESTAMP)) {
        startingIndexBlock = block.number;
    }
}

/**
 * Mints tokens
 */