// Contract: Duorice
// Target function: function buyStakeRequest(address sellerAddress , uint256 stakeId) external payable
(){
        require(launched == false,"contract already launched!");
		LAUNCH_TIME = block.timestamp.sub(1 days);
        launched = true;
    } 

    /* Owner switching the loaning feature status */
    function switchLoaningStatus() external onlyOwner() {
        if (loaningIsPaused == true) {
            loaningIsPaused = false;
        } 
        else if (loaningIsPaused == false) {
            loaningIsPaused = true;
        }
    }
    
    /* Owner switching the stake selling feature status */
    function switchStakeSellingStatus() external onlyOwner() {
        if (stakeSellingIsPaused == true) {
            stakeSellingIsPaused = false;
        } 
        else if (stakeSellingIsPaused == false) {
            stakeSellingIsPaused = true;
        }
    }

    /* Flushed the 1st day's lobby entry to dev address (since there could be no users with tokens on day 1 and therefor no active stakes) */ 
    function flushFirstDayLobbyEntry(uint256 at) external onlyOwner() nonReentrant {
        require(firstDayFlushed == false);
        firstDayFlushed = true;

        payable(avariceTeam_addr).transfer((lobbyEntry[at] * 93) /100);  
    }

    /* turning off the first day flush functionality */ 
      function flushFirstDayLobbyEntrySwitch() external onlyOwner() {
        if (firstDayFlushed == true) {
            firstDayFlushed = false;
        } 
        else if (firstDayFlushed == false) {
            firstDayFlushed = true;
        }
    }
      
    
    /* change marketing wallet address % */ 
    function do_changeMarketingAddress(address adr) external onlyOwner() {
        marketing_addr = adr;
    }

    /**
     * @dev flushes the dev share from stake sells
     */
    function flushdevShareOfStakeSells() external onlyOwner() nonReentrant {
        require(devShareOfStakeSellsAndLoanFee > 0);
        
        payable(marketing_addr).transfer(devShareOfStakeSellsAndLoanFee);
        devShareOfStakeSellsAndLoanFee = 0;
    }


								 
														  
					   

    function _clcDay() public view returns (uint256) {
        return (block.timestamp - LAUNCH_TIME) / (1 days);
    }

    function _updateDaily() public {
        if(currentDay == _clcDay()){
            return;
        }
        // this is true once a day
        
        if (currentDay < dividendsPoolCapDays) {
            for(uint256 _day = currentDay + 1 ; _day <= currentDay * 2 ; _day++){
                dayBNBPool[_day] += (lobbyEntry[currentDay] * 93 ) / (currentDay * 100);
            }

													
																					 
																							
				 

        } else {
            for(uint256 _day = currentDay + 1 ; _day <= currentDay + dividendsPoolCapDays ; _day++){
                dayBNBPool[_day] += (lobbyEntry[currentDay] * 93 ) / (dividendsPoolCapDays * 100);
				 
            }
        }
								   
							   

        currentDay = _clcDay();
							
								  
								  
											   
									

        // total of 7% from every day's lobby entry goes to:
        _sendDevShare();
        // 1% marketing share 
        _sendMarketingShare();
        // 1% buy back to current lobby day
        _buyLobbyBuybackShare();
	 

        emit day_lobby_entry(
            block.timestamp,
            currentDay,
            lobbyEntry[currentDay -1]
        );
        
    }

    /* Gets called once a day and withdraws avarice team's share for the privious day of lobby */ 
    function _sendDevShare() internal nonReentrant {
        require(currentDay > 0);

        // avariceTeamPercentage = 5% of every day's lobby entry
        uint256 avariceTeamPercentage = (lobbyEntry[currentDay - 1] * DM_avariceTeam_percentage) /100;
    
								
        payable(avariceTeam_addr).transfer(avariceTeamPercentage);
																				
																				
																				
    }

    /* Gets called once a day and withdraws marketing's share for the privious day of lobby */ 
    function _sendMarketingShare() internal nonReentrant {
        require(currentDay > 0);

        // marketing share = 1% of every day's lobby entry
        payable(marketing_addr).transfer((lobbyEntry[currentDay - 1] * DM_marketing_percentage) /100);
    }

    /* Gets called once a day and withdraws buy back share for the privious day of lobby */ 
    function _buyLobbyBuybackShare() internal nonReentrant {
        require(currentDay > 0);

        // buyback share = 1% of every day's lobby entry
        payable(buyBack_addr).transfer((lobbyEntry[currentDay - 1] * DM_buyBack_percentage) /100);
    }




    /**
     * @dev External function for entering the auction lobby for the current day
     * @param referrerAddr address of referring user (optional; 0x0 for no referrer)
     */
    function EnterLobby(address referrerAddr) external payable {
        uint256 rawAmount = msg.value;
        require(rawAmount > 0, "ERR: Amount required");

        _updateDaily();
        require(currentDay > 0);
    
        if (mapMemberLobby[msg.sender][currentDay].memberLobbyValue == 0) {
            usersCount++;
            usersCountDaily[currentDay]++;
        }

        mapMemberLobby_overallData[msg.sender].overall_lobbyEnteries += rawAmount;
        lobbyEntry[currentDay] += rawAmount;
        overall_lobbyEntry += rawAmount;

																				  
        mapMemberLobby[msg.sender][currentDay].memberLobbyValue += rawAmount; 
																				
        mapMemberLobby[msg.sender][currentDay].hasCollected = false;

        if (referrerAddr != msg.sender) {
            /* No Self-referred */
            mapMemberLobby[msg.sender][currentDay].referrer = referrerAddr;
        } else {
            mapMemberLobby[msg.sender][currentDay].referrer = address(0);
        }
        
        emit UserLobby(
            msg.sender, 
            block.timestamp,
            rawAmount
        );
    }
    

    /**
     * @dev External function for leaving the lobby / collecting the tokens
     * @param targetDay Target day of lobby to collect 
     */
    function ExitLobby(uint256 targetDay) external {
        require(mapMemberLobby[msg.sender][targetDay].hasCollected == false, "ERR: Already collected");
        _updateDaily();
        require(targetDay < currentDay);

        uint256 tokensToPay = _clcTokenValue(msg.sender, targetDay);

        _mint(msg.sender, tokensToPay);
        mapMemberLobby[msg.sender][targetDay].hasCollected = true;
        
        overall_collectedTokens += tokensToPay;
        mapMemberLobby_overallData[msg.sender].overall_collectedTokens += tokensToPay;

        address referrerAddress = mapMemberLobby[msg.sender][targetDay].referrer;
        if (referrerAddress != address(0)) {
            /* there is a referrer, pay their % ref bonus of tokens */ 
            uint256 refBonus = tokensToPay /ref_bonus_NR;

            _mint(referrerAddress, refBonus);  
            referrerBonusesPaid[referrerAddress] += refBonus;

            /* pay the referred user bonus */
            _mint(msg.sender, tokensToPay /ref_bonus_NRR); 
        }

        emit UserLobbyCollect(
            msg.sender, 
            block.timestamp,
            tokensToPay
        );
    }


    /**
     * @dev Calculating user's share from lobby based on their entry value
     * @param _Day The lobby day
     */
    function _clcTokenValue (address _address, uint256 _Day) public view returns (uint256) {
        require(_Day != 0, "ERR");
        uint256 _tokenVlaue;
																			  


        if(_Day != 0 && _Day < currentDay) {
            _tokenVlaue = (tokenForDay(_Day) / lobbyEntry[_Day]) * mapMemberLobby[_address][_Day].memberLobbyValue; 
        }else{
            _tokenVlaue = 0;
        }
        
        return _tokenVlaue;
    }

    function tokenForDay(uint256 _day) public view returns (uint256 value_) {
        if(_day < 1){
            value_ = 0;
        } else{
            _day = _day - 1;
            if(_day > max_stake_days){
                _day = max_stake_days;
            }
            value_ = defaultLobby;
            for(uint i=0;i<_day;i++){
                value_ = value_.add(value_.mul(2).div(100));
            }
        }
    }

    mapping(uint256 => uint256)public dayBNBPool;
    mapping(uint256 => uint256)public enterytokenMath;
    mapping(uint256 => uint256)public totalTokensInActiveStake;
   
    /**
     * @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
        );
    }