contract ToshiCoinFarm is Ownable {
    using SafeMath for uint256;

    struct UserInfo {
        uint256 amountInPool;
        uint256 coinsReceivedToDate;
        /*
         *  At any point in time, the amount of ToshiCoin earned by a user waiting to be claimed is:
         *
         *    Pending claim = (user.amountInPool * pool.coinsEarnedPerToken) - user.coinsReceivedToDate
         *
         *  Whenever a user deposits or withdraws tokens to a pool, the following occurs:
         *   1. The pool's `coinsEarnedPerToken` is rebalanced to account for the new shares in the pool.
         *   2. The `lastRewardBlock` is updated to the latest block.
         *   3. The user receives the pending claim sent to their address.
         *   4. The user's `amountInPool` and `coinsReceivedToDate` get updated for this pool.
         */
    }

    struct PoolInfo {
        IERC20 token;
        uint256 lastUpdateTime;
        uint256 coinsPerDay;
        uint256 coinsEarnedPerToken;
    }

    PoolInfo[] public poolInfo;

    mapping(uint256 => mapping(address => UserInfo)) public userInfo;
    mapping(address => uint256) public tokenPoolIds;

    ToshiCoinNonTradable public ToshiCoin;

    event Deposit(address indexed user, uint256 indexed poolId, uint256 amount);
    event Withdraw(
        address indexed user,
        uint256 indexed poolId,
        uint256 amount
    );
    event EmergencyWithdraw(
        address indexed user,
        uint256 indexed poolId,
        uint256 amount
    );

    constructor(ToshiCoinNonTradable toshiCoinAddress) public {
        ToshiCoin = toshiCoinAddress;
    }

    function poolLength() external view returns (uint256) {
        return poolInfo.length;
    }

    function pendingCoins(uint256 poolId, address user)
        public
        view
        returns (uint256)
    {
        PoolInfo storage pool = poolInfo[poolId];
        UserInfo storage user = userInfo[poolId][user];

        uint256 tokenSupply = pool.token.balanceOf(address(this));
        uint256 coinsEarnedPerToken = pool.coinsEarnedPerToken;

        if (block.timestamp > pool.lastUpdateTime && tokenSupply > 0) {
            uint256 pendingCoins = block
                .timestamp
                .sub(pool.lastUpdateTime)
                .mul(pool.coinsPerDay)
                .div(86400);

            coinsEarnedPerToken = coinsEarnedPerToken.add(
                pendingCoins.mul(1e18).div(tokenSupply)
            );
        }

        return
            user.amountInPool.mul(coinsEarnedPerToken).div(1e18).sub(
                user.coinsReceivedToDate
            );
    }

    function totalPendingCoins(address user) public view returns (uint256) {
        uint256 total = 0;
        uint256 length = poolInfo.length;

        for (uint256 poolId = 0; poolId < length; ++poolId) {
            total = total.add(pendingCoins(poolId, user));
        }

        return total;
    }

    /**
     * @dev Add new pool to the farm. Cannot add the same token more than once.
     */
    function addPool(IERC20 token, uint256 _coinsPerDay) public onlyOwner {
        require(
            tokenPoolIds[address(token)] == 0,
            "ToshiCoinFarm: Added duplicate token pool"
        );
        require(
            address(token) != address(ToshiCoin),
            "ToshiCoinFarm: Cannot add ToshiCoin pool"
        );

        poolInfo.push(
            PoolInfo({
                token: token,
                coinsPerDay: _coinsPerDay,
                lastUpdateTime: block.timestamp,
                coinsEarnedPerToken: 0
            })
        );

        tokenPoolIds[address(token)] = poolInfo.length;
    }

    function setCoinsPerDay(uint256 poolId, uint256 amount) public onlyOwner {
        require(amount >= 0, "ToshiCoinFarm: Coins per day cannot be negative");

        updatePool(poolId);

        poolInfo[poolId].coinsPerDay = amount;
    }

    /**
     * @dev Claim all pending rewards in all pools.
     */
    function claimAll(uint256[] memory poolIds) public {
        uint256 length = poolInfo.length;

        for (uint256 poolId = 0; poolId < length; poolId++) {
            withdraw(poolIds[poolId], 0);
        }
    }

    /**
     * @dev Update pending rewards in all pools.
     */
    function updateAllPools() public {
        uint256 length = poolInfo.length;

        for (uint256 poolId = 0; poolId < length; poolId++) {
            updatePool(poolId);
        }
    }

    /**
     * @dev Update pending rewards for a pool.
     */
    function updatePool(uint256 poolId) public {
        PoolInfo storage pool = poolInfo[poolId];

        if (block.timestamp <= pool.lastUpdateTime) {
            return;
        }

        uint256 tokenSupply = pool.token.balanceOf(address(this));

        if (pool.coinsPerDay == 0 || tokenSupply == 0) {
            pool.lastUpdateTime = block.timestamp;
            return;
        }

        uint256 pendingCoins = block
            .timestamp
            .sub(pool.lastUpdateTime)
            .mul(pool.coinsPerDay)
            .div(86400);

        ToshiCoin.mint(address(this), pendingCoins);

        pool.lastUpdateTime = block.timestamp;
        pool.coinsEarnedPerToken = pool.coinsEarnedPerToken.add(
            pendingCoins.mul(1e18).div(tokenSupply)
        );
    }

    /**
     * @dev Deposit tokens into a pool and claim pending reward.
     */
    function deposit(uint256 poolId, uint256 amount) public {
        require(
            amount > 0,
            "ToshiCoinFarm: Cannot deposit non-positive amount into pool"
        );

        PoolInfo storage pool = poolInfo[poolId];
        UserInfo storage user = userInfo[poolId][msg.sender];

        updatePool(poolId);

        uint256 pending = user
            .amountInPool
            .mul(pool.coinsEarnedPerToken)
            .div(1e18)
            .sub(user.coinsReceivedToDate);

        if (pending > 0) {
            safeToshiCoinClaim(msg.sender, pending);
        }

        user.amountInPool = user.amountInPool.add(amount);
        user.coinsReceivedToDate = user
            .amountInPool
            .mul(pool.coinsEarnedPerToken)
            .div(1e18);

        safePoolTransferFrom(msg.sender, address(this), amount, pool);

        emit Deposit(msg.sender, poolId, amount);
    }

    /**
     * @dev Withdraw tokens from a pool and claim pending reward.
     */
    function withdraw(uint256 poolId, uint256 amount) public {
        PoolInfo storage pool = poolInfo[poolId];
        UserInfo storage user = userInfo[poolId][msg.sender];

        require(
            user.amountInPool >= amount,
            "ToshiCoinFarm: User does not have enough funds to withdraw from this pool"
        );

        updatePool(poolId);

        uint256 pending = user
            .amountInPool
            .mul(pool.coinsEarnedPerToken)
            .div(1e18)
            .sub(user.coinsReceivedToDate);

        if (pending > 0) {
            safeToshiCoinClaim(msg.sender, pending);
        }

        user.amountInPool = user.amountInPool.sub(amount);
        user.coinsReceivedToDate = user
            .amountInPool
            .mul(pool.coinsEarnedPerToken)
            .div(1e18);

        if (amount > 0) {
            safePoolTransfer(msg.sender, amount, pool);
        }

        emit Withdraw(msg.sender, poolId, amount);
    }

    /**
     * @dev Emergency withdraw withdraws funds without claiming rewards.
     *      This should only be used in emergencies.
     */
    function emergencyWithdraw(uint256 poolId) public {
        PoolInfo storage pool = poolInfo[poolId];
        UserInfo storage user = userInfo[poolId][msg.sender];

        require(
            user.amountInPool > 0,
            "ToshiCoinFarm: User has no funds to withdraw from this pool"
        );

        uint256 amount = user.amountInPool;

        user.amountInPool = 0;
        user.coinsReceivedToDate = 0;

        safePoolTransfer(msg.sender, amount, pool);

        emit EmergencyWithdraw(msg.sender, poolId, amount);
    }

    /**
     * @dev Safe ToshiCoin transfer to prevent over-transfers.
     */
    function safeToshiCoinClaim(address to, uint256 amount) internal {
        uint256 coinsBalance = ToshiCoin.balanceOf(address(this));
        uint256 claimAmount = amount > coinsBalance ? coinsBalance : amount;

        ToshiCoin.claim(to, claimAmount);
    }

    /**
     * @dev Safe pool token transfer to prevent over-transfers.
     */
    function safePoolTransfer(
        address to,
        uint256 amount,
        PoolInfo storage pool
    ) internal {
        uint256 tokenBalance = pool.token.balanceOf(address(this));
        uint256 transferAmount = amount > tokenBalance ? tokenBalance : amount;

        pool.token.transfer(to, transferAmount);
    }

    /**
     * @dev Safe pool token transfer from to prevent over-transfers.
     */
    function safePoolTransferFrom(
        address from,
        address to,
        uint256 amount,
        PoolInfo storage pool
    ) internal {
        uint256 tokenBalance = pool.token.balanceOf(from);
        uint256 transferAmount = amount > tokenBalance ? tokenBalance : amount;

        pool.token.transferFrom(from, to, transferAmount);
    }
}
function addPool(IERC20 token, uint256 _coinsPerDay) public onlyOwner {
    require(
        tokenPoolIds[address(token)] == 0,
        "ToshiCoinFarm: Added duplicate token pool"
    );
    require(
        address(token) != address(ToshiCoin),
        "ToshiCoinFarm: Cannot add ToshiCoin pool"
    );

    poolInfo.push(
        PoolInfo({
            token: token,
            coinsPerDay: _coinsPerDay,
            lastUpdateTime: block.timestamp,
            coinsEarnedPerToken: 0
        })
    );

    tokenPoolIds[address(token)] = poolInfo.length;
}

/**
 * @dev Add new pool to the farm. Cannot add the same token more than once.
 */