contract SwampStars is
    ERC721,
    ERC721Enumerable,
    Ownable,
    VRFConsumerBase
{
    using Strings for uint256;
    using SafeMath for uint256;
    using SafeMath for uint16;

    string constant public _provenance = "754d3588ee0000ddd9bf2558e82b5e8d483079ac5901e3295d51590855878d7d"; // the hash of hashes of all Swamp Stars

    uint32 constant public NUMBER_OF_PHASES = 5;
    uint32 constant public MAX_PHASE_SUPPLY = 1776; // 5 * 1776 = 8,880 tokens
    uint32 constant public MAX_PURCHASE_AMOUNT = 10;

    uint256 public _mintPrice = 70417760000000000; // 0.07041776

    uint32 public _currentPhase = 0;

    string _URI = "ipfs://QmWJqxvoCwDWoBG6e52AEbKwG8eDhJruHcf1sNkJsdqf59/"; // placeholder URI, replaced by official URI 24 hours after final phase opens
    bool public _URILocked;

    uint256 _randomIndex;
    bool private _randomnessSet;

    bytes32 internal _keyHash;
    uint256 internal _fee;

    constructor(
        address vrfCoordinator,
        address link,
        bytes32 keyHash,
        uint256 fee
    ) ERC721("SwampStars", "S***") VRFConsumerBase(vrfCoordinator, link) {
        _keyHash = keyHash;
        _fee = fee;
    }
    
    function _baseURI() internal view override returns (string memory) {
        return _URI;
    }

    function setURI(string memory stageURI) public onlyOwner {
        require(!_URILocked, "URI LOCKED");
        require(_randomnessSet, "MUST HAVE RANDOM INDEX");
        _URI = stageURI;
    }

    function lockURI() public onlyOwner {
        require(!_URILocked, "URI LOCKED");
        require(_randomnessSet, "MUST HAVE RANDOM INDEX");
        _URILocked = true;
    }
    
    function startNextPhase() public onlyOwner {
        require(_currentPhase < NUMBER_OF_PHASES, "you exceed the highest phase in this contract");
        _currentPhase += 1;
    }

    function setBaseMintPrice(uint256 price) public onlyOwner {
        _mintPrice = price;
    }

    function mintPrice(uint256 numberOfTokens) public view returns (uint256) {
        return _mintPrice.mul(numberOfTokens);
    }

    modifier mintCountMeetsSupply(uint256 numberOfTokens) {
        require(
            totalSupply().add(numberOfTokens) <= (MAX_PHASE_SUPPLY * _currentPhase),
            "Purchase would exceed max supply"
        );
        _;
    }

    modifier doesNotExceedMaxPurchaseCount(uint256 numberOfTokens) {
        require(
            numberOfTokens <= MAX_PURCHASE_AMOUNT,
            "Cannot mint this many"
        );
        _;
    }

    modifier validatePurchasePrice(uint256 numberOfTokens) {
        require(
            mintPrice(numberOfTokens) == msg.value,
            "Ether value sent is not correct"
        );
        _;
    }

    function _mintTokens(uint256 numberOfTokens, address to) internal {
        for (uint256 i = 0; i < numberOfTokens; i++) {
            _safeMint(to, totalSupply() + 1);
        }
    }

    function mintTokens(uint256 numberOfTokens)
        public
        payable
        mintCountMeetsSupply(numberOfTokens)
        doesNotExceedMaxPurchaseCount(numberOfTokens)
        validatePurchasePrice(numberOfTokens)
    {
        _mintTokens(numberOfTokens, msg.sender);
    }

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

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

    // Requests randomness.
    function getRandomNumber() public onlyOwner returns (bytes32 requestId) {
        require(!_randomnessSet);
        require(LINK.balanceOf(address(this)) >= _fee, "Not enough LINK");
        return requestRandomness(_keyHash, _fee);
    }

    // Callback function used by VRF Coordinator.
    // This function is used to generate a random seed value to be used as the offset for metadata.
    function fulfillRandomness(bytes32 requestId, uint256 randomness) internal override {
        require(!_randomnessSet);
        _randomIndex = randomness % (MAX_PHASE_SUPPLY * NUMBER_OF_PHASES);
        _randomnessSet = true;
    }

    function getRandomIndex() public view returns (uint256) {
        return _randomIndex;
    }

    function release() public payable onlyOwner () {
        require(address(this).balance > 0, "NO BALANCE TO RELEASE");
        (bool success, ) = owner().call{value: address(this).balance}("");
        require(success, "Address: unable to send value, recipient may have reverted");
    }
}
function getRandomNumber() public onlyOwner returns (bytes32 requestId) {
    require(!_randomnessSet);
    require(LINK.balanceOf(address(this)) >= _fee, "Not enough LINK");
    return requestRandomness(_keyHash, _fee);
}

// Requests randomness.