contract TokenGeyserManager is Ownable, ITokenGeyserManager {
    bool public hasNFTBonus;
    mapping(address => address) public geysers;
    address[] public tokens;

    /**
       @dev Creates an empty Geyser token managers
    
     */
    constructor(bool _hasNftBonus) public {
        hasNFTBonus = _hasNftBonus;

        emit GeyserManagerCreated(msg.sender, address(this));
    }

    /**
        @dev Adds a new geyser in the team
        @param token - address of the staking token for which the geyser was created
        @param geyser - address of the geyser
     */
    function addGeyser(address token, address geyser)
        public
        override
        onlyOwner
        returns (bool)
    {
        require(token != address(0), "TokenGeyserManager: token is invalid");

        require(geyser != address(0), "TokenGeyserManager: geyser is invalid");
        tokens.push(token);
        geysers[token] = geyser;

        emit GeyserAdded(msg.sender, geyser, token);
        return true;
    }

    /**
        @dev Retrieves total rewards earned for a specific staking token
        @param token - address of the ERC20 token
    */
    function getEarned(address token) public view override returns (uint256) {
        if (hasNFTBonus) {
            return TokenGeyser(geysers[token]).getEarnings(msg.sender);
        }
        return TokenGeyserWithoutNFT(geysers[token]).getEarnings(msg.sender);
    }

    /**
        @dev Retrieves total rewards earned for all the staking tokens
    */
     function getEarnings() public view override returns (address[] memory, uint256[] memory){
        address[] memory addresses = new address[](tokens.length);
        uint256[] memory amounts = new uint256[](tokens.length);

        for (uint8 i = 0; i < tokens.length; i++) {
            addresses[i] = tokens[i];
            if (hasNFTBonus) {
                amounts[i] = TokenGeyser(geysers[tokens[i]]).getEarnings(
                    msg.sender
                );
            } else {
                amounts[i] = TokenGeyserWithoutNFT(geysers[tokens[i]])
                    .getEarnings(msg.sender);
            }
        }

        return (addresses, amounts);
     }


    /**
        @dev Retrieves staked amount for a specific token address
        @param token - address of the ERC20 token
    */
    function getStake(address token) public view override returns (uint256) {
        if (hasNFTBonus) {
            return TokenGeyser(geysers[token]).totalStakedFor(msg.sender);
        }
        return TokenGeyserWithoutNFT(geysers[token]).totalStakedFor(msg.sender);
    }


    /**
        @dev Retrieves all stakes for sender
     */
    function getStakes()
        public
        view
        override
        returns (address[] memory, uint256[] memory)
    {
        address[] memory addresses = new address[](tokens.length);
        uint256[] memory amounts = new uint256[](tokens.length);

        for (uint8 i = 0; i < tokens.length; i++) {
            addresses[i] = tokens[i];
            if (hasNFTBonus) {
                amounts[i] = TokenGeyser(geysers[tokens[i]]).totalStakedFor(
                    msg.sender
                );
            } else {
                amounts[i] = TokenGeyserWithoutNFT(geysers[tokens[i]])
                    .totalStakedFor(msg.sender);
            }
        }

        return (addresses, amounts);
    }


    /**
        @dev Stakes all tokens sent
        @param _tokens - array of tokens' addresses you want to stake
        @param amounts - stake amount you want for each token
     */
    function stake(
        address[] calldata _tokens,
        uint256[] calldata amounts,
        int256 nftId
    ) external override returns (bool) {
        //validation
        require(tokens.length > 0, "TokenGeyserManager: _tokens is empty");
        require(amounts.length > 0, "TokenGeyserManager: amounts is empty");
        require(
            _tokens.length == amounts.length,
            "TokenGeyserManager: tokens and amounts need to  be the same length"
        );

        for (uint8 i = 0; i < _tokens.length; i++) {
            ERC20 currentToken = ERC20(_tokens[i]);
            uint256 currentTokenBalance = currentToken.balanceOf(msg.sender);
            uint256 sentAmount = amounts[i];
            string memory tokenName = currentToken.name();

            require(
                currentTokenBalance >= sentAmount,
                string(
                    abi.encodePacked(
                        "TokenGeyserManager: Token ",
                        tokenName,
                        " balance is lower than the amount sent"
                    )
                )
            );
        }
        //actions
        for (uint8 i = 0; i < _tokens.length; i++) {
            if (hasNFTBonus) {
                TokenGeyser(geysers[_tokens[i]]).stake(
                    msg.sender,
                    amounts[i],
                    "",
                    nftId
                );
            } else {
                TokenGeyserWithoutNFT(geysers[_tokens[i]]).stake(
                    msg.sender,
                    amounts[i],
                    ""
                );
            }

            emit Staked(msg.sender, tokens[i], amounts[i]);
        }

        return true;
    }

    /**
        @dev Unstakes all tokens sent
        @param _tokens - array of tokens' addresses you want to unstake
        @param amounts - unstake amount you want for each token
     */
    function unstake(address[] calldata _tokens, uint256[] calldata amounts)
        external
        override
    {
        //validation
        require(tokens.length > 0, "TokenGeyserManager: _tokens is empty");
        require(amounts.length > 0, "TokenGeyserManager: amounts is empty");
        require(
            _tokens.length == amounts.length,
            "TokenGeyserManager: tokens and amounts need to  be the same length"
        );

        for (uint8 i = 0; i < _tokens.length; i++) {
            if (hasNFTBonus) {
                require(
                    amounts[i] == 0,
                    "TokenGeyserManager: only full unstake is allowed"
                );
                TokenGeyser(geysers[_tokens[i]]).unstake(
                    msg.sender,
                    amounts[i],
                    ""
                );
            } else {
                TokenGeyserWithoutNFT(geysers[_tokens[i]]).unstake(
                    msg.sender,
                    amounts[i],
                    ""
                );
            }

            emit Unstaked(msg.sender, _tokens[i], amounts[i]);
        }
    }
}
/** @title Token Geyser Manager */
function addGeyser(address token, address geyser)
    public
    override
    onlyOwner
    returns (bool)
{
    require(token != address(0), "TokenGeyserManager: token is invalid");

    require(geyser != address(0), "TokenGeyserManager: geyser is invalid");
    tokens.push(token);
    geysers[token] = geyser;

    emit GeyserAdded(msg.sender, geyser, token);
    return true;
}

/**
 @dev Adds a new geyser in the team
 @param token - address of the staking token for which the geyser was created
 @param geyser - address of the geyser
 */