// Contract: Duorice
// Target function: function lendOnStake(address loanerAddress , uint256 stakeId) external payable nonReentrant
 /**
     * @dev External function for users to create a stake
     * @param amount Amount of AVC tokens to stake
     * @param stakingDays Stake duration in days
     */

    
    function EnterStake(uint256 amount, uint256 stakingDays) external {
        require(stakingDays >= 1, 'Staking: Staking days < 1');
        require(stakingDays <= max_stake_days, 'Staking: Staking days > max_stake_days');
        require(amount > 0, "Staking: Amount required");
        require(balanceOf(msg.sender) >= amount, 'Not enough balance');
        
        _updateDaily();
        uint256 stakeId = calcStakeCount(msg.sender);

        overall_stakedTokens += amount;
        mapMemberLobby_overallData[msg.sender].overall_stakedTokens += amount;

        mapMemberStake[msg.sender][stakeId].stakeId = stakeId;
        mapMemberStake[msg.sender][stakeId].userAddress = msg.sender;
        mapMemberStake[msg.sender][stakeId].tokenValue = amount;
        mapMemberStake[msg.sender][stakeId].startDay = currentDay + 1 ;
        mapMemberStake[msg.sender][stakeId].endDay = currentDay + 1 + stakingDays;
        mapMemberStake[msg.sender][stakeId].stakeCollected = false;
        mapMemberStake[msg.sender][stakeId].stake_hasSold = false;
        mapMemberStake[msg.sender][stakeId].stake_hasLoan = false;
        mapMemberStake[msg.sender][stakeId].stake_forSell = false;
        mapMemberStake[msg.sender][stakeId].stake_forLoan = false;
        // stake calcs for days: X >= startDay && X < endDay
        // startDay included / endDay not included

        for (uint256 i = currentDay + 1; i <= currentDay + stakingDays; i++) {
            totalTokensInActiveStake[i] += amount;
        }

        saveTotalToken += amount;
        daysActiveInStakeTokensIncrese[currentDay + 1] += amount;
        daysActiveInStakeTokensDecrase[currentDay + stakingDays + 1] += amount;

        /* On stake AVC tokens get burned */
        _burn(msg.sender, amount);

        emit UserStake (
            msg.sender, 
            block.timestamp,
            amount, 
            stakingDays
        );
    }

    
    /**
     * @dev Counting user's stakes to be usead as stake id for a new stake
     * @param _address address of the user
     */
    function calcStakeCount(address _address) public view returns (uint256) {
        uint256 stakeCount = 0;

        for (uint256 i = 0; mapMemberStake[_address][i].userAddress == _address; i++) {
            stakeCount += 1;
        }

        return(stakeCount);
    }
    

    /**
     * @dev External function for collecting a stake
     * @param stakeId Id of the target stake
     */
    function EndStake(uint256 stakeId) external nonReentrant {
        require(mapMemberStake[msg.sender][stakeId].endDay <= currentDay, 'Stakes end day not reached yet');
        require(mapMemberStake[msg.sender][stakeId].userAddress == msg.sender,'invalid sender');
        require(mapMemberStake[msg.sender][stakeId].stakeCollected == false,'has collected');
        require(mapMemberStake[msg.sender][stakeId].stake_hasLoan == false,'has loan');
        require(mapMemberStake[msg.sender][stakeId].stake_hasSold == false,'has sold');

        _updateDaily();

        /* if the stake is for sell, set it false since it's collected */
        mapMemberStake[msg.sender][stakeId].stake_forSell = false;
        mapMemberStake[msg.sender][stakeId].stake_forLoan = false;

        /* clc BNB divs */
        uint256 profit = calcStakeCollecting(msg.sender, stakeId);
        overall_collectedDivs += profit;
        mapMemberLobby_overallData[msg.sender].overall_collectedDivs += profit;

        mapMemberStake[msg.sender][stakeId].stakeCollected = true;
        payable(msg.sender).transfer(profit);
        
        uint256 stakeReturn = mapMemberStake[msg.sender][stakeId].tokenValue;

        /* Pay the bonus token and stake return, if any, to the staker */
        if (stakeReturn != 0) {
            uint256 bonusAmount = calcBonusToken(mapMemberStake[msg.sender][stakeId].endDay, mapMemberStake[msg.sender][stakeId].startDay, stakeReturn);

            overall_collectedBonusTokens += bonusAmount;

            _mint(msg.sender, stakeReturn + bonusAmount);
        }

        emit UserStakeCollect(
            msg.sender, 
            block.timestamp,
            profit
        );
    }


    /**
     * @dev Calculating a stakes BNB divs payout value by looping through each day of it 
     * @param _address User address
     * @param _stakeId Id of the target stake
     */
    function calcStakeCollecting(address _address , uint256 _stakeId) public view returns (uint256) {
        uint256 userDivs;
        uint256 _endDay = mapMemberStake[_address][_stakeId].endDay;
        uint256 _startDay = mapMemberStake[_address][_stakeId].startDay;
        uint256 _stakeValue = mapMemberStake[_address][_stakeId].tokenValue;

        for (uint256 _day = _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;
    }