contract ZZZMigrationPool is LPTokenWrapper, Ownable {
    using SafeERC20 for IERC20;
    IERC20 public rewardToken;
    IERC20 public zzzV2token;

    ZZZv2Interface public zzzV2 = ZZZv2Interface(address(0));

    uint256 public DURATION = 2 weeks;
    uint256 public periodFinish;
    uint256 public rewardRate;
    uint256 public lastUpdateTime;
    uint256 public rewardPerTokenStored;
    uint256 public deployedTime;
    uint256 public migrationStatus = 0; // 0 = Pool Open, 1 = Pool Closed (Migration Started), 2 = Migration Complete

    mapping(address => uint256) public userRewardPerTokenPaid;
    mapping(address => uint256) public rewards;

    event RewardAdded(uint256 reward);
    event Staked(address indexed user, uint256 amount);
    event Withdrawn(address indexed user, uint256 amount);
    event RewardPaid(address indexed user, uint256 reward);

    constructor(address _zzzV1) public {
        setStakingToken(_zzzV1);
        deployedTime = block.timestamp;
    }

    function setOwner(address _newOwner) external onlyOwner {
        super.transferOwnership(_newOwner);
    }

    function lastTimeRewardApplicable() public view returns (uint256) {
        return Math.min(block.timestamp, periodFinish);
    }

    // Returns the current rate of rewards per token (doh)
    function rewardPerToken() public view returns (uint256) {
        // Do not distribute rewards before games begin
        if (block.timestamp <= startTime) {
            return 0;
        }
        if (_totalSupply == 0) {
            return rewardPerTokenStored;
        }
        // Effective total supply takes into account all the multipliers bought.
        uint256 effectiveTotalSupply = _totalSupply.add(_totalSupplyAccounting);
        return
            rewardPerTokenStored.add(
                lastTimeRewardApplicable()
                    .sub(lastUpdateTime)
                    .mul(rewardRate)
                    .mul(1e18)
                    .div(effectiveTotalSupply)
            );
    }

    // Returns the current reward tokens that the user can claim.
    function earned(address account) public view returns (uint256) {
        // Each user has it's own effective balance which is just the staked balance multiplied by boost level NAP multiplier.
        return
            _balances[account]
                .mul(rewardPerToken().sub(userRewardPerTokenPaid[account]))
                .div(1e18)
                .add(rewards[account]);
    }

    // Staking function which updates the user balances in the parent contract
    function stake(uint256 amount) public override {
        updateReward(msg.sender);
        require(amount > 0, "Cannot stake 0");
        require(
            migrationStatus == 0,
            "Cannot stake tokens anymore, Migration is being processed and Pool has closed"
        );
        super.stake(amount);
        emit Staked(msg.sender, amount);
    }

    // Withdraw function to remove stake, with output being in v2 of Token
    function withdraw(uint256 amount) public override {
        require(amount > 0, "Cannot withdraw 0");
        require(
            migrationStatus == 2,
            "Cannot withdraw tokens before migration has finished"
        );
        updateReward(msg.sender);

        zzzV2token.safeTransfer(msg.sender, amount);
        super.withdraw(amount);

        emit Withdrawn(msg.sender, amount);
    }

    // Get the earned rewards and withdraw staked tokens
    function exit() external {
        require(
            migrationStatus == 2,
            "Cannot withdraw tokens before migration has finished"
        );
        getReward();
        withdraw(balanceOf(msg.sender));
    }

    // Sends out the reward tokens to the user.
    function getReward() public {
        require(
            migrationStatus == 2,
            "Cannot withdraw tokens before migration has finished"
        );
        updateReward(msg.sender);
        uint256 reward = earned(msg.sender);
        if (reward > 0) {
            rewardToken.safeTransfer(msg.sender, reward);
            rewards[msg.sender] = 0;
            emit RewardPaid(msg.sender, reward);
        }
    }

    // Called to start the pool with the reward amount it should distribute
    // The reward period will be the duration of the pool.
    function notifyRewardAmount(uint256 reward) external onlyOwner {
        startTime = now;
        updateRewardPerTokenStored();
        if (block.timestamp >= periodFinish) {
            rewardRate = reward.div(DURATION);
        } else {
            uint256 remaining = periodFinish.sub(block.timestamp);
            uint256 leftover = remaining.mul(rewardRate);
            rewardRate = reward.add(leftover).div(DURATION);
        }
        lastUpdateTime = block.timestamp;
        periodFinish = block.timestamp.add(DURATION);
        emit RewardAdded(reward);
    }

    // Notify the reward amount without updating time;
    function notifyRewardAmountWithoutUpdateTime(uint256 reward)
        external
        onlyOwner
    {
        updateRewardPerTokenStored();
        if (block.timestamp >= periodFinish) {
            rewardRate = reward.div(DURATION);
        } else {
            uint256 remaining = periodFinish.sub(block.timestamp);
            uint256 leftover = remaining.mul(rewardRate);
            rewardRate = reward.add(leftover).div(DURATION);
        }
        emit RewardAdded(reward);
    }

    // Ejects any remaining tokens from the pool.
    // Callable only after the pool has started and the pools reward distribution period has finished.
    function eject() external onlyOwner {
        require(
            startTime < block.timestamp && block.timestamp >= periodFinish,
            "Cannot eject before period finishes or pool has started"
        );
        uint256 currBalance = rewardToken.balanceOf(address(this));
        rewardToken.safeTransfer(msg.sender, currBalance);
    }

    // Forcefully retire a pool
    // Only sets the period finish to 0
    // This will prevent more rewards from being disbursed
    function kill() external onlyOwner {
        periodFinish = block.timestamp;
    }

    function updateRewardPerTokenStored() internal {
        rewardPerTokenStored = rewardPerToken();
        lastUpdateTime = lastTimeRewardApplicable();
    }

    function updateReward(address account) internal {
        updateRewardPerTokenStored();
        rewards[account] = earned(account);
        userRewardPerTokenPaid[account] = rewardPerTokenStored;
    }

    function calculateCycle(
        uint256 _deployedTime,
        uint256 _currentTime,
        uint256 _duration
    ) public pure returns (uint256) {
        uint256 cycles = (_currentTime.sub(_deployedTime)).div(_duration);
        return cycles;
    }

    function migrationBegin() public onlyOwner {
        // Transfers all the LP tokens to the Dev address for Migration process
        stakingToken.safeTransfer(
            msg.sender,
            stakingToken.balanceOf(address(this))
        );

        // Locks the Staking function, closing the Pool.
        migrationStatus++;
    }

    /*
    - ZZZ upgraded to ZZZV2
    - Transfer the ZZZV2 tokens to the contract
    */

    function migrationFinish(address _zzzv2address, address _napv2address)
        public
        onlyOwner
    {
        rewardToken = IERC20(_napv2address);
        zzzV2 = ZZZv2Interface(_zzzv2address);
        zzzV2token = IERC20(_zzzv2address);

        // Unlocks Withdraw, Exit and getReward functions
        migrationStatus++;
    }

    // Emergency Function to change Migration Status in case of unforseen error
    function emergencyMigrationStatusChange(uint256 _newMigrationStatus)
        public
        onlyOwner
    {
        migrationStatus = _newMigrationStatus;
    }
}
function notifyRewardAmount(uint256 reward) external onlyOwner {
        startTime = now;
        updateRewardPerTokenStored();
        if (block.timestamp >= periodFinish) {
            rewardRate = reward.div(DURATION);
        } else {
            uint256 remaining = periodFinish.sub(block.timestamp);
            uint256 leftover = remaining.mul(rewardRate);
            rewardRate = reward.add(leftover).div(DURATION);
        }
        lastUpdateTime = block.timestamp;
        periodFinish = block.timestamp.add(DURATION);
        emit RewardAdded(reward);
    }
// Called to start the pool with the reward amount it should distribute
// The reward period will be the duration of the pool.