// Contract: Duorice
// Target function: function updateFinishedLoan(address lenderAddress, address loanerAddress, uint256 lenderStakeId, uint256 stakeId) internal nonReentrant
 _startDay ; _day < _endDay && _day < currentDay; _day++) { 
            userDivs += (dayBNBPool[_day] * _stakeValue) / totalTokensInActiveStake[_day]  ;
        }

        return (userDivs - mapMemberStake[_address][_stakeId].loansReturnAmount);
    }


    /**
     * @dev Calculating a stakes Bonus AVC tokens based on stake duration and stake amount
											
     * @param StakeAmount The stake's AVC tokens amount
     */
    function calcBonusToken (uint256 endDay ,uint256 startDay, uint256 StakeAmount) public view returns (uint256) {        
        require(endDay>startDay,'Staking: startDay > endDay');
        require(startDay > 0,'Staking: startDay < 1');
        uint256 StakeDuration = endDay - startDay;
        require(StakeDuration <= max_stake_days, 'Staking: Staking days > max_stake_days');
        uint256 startAmount = tokenForDay(startDay);
        uint256 endAmount = tokenForDay(endDay);

        uint256 _bonusAmount = endAmount.mul(1e18).div(startAmount);
        _bonusAmount = _bonusAmount.sub(1e18).mul(120).div(100);
        _bonusAmount = StakeAmount.mul(_bonusAmount).div(1e18);
        return _bonusAmount;
    }

																						   
																			
									 

						 

												
																	   
								 
				
			
						
		
	


    /**
     * @dev calculating user dividends for a specific day
     */
    
    
    uint256 public devShareOfStakeSellsAndLoanFee;
    uint256 public totalStakesSold;
    uint256 public totalTradeAmount;

    /* withdrawable funds for the stake seller address */
    mapping(address => uint256) public soldStakeFunds;
    mapping(address => uint256) public totalStakeTradeAmount;

    /**
     * @dev User putting up their stake for sell or user changing the previously setted sell price of their stake
     * @param stakeId stake id
     * @param price sell price for the stake
     */
    function sellStakeRequest(uint256 stakeId , uint256 price) external {
        _updateDaily();

        require(stakeSellingIsPaused == false, 'functionality is paused');
        require(mapMemberStake[msg.sender][stakeId].userAddress == msg.sender, 'auth failed');
        require(mapMemberStake[msg.sender][stakeId].stake_hasLoan == false, 'Target stake has an active loan on it');
        require(mapMemberStake[msg.sender][stakeId].stake_hasSold == false, 'Target stake has been sold');
        require(mapMemberStake[msg.sender][stakeId].endDay > currentDay, 'Target stake is ended');

        /* if stake is for loan, remove it from loan requests */
        if (mapMemberStake[msg.sender][stakeId].stake_forLoan == true) {
            cancelStakeLoanRequest(stakeId);
        }

        require(mapMemberStake[msg.sender][stakeId].stake_forLoan == false);

        mapMemberStake[msg.sender][stakeId].stake_forSell = true;
        mapMemberStake[msg.sender][stakeId].price = price;

        emit stake_sell_request (
            msg.sender, 
            block.timestamp,
            price,
            mapMemberStake[msg.sender][stakeId].tokenValue,
            stakeId
        );
    } 
 

    /**
     * @dev A user buying a stake
     * @param sellerAddress stake seller address (current stake owner address)
     * @param stakeId stake id
     */
    function buyStakeRequest(address sellerAddress , uint256 stakeId) external payable {
        _updateDaily();

        require(stakeSellingIsPaused == false, 'functionality is paused');
        require(mapMemberStake[sellerAddress][stakeId].userAddress != msg.sender, 'no self buy'); 
        require(mapMemberStake[sellerAddress][stakeId].userAddress == sellerAddress, 'auth failed'); 
        require(mapMemberStake[sellerAddress][stakeId].stake_hasSold == false, 'Target stake has been sold');
        require(mapMemberStake[sellerAddress][stakeId].stake_forSell == true, 'Target stake is not for sell');
        uint256 priceP = msg.value;
        require(mapMemberStake[sellerAddress][stakeId].price == priceP, 'not enough funds');
        require(mapMemberStake[sellerAddress][stakeId].endDay > currentDay);

        /* 10% stake sell fee ==> 2% dev share & 8% buy back to the current day's lobby */
        lobbyEntry[currentDay] += (mapMemberStake[sellerAddress][stakeId].price * 8) / 100;
        devShareOfStakeSellsAndLoanFee += (mapMemberStake[sellerAddress][stakeId].price * 2) / 100;

        /* stake seller gets 90% of the stake's sold price */
        soldStakeFunds[sellerAddress] += (mapMemberStake[sellerAddress][stakeId].price * 90) / 100 ;
 
        /* setting data for the old owner */
        mapMemberStake[sellerAddress][stakeId].stake_hasSold = true;
        mapMemberStake[sellerAddress][stakeId].stake_forSell = false;
        mapMemberStake[sellerAddress][stakeId].stakeCollected = true;

        totalStakeTradeAmount[msg.sender] += msg.value;
        totalStakeTradeAmount[sellerAddress] += msg.value;

        totalStakesSold += 1;
        totalTradeAmount += msg.value;

        /* new stake & stake ID for the new stake owner (the stake buyer) */
        uint256 newStakeId = calcStakeCount(msg.sender);
        mapMemberStake[msg.sender][newStakeId].userAddress = msg.sender;
        mapMemberStake[msg.sender][newStakeId].tokenValue = mapMemberStake[sellerAddress][stakeId].tokenValue ;
        mapMemberStake[msg.sender][newStakeId].startDay = mapMemberStake[sellerAddress][stakeId].startDay ;
        mapMemberStake[msg.sender][newStakeId].endDay = mapMemberStake[sellerAddress][stakeId].endDay;
        mapMemberStake[msg.sender][newStakeId].loansReturnAmount = mapMemberStake[sellerAddress][stakeId].loansReturnAmount;
        mapMemberStake[msg.sender][newStakeId].stakeId = newStakeId;
        mapMemberStake[msg.sender][newStakeId].stakeCollected = false;
        mapMemberStake[msg.sender][newStakeId].stake_hasSold = false;
        mapMemberStake[msg.sender][newStakeId].stake_hasLoan = false;
        mapMemberStake[msg.sender][newStakeId].stake_forSell = false;
        mapMemberStake[msg.sender][newStakeId].stake_forLoan = false;
        mapMemberStake[msg.sender][newStakeId].price = 0;
    } 

    /**
     * @dev User asking to withdraw their funds from their sold stake
     */
    function withdrawSoldStakeFunds() external nonReentrant {
        require(soldStakeFunds[msg.sender] > 0, 'No funds to withdraw');

        uint256 toBeSend = soldStakeFunds[msg.sender];
        soldStakeFunds[msg.sender] = 0;

        payable(msg.sender).transfer(toBeSend);
    }







    struct loanRequest {
        address loanerAddress; // address
        address lenderAddress; // address (sets after loan request accepted by a lender)
        uint256 stakeId;       // id of the stakes that is being loaned on
        uint256 loanAmount;    // requesting loan BNB amount
        uint256 returnAmount;  // requesting loan BNB return amount
        uint256 duration;      // duration of loan (days)
        uint256 lend_startDay; // lend start day (sets after loan request accepted by a lender)
        uint256 lend_endDay;   // lend end day (sets after loan request accepted by a lender)
        bool hasLoan;
        bool loanIsPaid;       // gets true after loan due date is reached and loan is paid
    }

    struct lendInfo {
        address lenderAddress;
        address loanerAddress;
        uint256 stakeId;
        uint256 loanAmount;
        uint256 returnAmount;
        uint256 endDay;
        bool loanIsPaid;
    }
        

    /* withdrawable funds for the loaner address */
    mapping(address => uint256) public LoanedFunds;
    
    uint256 public totalLoanedAmount;
    uint256 public totalLoanedCount;

    mapping(address => mapping(uint256 => loanRequest)) public mapRequestingLoans;
    mapping(address => mapping(uint256 => lendInfo)) public mapLenderInfo;
    mapping(address => uint256) public lendersPaidAmount; // total amounts of paid to lender
    
    /**
     * @dev User submiting a loan request on their stake or changing the previously setted loan request data
     * @param stakeId stake id
     * @param loanAmount amount of requesting BNB loan
     * @param returnAmount amount of BNB loan return
     * @param loanDuration duration of requesting loan
     */
    function getLoanOnStake(uint256 stakeId ,uint256 loanAmount , uint256 returnAmount , uint256 loanDuration) external {
        _updateDaily();

        require(loaningIsPaused == false, 'functionality is paused');
        require(loanAmount < returnAmount, 'loan return must be higher than loan amount');
        require(loanDuration >= 4, 'lowest loan duration is 4 days'); 
        require(mapMemberStake[msg.sender][stakeId].userAddress == msg.sender, 'auth failed');
        require(mapMemberStake[msg.sender][stakeId].stake_hasLoan == false, 'Target stake has an active loan on it');
        require(mapMemberStake[msg.sender][stakeId].stake_hasSold == false, 'Target stake has been sold');
        require(mapMemberStake[msg.sender][stakeId].endDay - loanDuration > currentDay); 
    
        /* calc stake divs */
        uint256 stakeDivs = calcStakeCollecting(msg.sender, stakeId);

        /* max amount of possible stake return can not be higher than stake's divs */
        require(returnAmount <= stakeDivs); 

        /* if stake is for sell, remove it from sell requests */
        if (mapMemberStake[msg.sender][stakeId].stake_forSell == true) {
            cancelSellStakeRequest(stakeId);
        }

        require(mapMemberStake[msg.sender][stakeId].stake_forSell == false);

        mapMemberStake[msg.sender][stakeId].stake_forLoan = true;

        /* data of the requesting loan */
        mapRequestingLoans[msg.sender][stakeId].loanerAddress = msg.sender;
        mapRequestingLoans[msg.sender][stakeId].stakeId = stakeId;
        mapRequestingLoans[msg.sender][stakeId].loanAmount = loanAmount;
        mapRequestingLoans[msg.sender][stakeId].returnAmount = returnAmount;
        mapRequestingLoans[msg.sender][stakeId].duration = loanDuration;
        mapRequestingLoans[msg.sender][stakeId].loanIsPaid = false;

        emit stake_loan_request (
            msg.sender, 
            block.timestamp,
            loanAmount,
            loanDuration,
            stakeId
        );
    }


    /**
     * @dev Canceling loan request
     * @param stakeId stake id
     */
    function cancelStakeLoanRequest(uint256 stakeId) public {
        require(mapMemberStake[msg.sender][stakeId].stake_hasLoan == false);
        mapMemberStake[msg.sender][stakeId].stake_forLoan = false;
    }


    /**
     * @dev User asking to their stake's sell request
     */
    function cancelSellStakeRequest(uint256 _stakeId) internal {
        require(mapMemberStake[msg.sender][_stakeId].userAddress == msg.sender);
        require(mapMemberStake[msg.sender][_stakeId].stake_forSell == true);
        require(mapMemberStake[msg.sender][_stakeId].stake_hasSold == false);

        mapMemberStake[msg.sender][_stakeId].stake_forSell = false;
    }


    /**
     * @dev User filling loan request (lending)
     * @param loanerAddress address of loaner aka the person who is requesting for loan
     * @param stakeId stake id
     */
    function lendOnStake(address loanerAddress , uint256 stakeId) external payable nonReentrant {
        _updateDaily();

        require(loaningIsPaused == false, 'functionality is paused');
        require(mapMemberStake[loanerAddress][stakeId].userAddress != msg.sender, 'no self lend'); 
        require(mapMemberStake[loanerAddress][stakeId].stake_hasLoan == false, 'Target stake has an active loan on it');
        require(mapMemberStake[loanerAddress][stakeId].stake_forLoan == true, 'Target stake is not requesting a loan');
        require(mapMemberStake[loanerAddress][stakeId].stake_hasSold == false, 'Target stake is sold');
        require(mapMemberStake[loanerAddress][stakeId].endDay > currentDay, 'Target stake duration is finished');
        
        uint256 loanAmount = mapRequestingLoans[loanerAddress][stakeId].loanAmount;
        uint256 returnAmount = mapRequestingLoans[loanerAddress][stakeId].returnAmount;
        uint256 rawAmount = msg.value;

        require(rawAmount == mapRequestingLoans[loanerAddress][stakeId].loanAmount);
        
        /* 2% loaning fee, taken from loaner's stake dividends, 1% buybacks to current day's lobby, 1% dev fee */
        uint256 theLoanFee = (rawAmount * 2) /100;  
        devShareOfStakeSellsAndLoanFee += theLoanFee /2;     
        lobbyEntry[currentDay] += theLoanFee /2;

        mapMemberStake[loanerAddress][stakeId].loansReturnAmount += returnAmount;
        mapMemberStake[loanerAddress][stakeId].stake_hasLoan = true;
        mapMemberStake[loanerAddress][stakeId].stake_forLoan = false;

        mapRequestingLoans[loanerAddress][stakeId].hasLoan = true;
        mapRequestingLoans[loanerAddress][stakeId].loanIsPaid = false;
        mapRequestingLoans[loanerAddress][stakeId].lenderAddress = msg.sender;
        mapRequestingLoans[loanerAddress][stakeId].lend_startDay = currentDay + 1;
        mapRequestingLoans[loanerAddress][stakeId].lend_endDay = currentDay + 1 + mapRequestingLoans[loanerAddress][stakeId].duration;

        uint256 LenderStakeId = clcLenderStakeId(msg.sender);
        mapLenderInfo[msg.sender][LenderStakeId].lenderAddress = msg.sender;
        mapLenderInfo[msg.sender][LenderStakeId].loanerAddress = loanerAddress;
        mapLenderInfo[msg.sender][LenderStakeId].stakeId = LenderStakeId; // not same with the stake id on "mapRequestingLoans"
        mapLenderInfo[msg.sender][LenderStakeId].loanAmount = loanAmount;
        mapLenderInfo[msg.sender][LenderStakeId].returnAmount = returnAmount;
        mapLenderInfo[msg.sender][LenderStakeId].endDay = mapRequestingLoans[loanerAddress][stakeId].lend_endDay;

        LoanedFunds[loanerAddress] += (rawAmount * 98) /100;
        totalLoanedAmount += (rawAmount * 98) /100;
        totalLoanedCount += 1;

        emit stake_lend(
            msg.sender, 
            block.timestamp,
            LenderStakeId
        );

        emit stake_loan(
            loanerAddress,
            block.timestamp,
            stakeId,
            (rawAmount * 98) /100
        );
    }


    /**
     * @dev User asking to withdraw their loaned funds
     */
    function withdrawLoanedFunds() external nonReentrant {
        require(LoanedFunds[msg.sender] > 0, 'No funds to withdraw');

        uint256 toBeSend = LoanedFunds[msg.sender];
        LoanedFunds[msg.sender] = 0;

        payable(msg.sender).transfer(toBeSend);
    }


    /**
     * @dev returns a unique id for the lend by lopping through the user's lends and counting them
     * @param _address the lender user address
     */
    function clcLenderStakeId(address _address) public view returns (uint256) {
        uint256 stakeCount = 0;

        for (uint256 i = 0; mapLenderInfo[_address][i].lenderAddress == _address; i++) {
            stakeCount += 1;
        }

        return stakeCount;
    }
  
  
    /* 
        after a loan's due date is reached there is no automatic way in contract to pay the lender and set the lend data as finished (for the sake of performance and gas)
        so either the lender user calls the "collectLendReturn" function or the loaner user automatically call the  "updateFinishedLoan" function by trying to collect their stake 
    */

    /**
     * @dev Lender requesting to collect their return amount from their finished lend
     * @param stakeId stake id 
     */
    function collectLendReturn(uint256 stakeId, uint256 lenderStakeId) external {
        updateFinishedLoan(msg.sender, mapLenderInfo[msg.sender][stakeId].loanerAddress, lenderStakeId, stakeId);
    }