contract TokenFarm is ReentrancyGuard {
    using SafeMath for uint256;

    string public name = "Rewards Staking Dapp";
    address public owner;
    ERC20 public erc20;
    uint256 public pot;

    address[] public stakers;
    mapping(address => uint256) public startTime;
    mapping(address => uint256) public stakingBalance;
    mapping(address => bool) public hasStaked;
    mapping(address => bool) public isStaking;

    constructor(ERC20 _erc20) public {
        erc20 = _erc20;
        owner = msg.sender;
    }

    // Staking tokens
    function stakeTokens(uint256 _amount) external nonReentrant {
        // Require amount greater than 0
        require(_amount > 0, "amount cannot be 0");

        // Transfer tokens to this contract for staking
        erc20.transferFrom(msg.sender, address(this), _amount);

        // Update staking balance
        stakingBalance[msg.sender] = SafeMath.add(
            stakingBalance[msg.sender],
            _amount
        );

        // Add user to stakers array *only* if they haven't staked already
        if (!hasStaked[msg.sender]) {
            stakers.push(msg.sender);
        }

        // Update staking status
        isStaking[msg.sender] = true;
        hasStaked[msg.sender] = true;

        // Add stake timestamp
        startTime[msg.sender] = block.timestamp;

        // Update pot if owner
        if (msg.sender == owner) {
            pot = SafeMath.add(
                pot,
                _amount
            );
        }
    }

    // Unstaking Tokens (Withdraw)
    function unstakeTokens() external nonReentrant {
        // Fetch staking balance
        uint256 balance = stakingBalance[msg.sender];

        // Require amount greater than 0
        require(balance > 0, "staking balance cannot be 0");

        // Transfer tokens from this contract back to staker
        erc20.transfer(msg.sender, balance);

        // Reset staking balance
        stakingBalance[msg.sender] = 0;

        // Update staking status
        isStaking[msg.sender] = false;

        // Update staking timestamp
        startTime[msg.sender] = block.timestamp;

        // Update pot if owner
        if (msg.sender == owner) {
            pot = 0;
        }
    }

    // Get active stakers count
    function getActiveStakeCount() external view returns (uint256) {
        uint256 count = 0;

        for (uint256 i = 0; i < stakers.length; i++) {
            address activeStakerCount = stakers[i];
            if (activeStakerCount != owner && isStaking[activeStakerCount] == true) {
                count = count + 1;
            }
        }
        return count;
    }

    // Get active stakers balance
    function getActiveStakeBalance() external view returns (uint256) {
        uint256 activeBal = 0;

        for (uint256 i = 0; i < stakers.length; i++) {
            address activeStaker = stakers[i];
            if (activeStaker != owner) {
                uint256 balance = stakingBalance[activeStaker];
                activeBal = activeBal + balance;
            }
        }
        return activeBal;
    }

    // Get Staking Rewards pot
    function getStakingPot() external view returns (uint256) {
        return pot;
    }

    // Issuing Tokens
    function issueTokens() external nonReentrant {
        // Only owner can call this function
        require(msg.sender == owner, "caller must be the owner");

        // Issue tokens to all stakers
        for (uint256 i = 0; i < stakers.length; i++) {
            address recipient = stakers[i];
            uint256 balance = stakingBalance[recipient];
            
            // 365 days / 30 = 12.166666667 30 day intervals in a year
            // 21% yearly APY / 12.166666667 intervals=1.726
            uint256 yield = SafeMath.add(SafeMath.div(SafeMath.mul(balance, 1726), 100000), balance);
            
            // Calculate staking time based on current block
            uint256 end = block.timestamp;
            uint256 totalTime = SafeMath.sub(end, startTime[recipient]);

            // Balance is not 0 and exclude owner
            if (balance > 0 && recipient != owner) {
                
                // Staking for more than 30 days in seconds 2592000
                // For testing 20 minutes 1200
                if (totalTime > 2592000) {
                    
                    // Add APY staking interest to existing balance
                    stakingBalance[recipient] = yield;

                    // Update staking timestamp
                    startTime[recipient] = block.timestamp;

                    // Update pot balance
                    pot = SafeMath.sub(
                        stakingBalance[owner],
                        yield
                    );
                }
            }
        }
    }
}
function getStakingPot() external view returns (uint256) {
    return pot;
}

// Get Staking Rewards pot