// Contract: BnsdLaunchPool
// Target function: function addStakingPool(address _token, uint256 _weight) external onlyAdmin
okens(uint256 _pid, uint256 _amount, IERC20 _lpAdd) external {
        require(stakingEnabled[address(_lpAdd)]==true, "NAT"); // NOT AUTHORISED TOKEN
        RaisePoolInfo storage pool = poolInfo[_pid];
        require(block.number > pool.endBlock, "SE"); // SALE NOT ENDED
        address msgSender = _msgSender();
        claimRewardAndAirdrop(_pid);
        userStakeInfo[_pid][msgSender][address(_lpAdd)] = (userStakeInfo[_pid][msgSender][address(_lpAdd)]).sub(_amount);
        _lpAdd.safeTransfer(msgSender, _amount);
        emit UnStake(msg.sender, address(_lpAdd), _pid, _amount);
    }

    // Withdraw airdrop tokens accumulated over one or more than one sale.
    function withdrawAirdropTokens(IERC20 _token, uint256 _amount) external {
        address msgSender = _msgSender();
        airdropBalances[address(_token)][msgSender] = (airdropBalances[address(_token)][msgSender]).sub(_amount);
        _token.safeTransfer(msgSender, _amount);
        emit WithdrawAirdrop(msgSender, address(_token), _amount);
    }

    // Move LP tokens from one sale to another directly => Automatically claims rewards and airdrops also
    function moveLPTokens(uint256 _pid, uint256 _newpid, uint256 _amount, address _lpAdd) external {
        require(stakingEnabled[_lpAdd]==true, "NAT1"); // NOT AUTHORISED TOKEN 1
        RaisePoolInfo storage poolOld = poolInfo[_pid];
        RaisePoolInfo storage poolNew = poolInfo[_newpid];
        require(block.number > poolOld.endBlock, "NUA"); // OLD SALE NOT ENDED
        require(block.number < poolNew.startBlock, "NSA"); // SALE START CHECK FAILED
        address msgSender = _msgSender();
        claimRewardAndAirdrop(_pid);
        userStakeInfo[_pid][msgSender][_lpAdd] = (userStakeInfo[_pid][msgSender][_lpAdd]).sub(_amount);
        userStakeInfo[_newpid][msgSender][_lpAdd] = (userStakeInfo[_newpid][msgSender][_lpAdd]).add(_amount);
        emit MoveStake(msg.sender, _lpAdd, _pid, _newpid, _amount);
    }

    function claimRewardAndAirdrop(uint256 _pid) public {
        RaisePoolInfo storage pool = poolInfo[_pid];
        require(block.number > pool.endBlock, "SE"); // SUM INVALID
        _claimReward(_pid, _msgSender());
        _claimAirdrop(_pid, _msgSender());
    }

    function _claimReward(uint256 _pid, address _user) internal {
        if (rewardClaimed[_pid][_user]==false){
            rewardClaimed[_pid][_user] = true;
            for (uint256 i=0; i<stakingPools.length; i++){
                for(uint256 j=0; j<listSupportedCurrencies[_pid].length; j++){
                    uint256 _tokenAmt = getReward(_pid, _user, stakingPools[i], listSupportedCurrencies[_pid][j]);
                    _creditAirdrop(_user, listSupportedCurrencies[_pid][j], _tokenAmt);
                }
            }
        }
    }

    function _claimAirdrop(uint256 _pid, address _user) internal {
        if (airdropClaimed[_pid][_user]==false){
            airdropClaimed[_pid][_user] = true;
            address _airdropToken = address(airdropInfo[_pid].airdropToken);
            uint256 _tokenAmt = 0;
            for (uint256 i=0; i<stakingPools.length; i++){
                _tokenAmt = _tokenAmt.add(getAirdrop(_pid, _user, stakingPools[i]));
            }
            if(_tokenAmt !=0){
                _creditAirdrop(_user, _airdropToken, _tokenAmt);
            }
        }
    }

    function _creditAirdrop(address _user, address _token, uint256 _amt) internal {
        airdropBalances[_token][_user] = (airdropBalances[_token][_user]).add(_amt);
        emit ClaimAirdrop(_user, _token, _amt);
    }

    function getReward(uint256 _pid, address _user, address _lpAdd, address _token) public view returns (uint256) {
          uint256 stake = stakedLPTokensInfo[_pid][_lpAdd];
          if(stake==0) return 0;
          uint256 _multipliedData = (userStakeInfo[_pid][_user][_lpAdd]).mul(fundsRaisedSoFar[_pid][_token]);
          _multipliedData = (_multipliedData).mul(rewardPer).mul(fee).mul(stakingWeight[_lpAdd]);
          return (((_multipliedData).div(stake)).div(1e8)).div(totalStakeWeight);
    }

    function getAirdrop(uint256 _pid, address _user, address _lpAdd) public view returns (uint256) {
          uint256 _userStaked = userStakeInfo[_pid][_user][_lpAdd];
          uint256 _totalStaked = stakedLPTokensInfo[_pid][_lpAdd];
          if(_totalStaked==0) return 0;
          return ((((_userStaked).mul(airdropInfo[_pid].totalTokensAvailable).mul(stakingWeight[_lpAdd])).div(_totalStaked))).div(totalStakeWeight);
    }

    // Deposit ICO tokens for a use case as reserve.
    function depositReserveICOTokens(uint256 _pid, uint256 _amount, IERC20 _tokenAdd, address _resAdd) external {
        RaisePoolInfo storage pool = poolInfo[_pid];
        UseCasePoolInfo storage poolU = useCaseInfo[_pid][_resAdd];
        address msgSender = _msgSender();
        require(_tokenAdd == pool.raiseToken, "NOT"); // NOT AUTHORISED TOKEN
        require(msgSender == pool.owner, "NAU"); // NOT AUTHORISED USER
        require(poolU.tokensDeposited == false, "DR"); // TOKENS NOT DEPOSITED
        require(poolU.tokensAllocated == _amount && _amount!=0, "NA"); // NOT AVAILABLE
        require(block.number < pool.endBlock, "CRN"); // CANNOT_RESERVE_NOW to avoid deadlocks
        _tokenAdd.safeTransferFrom(msgSender, address(this), _amount);
        totalTokenReserved[_pid] = (totalTokenReserved[_pid]).add(_amount);
        poolU.tokensDeposited = true;
        emit Deposit(msg.sender, _pid, _amount);
    }

    

    // Withdraw extra unsold ICO tokens or extra deposited tokens.
    function withdrawExtraICOTokens(uint256 _pid, uint256 _amount, IERC20 _tokenAdd) external {
        RaisePoolInfo storage pool = poolInfo[_pid];
        address msgSender = _msgSender();

        require(_tokenAdd == pool.raiseToken, "NT"); // NOT AUTHORISED TOKEN
        require(msgSender == pool.owner, "NAU"); // NOT AUTHORISED USER
        require(block.number > pool.endBlock, "NA"); // NOT AVAILABLE TIME

        uint256 _amtAvail = pool.tokensDeposited.sub(pool.totalTokensSold);
        require(_amtAvail >= _amount, "NAT"); // NOT AVAILABLE TOKEN
        pool.tokensDeposited = (pool.tokensDeposited).sub(_amount);
        _tokenAdd.safeTransfer(msgSender, _amount);
        emit Withdraw(msgSender, _pid, _amount);
    }


    // Fetch extra ICO tokens available.
    function fetchExtraICOTokens(uint256 _pid) external view returns (uint256){
        RaisePoolInfo storage pool = poolInfo[_pid];
        return pool.tokensDeposited.sub(pool.totalTokensSold);
    }


    // Deposit tokens to a pool for ICO.
    function deposit(uint256 _pid, uint256 _amount, IERC20 _tokenAdd) external {
        address msgSender = _msgSender();
        uint256 _buyThisStep = _checkDepositAllowed(_pid, address(_tokenAdd), _amount);
        // require(_buyThisStep >= _amount, "CDE");
        _tokenAdd.safeTransferFrom(msgSender, address(this), _amount);
        userDepositInfo[_pid][msgSender][address(_tokenAdd)] = userDepositInfo[_pid][msgSender][address(_tokenAdd)].add(_amount);
        userTokenAllocation[_pid][msgSender] = userTokenAllocation[_pid][msgSender].add(_buyThisStep);
        poolInfo[_pid].totalTokensSold = poolInfo[_pid].totalTokensSold.add(_buyThisStep);
        fundsRaisedSoFar[_pid][address(_tokenAdd)] = fundsRaisedSoFar[_pid][address(_tokenAdd)].add(_amount);
        emit Deposit(msg.sender, _pid, _amount);
    }


    // Vote your favourite ICO project.
    function voteProject(uint256 _pid) external {
        address msgSender = _msgSender();
        require(userVotes[_pid][msgSender]==false,"AVO"); // ALREADY VOTED
        require(poolInfo[_pid].endBlock >= block.number,"CVO"); // CANNOT VOTE NOW
        userVotes[_pid][msgSender] = true;
        poolInfo[_pid].votes = (poolInfo[_pid].votes).add(1);
        emit Voted(msgSender, _pid);
    }

    function _calculatePerAvailable(uint256[] memory _daysArray, uint256[] memory _percentArray, uint256 blockEnd) internal view returns (uint256) {
        uint256 _defaultPer = 10000;
        uint256 _perNow;
        if(_daysArray.length==0){
            return _defaultPer;
        }
        uint256 daysDone = ((block.number).sub(blockEnd)).div(BLOCKS_PER_DAY);
        for (uint256 i = 0; i < _daysArray.length; i++) {
            if(_daysArray[i] <= daysDone){
                _perNow = _perNow.add(_percentArray[i]);
            }
            else {
                break;
            }
        }
        return _perNow;
    }

    function _getPercentAvailable(uint256 _pid, uint256 blockEnd) internal view returns (uint256){
        DistributionInfo storage distInfo = userDistInfo[_pid];
        uint256[] storage _percentArray = distInfo.percentArray;
        uint256[] storage _daysArray = distInfo.daysArray;
        return _calculatePerAvailable(_daysArray, _percentArray, blockEnd);
    }

    // Check amount of ICO tokens withdrawable by user till now - public
    function amountAvailToWithdrawUser(uint256 _pid, address _user) public view returns (uint256){
        RaisePoolInfo storage pool = poolInfo[_pid];
        if(pool.endBlock < block.number){
            uint256 percentAvail = _getPercentAvailable(_pid, pool.endBlock);
            return ((percentAvail).mul(userTokenAllocation[_pid][_user]).div(10000)).sub(userTokenClaimed[_pid][_user]);
        }
        else {
            return 0;
        }
    }

    // Withdraw ICO tokens after sale is over based on distribution rules.
    function withdrawUser(uint256 _pid, uint256 _amount) external {
        RaisePoolInfo storage pool = poolInfo[_pid];
        address msgSender = _msgSender();
        uint256 _amtAvail = amountAvailToWithdrawUser(_pid, msgSender);
        require(_amtAvail >= _amount, "NAT"); // NOT AUTHORISED TOKEN
        userTokenClaimed[_pid][msgSender] = userTokenClaimed[_pid][msgSender].add(_amount);
        totalTokenClaimed[_pid] = totalTokenClaimed[_pid].add(_amount);
        pool.raiseToken.safeTransfer(msgSender, _amount);
        emit Withdraw(msgSender, _pid, _amount);
    }


    function _getPercentAvailableFundRaiser(uint256 _pid, uint256 blockEnd) internal view returns (uint256){
        DistributionInfo storage distInfo = ownerDistInfo[_pid];
        uint256[] storage _percentArray = distInfo.percentArray;
        uint256[] storage _daysArray = distInfo.daysArray;
        return _calculatePerAvailable(_daysArray, _percentArray, blockEnd);
    }

    // Check amount of ICO tokens withdrawable by user till now
    function amountAvailToWithdrawFundRaiser(uint256 _pid, IERC20 _tokenAdd) public view returns (uint256){
        RaisePoolInfo storage pool = poolInfo[_pid];
        if(pool.endBlock < block.number){
            uint256 percentAvail = _getPercentAvailableFundRaiser(_pid, pool.endBlock);
            return (((percentAvail).mul(fundsRaisedSoFar[_pid][address(_tokenAdd)]).div(10000))).sub(fundsClaimedSoFar[_pid][address(_tokenAdd)]);
        }
        else {
            return 0;
        }
    }

    function _getPercentAvailableReserve(uint256 _pid, uint256 blockEnd, address _resAdd) internal view returns (uint256){
        UseCasePoolInfo storage poolU = useCaseInfo[_pid][_resAdd];
        uint256[] storage _percentArray = poolU.unlock_perArray;
        uint256[] storage _daysArray = poolU.unlock_daysArray;
        return _calculatePerAvailable(_daysArray, _percentArray, blockEnd);
    }

    // Check amount of ICO tokens withdrawable by reserve user till now
    function amountAvailToWithdrawReserve(uint256 _pid, address _resAdd) public view returns (uint256){
        RaisePoolInfo storage pool = poolInfo[_pid];
        UseCasePoolInfo storage poolU = useCaseInfo[_pid][_resAdd];
        if(pool.endBlock < block.number){
            uint256 percentAvail = _getPercentAvailableReserve(_pid, pool.endBlock, _resAdd);
            return ((percentAvail).mul(poolU.tokensAllocated).div(10000)).sub(poolU.tokensClaimed);
        }
        else {
            return 0;
        }
    }


    // Withdraw ICO tokens for various use cases as per the schedule promised on provided address.
    function withdrawReserveICOTokens(uint256 _pid, uint256 _amount, IERC20 _tokenAdd) external {
        UseCasePoolInfo storage poolU = useCaseInfo[_pid][_msgSender()];
        require(poolU.reserveAdd == _msgSender(), "NAUTH"); // NOT AUTHORISED USER
        require(_tokenAdd == poolInfo[_pid].raiseToken, "NT"); // NOT AUTHORISED TOKEN
        uint256 _amtAvail = amountAvailToWithdrawReserve(_pid, _msgSender());
        require(_amtAvail >= _amount, "NAT"); // NOT AVAILABLE USER
        poolU.tokensClaimed = poolU.tokensClaimed.add(_amount);
        totalTokenReserved[_pid] = totalTokenReserved[_pid].sub(_amount);
        totalReservedTokenClaimed[_pid] = totalReservedTokenClaimed[_pid].add(_amount);
        _tokenAdd.safeTransfer(_msgSender(), _amount);
        emit Withdraw(_msgSender(), _pid, _amount);
    }


    // Withdraw raised funds after sale is over as per the schedule promised
    function withdrawFundRaiser(uint256 _pid, uint256 _amount, IERC20 _tokenAddress) external {
        RaisePoolInfo storage pool = poolInfo[_pid];
        require(pool.owner == _msgSender(), "NAUTH"); // NOT AUTHORISED USER
        uint256 _amtAvail = amountAvailToWithdrawFundRaiser(_pid, _tokenAddress);
        require(_amtAvail >= _amount, "NAT"); // NOT AUTHORISED TOKEN
        uint256 _fee = ((_amount).mul(fee)).div(1e4);
        uint256 _actualTransfer = _amtAvail.sub(_fee);
        uint256 _feeDev = (_fee).mul(10000 - rewardPer).div(1e4); // Remaining tokens for reward mining 
        fundsClaimedSoFar[_pid][address(_tokenAddress)] = fundsClaimedSoFar[_pid][address(_tokenAddress)].add(_amount);
        _tokenAddress.safeTransfer(_msgSender(), _actualTransfer);
        _tokenAddress.safeTransfer(devaddr, _feeDev);
        emit Withdraw(_msgSender(), _pid, _actualTransfer);
        emit Withdraw(devaddr, _pid, _feeDev);
    }

    // Update dev address by initiating with the previous dev.
    function changeDev(address _newowner) external onlyAdmin {
        potentialAdmin = _newowner;
    }

    function becomeDev() external {
        require(potentialAdmin == msg.sender, "NA"); // NOT ALLOWED
        devaddr = msg.sender;
    }

    // Update temp pool owner address by initiating with the previous pool owner.
    function changePoolOwner(uint256 _pid, address _newowner) external {
        require(_msgSender()==poolInfo[_pid].owner, "OA"); // NOT AUTHORISED USER
        tempAdmin[_pid] = _newowner;
    }

    // Claim pool ownership with new address
    function becomePoolOwner(uint256 _pid) external {
        if (tempAdmin[_pid] == _msgSender()) poolInfo[_pid].owner = _msgSender();
    }

    // Update fee, can never be more than 3%.
    function changeFee(uint256 _fee) external onlyAdmin{
        require(_fee <= 300, "MAX3"); // MAX FEE POSSIBLE
        fee = _fee;
    }

    // To generate a new invite code
    function generateNewCode(address _token, address _poolOwner) external onlyAdminOrTimeLock returns (uint256) {
        uint256 inviteCode = block.number;
        inviteCodeList[inviteCode][_token][_poolOwner] = true;
        return inviteCode;
    }

    // To invalidate an invite code
    function invalidateOldCode(uint256 _inviteCode, address _token, address _poolOwner) external onlyAdmin {
        inviteCodeList[_inviteCode][_token][_poolOwner] = false;
    }