contract ToshiCashFarm 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 UserInfoERC1155 {
        uint256 amountInPool;
        
        /*
         *  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;
    }
    struct ERC1155Multiplier {
        uint256 id;
        uint256 percentBoost;
       
    }
    struct ERC1155MultiplierUserInfo {
        uint256 multiplier;
        uint256 total;
       
    }

    PoolInfo[] public poolInfo;
    ERC1155Multiplier[] public eRC1155Multiplier;

    mapping(uint256 => mapping(address => UserInfo)) public userInfo;
    mapping(uint256 => mapping(address => UserInfoERC1155)) public userInfoERC1155;
    mapping(address => uint256) public tokenPoolIds;
    mapping(uint256 => uint256) public eRC1155MultiplierIds;
    mapping(address => ERC1155MultiplierUserInfo) public userMultiplier;

    ToshiCoinFarm public toshiCoinFarm;
    ToshiCash public toshiCash;
    ToshimonMinter public toshimonMinter;
    address public toshiCoinFarmDelegate;

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

    constructor(ToshiCoinFarm toshiCoinFarmAddress, address toshiCoinFarmDelegateAddress, ToshiCash toshiCashAddress, ToshimonMinter toshimonMinterAddress) public {
        toshiCash = toshiCashAddress;
        toshiCoinFarm = toshiCoinFarmAddress;
        toshiCoinFarmDelegate = toshiCoinFarmDelegateAddress;
        toshimonMinter = toshimonMinterAddress;
    }

    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 pendingCoinsBonus(address user)
        public
        view
        returns (uint256)
    {
        PoolInfo storage pool = poolInfo[0];
        UserInfo storage user = userInfo[0][msg.sender];
        (uint256 amountInPool, uint256 coinsReceivedToDate) = toshiCoinFarm.userInfo(0,msg.sender);

        uint256 tokenSupply = pool.token.balanceOf(address(toshiCoinFarm));
        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
            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;
    }
    function userMultiplierValue(address user) public view returns (uint256) {

        return userMultiplier[msg.sender].multiplier;
    }
    
    function userERC155StakedTotal(address user) public view returns (uint256) {

        return userMultiplier[msg.sender].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(
            address(token) != address(toshiCash),
            "ToshiCashFarm: Cannot add ToshiCash pool"
        );

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

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

    function addERC1155Multiplier(uint256 _id, uint256 _percentBoost) public onlyOwner {
        require(
            eRC1155MultiplierIds[_id] == 0,
            "ToshiCashFarm: Cannot add duplicate Toshimon E%C1155"
        );

        eRC1155Multiplier.push(
            ERC1155Multiplier({
                id:_id,
                percentBoost: _percentBoost
            })
        );

        eRC1155MultiplierIds[_id] = 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 = 1; poolId < length; poolId++) {
            updatePool(poolId);
        }
    }

    /**
     * @dev Update pending rewards for a pool.
     */
    function updatePool(uint256 poolId) public {
        PoolInfo storage pool = poolInfo[poolId];
        ERC1155MultiplierUserInfo storage multiplier = userMultiplier[msg.sender];
        if (block.timestamp <= pool.lastUpdateTime) {
            return;
        }

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

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

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

        toshiCash.mint(address(this), pendingCoins.mul(multiplier.multiplier.add(100)).div(100));

        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 depositERC1155(uint256 poolId) public {

        ERC1155Multiplier storage erc1155 = eRC1155Multiplier[poolId];
        UserInfoERC1155 storage user = userInfoERC1155[poolId][msg.sender];
        ERC1155MultiplierUserInfo storage multiplier = userMultiplier[msg.sender];
        
        require(
            user.amountInPool == 0,
            "ToshiCoinFarm: User can only stake one of each erc1155 type"
        );
        withdraw(1,0);
        
        user.amountInPool = user.amountInPool.add(1);
        multiplier.multiplier = multiplier.multiplier.add(erc1155.percentBoost);
        multiplier.total = multiplier.total.add(erc1155.percentBoost);
        if(multiplier.multiplier > 100){
            multiplier.multiplier = 100;
        }

        toshimonMinter.burn(msg.sender,erc1155.id, 1);

        emit DepositERC1155(msg.sender, erc1155.id);
    }
      /**
     * @dev Deposit tokens into a pool and claim pending reward.
     */
    function withdrawERC1155(uint256 poolId) public {

        ERC1155Multiplier storage erc1155 = eRC1155Multiplier[poolId];
        UserInfoERC1155 storage user = userInfoERC1155[poolId][msg.sender];
        ERC1155MultiplierUserInfo storage multiplier = userMultiplier[msg.sender];
        
        
        require(
            user.amountInPool >= 1,
            "ToshiCoinFarm: User does not have enough funds to withdraw from this pool"
        );
        withdraw(1,0);
        
        user.amountInPool = user.amountInPool.sub(1);
        
        
        multiplier.total = multiplier.total.sub(erc1155.percentBoost);
        multiplier.multiplier = multiplier.total;
        if(multiplier.multiplier > 100){
            multiplier.multiplier = 100;
        }
        
          toshimonMinter.mint(msg.sender,erc1155.id, 1,"");
        

        emit WithdrawERC1155(msg.sender, erc1155.id);
    }

    function claimBonus() public {
        PoolInfo storage pool = poolInfo[0];
        UserInfo storage user = userInfo[0][msg.sender];
        (uint256 amountInPool, uint256 coinsReceivedToDate) = toshiCoinFarm.userInfo(0,msg.sender);
        

        
        updatePool(0);

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

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

        user.coinsReceivedToDate = pending;

        emit Withdraw(msg.sender, pending, amountInPool);
    }


   //
 /**
     * @dev Deposit tokens into a pool and claim pending reward.
     */
    function deposit(uint256 poolId, uint256 amount) public {
        require(
            amount > 0,
            "ToshiCashFarm: Cannot deposit non-positive amount into pool"
        );
        require(
            poolId != 0,
            "ToshiCashFarm: There is no pool 0"
        );
        ERC1155MultiplierUserInfo storage multiplier = userMultiplier[msg.sender];
        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) {
            safeToshiCashClaim(msg.sender, pending.mul(multiplier.multiplier.add(100)).div(100));
        }

        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 {
        ERC1155MultiplierUserInfo storage multiplier = userMultiplier[msg.sender];
        PoolInfo storage pool = poolInfo[poolId];
        UserInfo storage user = userInfo[poolId][msg.sender];

        require(
            user.amountInPool >= amount,
            "ToshiCashFarm: User does not have enough funds to withdraw from this pool"
        );
        
        require(
            poolId != 0,
            "ToshiCashFarm: There is no pool 0"
        );

        updatePool(poolId);

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

        if (pending > 0) {
            safeToshiCashClaim(msg.sender, pending.mul(multiplier.multiplier.add(100)).div(100));
        }

        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,
            "ToshiCashFarm: 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 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);
    }
    /**
     * @dev Safe ToshiCoin transfer to prevent over-transfers.
     */
    function safeToshiCashClaim(address to, uint256 amount) internal {
       uint256 coinsBalance = toshiCash.balanceOf(address(this));
       uint256 claimAmount = amount > coinsBalance ? coinsBalance : amount;

       toshiCash.claim(to, claimAmount);
    }
    

   
}
function depositERC1155(uint256 poolId) public {

    ERC1155Multiplier storage erc1155 = eRC1155Multiplier[poolId];
    UserInfoERC1155 storage user = userInfoERC1155[poolId][msg.sender];
    ERC1155MultiplierUserInfo storage multiplier = userMultiplier[msg.sender];
        
    require(
        user.amountInPool == 0,
        "ToshiCoinFarm: User can only stake one of each erc1155 type"
    );
    withdraw(1,0);
        
    user.amountInPool = user.amountInPool.add(1);
    multiplier.multiplier = multiplier.multiplier.add(erc1155.percentBoost);
    multiplier.total = multiplier.total.add(erc1155.percentBoost);
    if(multiplier.multiplier > 100){
        multiplier.multiplier = 100;
    }

    toshimonMinter.burn(msg.sender,erc1155.id, 1);

    emit DepositERC1155(msg.sender, erc1155.id);
}

/**
 * @dev Deposit tokens into a pool and claim pending reward.
 */