contract StakeForVnfts2 is Roles {
    using SafeMath for uint256;

    IMuseToken public museToken;
    IERC721 public vNFT;

    // min $muse amount required to stake
    uint256 public minStake = 50 * 1 ether;

    // amount of points needed to redeem a vnft, roughly 1 point is given each day;
    uint256 public vnftPrice = 400 * 1 ether;

    uint256 public musePrice = 50 * 1 ether;

    uint256 public totalStaked;
    bool public gameStopped = false;

    mapping(address => uint256) public balance;
    mapping(address => uint256) public lastUpdateTime;
    mapping(address => uint256) public points;

    event Staked(address who, uint256 amount);
    event Withdrawal(address who, uint256 amount);
    event VnftMinted(address to, uint256 pointPrice, uint256 musePrice);

    event StakeReqChanged(uint256 newAmount);
    event PriceOfvnftChanged(uint256 newAmount);
    event MusePriceOfvnftChanged(uint256 newAmount);

    constructor(address _vNFT, address _museToken) public {
        vNFT = IERC721(_vNFT);
        museToken = IMuseToken(_museToken);
    }

    modifier notPaused() {
        require(!gameStopped, "Contract is paused");
        _;
    }

    // in case a bug happens or we upgrade to another smart contract
    function pauseGame(bool _pause) external onlyOperator {
        gameStopped = _pause;
    }

    // changes stake requirement
    function changeStakeReq(uint256 _newAmount) external onlyOperator {
        minStake = _newAmount;
        emit StakeReqChanged(_newAmount);
    }

    function changePriceOfNFT(uint256 _newAmount) external onlyOperator {
        vnftPrice = _newAmount;
        emit PriceOfvnftChanged(_newAmount);
    }

    function changeMusePriceOfNFT(uint256 _newAmount) external onlyOperator {
        musePrice = _newAmount;
        emit MusePriceOfvnftChanged(_newAmount);
    }

    modifier updateReward(address account) {
        if (account != address(0)) {
            points[account] = earned(account);
            lastUpdateTime[account] = block.timestamp;
        }
        _;
    }

    //calculate how many points earned so far, this needs to give roughly 1 point a day per 5 tokens staked
    function earned(address account) public view returns (uint256) {
        uint256 blockTime = block.timestamp;
        return
            balance[account]
                .mul(blockTime.sub(lastUpdateTime[account]).mul(2314814814000))
                .div(1e18)
                .add(points[account]);
    }

    function stake(uint256 _amount)
        external
        updateReward(msg.sender)
        notPaused
    {
        require(
            _amount >= minStake,
            "You need to stake at least the min $muse"
        );

        // transfer tokens to this address to stake them
        totalStaked = totalStaked.add(_amount);
        balance[msg.sender] = balance[msg.sender].add(_amount);
        museToken.transferFrom(msg.sender, address(this), _amount);
        emit Staked(msg.sender, _amount);
    }

    // withdraw part of your stake
    function withdraw(uint256 amount) public updateReward(msg.sender) {
        require(amount > 0, "Amount can't be 0");
        require(totalStaked >= amount);
        points[msg.sender] = earned(msg.sender);
        lastUpdateTime[msg.sender] = block.timestamp;
        balance[msg.sender] = balance[msg.sender].sub(amount);
        totalStaked = totalStaked.sub(amount);
        // transfer erc20 back from the contract to the user
        museToken.transfer(msg.sender, amount);
        emit Withdrawal(msg.sender, amount);
    }

    // withdraw all your amount staked
    function exit() external notPaused {
        withdraw(balance[msg.sender]);
    }

    //redeem a vNFT based on a set points price
    function redeem() public updateReward(msg.sender) notPaused {
        require(
            points[msg.sender] >= vnftPrice,
            "Not enough points to redeem vNFT"
        );
        require(
            balance[msg.sender] >= musePrice,
            "Not enough points to redeem vNFT"
        );
        points[msg.sender] = points[msg.sender].sub(vnftPrice);
        lastUpdateTime[msg.sender] = block.timestamp;
        balance[msg.sender] = balance[msg.sender].sub(musePrice);
        museToken.burn(musePrice);
        vNFT.mint(msg.sender);
        emit VnftMinted(msg.sender, vnftPrice, musePrice);
    }
}
// Stake to get vnfts
function withdraw(uint256 amount) public updateReward(msg.sender) {
    require(amount > 0, "Amount can't be 0");
    require(totalStaked >= amount);
    points[msg.sender] = earned(msg.sender);
    lastUpdateTime[msg.sender] = block.timestamp;
    balance[msg.sender] = balance[msg.sender].sub(amount);
    totalStaked = totalStaked.sub(amount);
    // transfer erc20 back from the contract to the user
    museToken.transfer(msg.sender, amount);
    emit Withdrawal(msg.sender, amount);
}

// withdraw part of your stake