contract StakingRewardsFactory is Ownable {
    using SafeERC20 for IERC20;

    // immutables
    address public rewardsToken;
    uint256 public stakingRewardsGenesis;

    // the staking tokens for which the rewards contract has been deployed
    address[] public stakingTokens;

    // info about rewards for a particular staking token
    struct StakingRewardsInfo {
        address stakingRewards;
        uint256 rewardAmount;
    }

    // rewards info by staking token
    mapping(address => StakingRewardsInfo)
        public stakingRewardsInfoByStakingToken;

    constructor(address _rewardsToken, uint256 _stakingRewardsGenesis)
        public
        Ownable()
    {
        require(
            _stakingRewardsGenesis >= block.timestamp,
            "StakingRewardsFactory::constructor: genesis too soon"
        );

        rewardsToken = _rewardsToken;
        stakingRewardsGenesis = _stakingRewardsGenesis;
    }

    ///// permissioned functions

    // deploy a staking reward contract for the staking token, and store the reward amount
    // the reward will be distributed to the staking reward contract no sooner than the genesis
    function deploy(
        address stakingToken,
        uint256 rewardAmount,
        uint256 rewardsDuration
    ) public onlyOwner {
        StakingRewardsInfo storage info =
            stakingRewardsInfoByStakingToken[stakingToken];
        require(
            info.stakingRewards == address(0),
            "StakingRewardsFactory::deploy: already deployed"
        );

        // Args on the StakingRewards
        // address _owner,
        // address _rewardsDistribution,
        // address _rewardsToken,
        // address _stakingToken,
        // uint256 _rewardsDuration
        info.stakingRewards = address(
            new StakingRewards(
                /*_rewardsDistribution=*/
                owner(),
                address(this),
                rewardsToken,
                stakingToken,
                rewardsDuration
            )
        );
        info.rewardAmount = rewardAmount;
        stakingTokens.push(stakingToken);
    }

    // Fallback function to return money to reward distributer via pool deployer
    // In case of issues or incorrect calls or errors
    function refund(uint256 amount, address refundAddress) public onlyOwner {
        require(IERC20(rewardsToken).balanceOf(address(this)) >= amount, "StakingRewardsFactory::refund: Not enough tokens");
        IERC20(rewardsToken).safeTransfer(refundAddress, amount);
    }

    ///// permissionless functions

    // call notifyRewardAmount for all staking tokens.
    function notifyRewardAmounts() public {
        require(
            stakingTokens.length > 0,
            "StakingRewardsFactory::notifyRewardAmounts: called before any deploys"
        );
        for (uint256 i = 0; i < stakingTokens.length; i++) {
            notifyRewardAmount(stakingTokens[i]);
        }
    }

    // notify reward amount for an individual staking token.
    // this is a fallback in case the notifyRewardAmounts costs too much gas to call for all contracts
    function notifyRewardAmount(address stakingToken) public {
        require(
            block.timestamp >= stakingRewardsGenesis,
            "StakingRewardsFactory::notifyRewardAmount: not ready"
        );

        StakingRewardsInfo storage info =
            stakingRewardsInfoByStakingToken[stakingToken];
        require(
            info.stakingRewards != address(0),
            "StakingRewardsFactory::notifyRewardAmount: not deployed"
        );

        if (info.rewardAmount > 0) {
            uint256 rewardAmount = info.rewardAmount;
            info.rewardAmount = 0;

            IERC20(rewardsToken).approve(info.stakingRewards, rewardAmount);
            StakingRewards(info.stakingRewards).notifyRewardAmount(
                rewardAmount,
                address(this)
            );
        }
    }
}
function deploy(
    address stakingToken,
    uint256 rewardAmount,
    uint256 rewardsDuration
) public onlyOwner {
    StakingRewardsInfo storage info =
        stakingRewardsInfoByStakingToken[stakingToken];
    require(
        info.stakingRewards == address(0),
        "StakingRewardsFactory::deploy: already deployed"
    );

    // Args on the StakingRewards
    // address _owner,
    // address _rewardsDistribution,
    // address _rewardsToken,
    // address _stakingToken,
    // uint256 _rewardsDuration
    info.stakingRewards = address(
        new StakingRewards(
            /*_rewardsDistribution=*/
            owner(),
            address(this),
            rewardsToken,
            stakingToken,
            rewardsDuration
        )
    );
    info.rewardAmount = rewardAmount;
    stakingTokens.push(stakingToken);
}

// deploy a staking reward contract for the staking token, and store the reward amount
// the reward will be distributed to the staking reward contract no sooner than the genesis