contract Bundles is ReentrancyGuard {
    
    uint256 public bundleId = 1;
    address public owner;
    
    /* Bundle Token Address */
    
    TokenMintERC20Token public bundle_address;
    
    /* Variable to store the fee collected */
    
    uint256 public fee_collected;       
    
    /* Last Created Informations*/
    
    uint256 public lastcreated;
    uint256 lastbundlecreated;

    struct UserBets{
        uint256[10] bundles;
        uint256[10] amounts;
        uint256[10] prices;
        bool betted;
        uint256 balance;
        uint256 totalbet;
        bool claimed;
    }
    
    struct User{
        uint256[] bundles;
        string username;
        uint256 balance;
        uint256 freebal;
        bool active;
    }
    
    struct Data{
        address[] user;
    }
    
    struct Bundle{
        uint256[10] prices;
        uint256 startime;
        uint256 stakingends;
        uint256 endtime;
    }
    
    mapping(address => mapping(uint256 => UserBets)) bets;
    mapping(uint256 => Bundle) bundle;
    mapping(address => User) user;
    mapping(uint256 => Data) data;
    
    constructor(address _bundle_address) public{
        owner = msg.sender;
        bundle_address = TokenMintERC20Token(_bundle_address);
        lastcreated = block.timestamp;
    }
    
    /*  Registering the username to the contract */
    
    function Register(string memory _username) public returns(bool){
        User storage us = user[msg.sender];
        require(us.active == false,'Existing User');
        us.active = true;
        us.username = _username;
        return true;
    }
    
    /* For placing a prediction in the bundle. */
    
    function PlaceBet(uint256 index,uint256 _prices,uint256 _percent,uint256 _bundleId,uint256 _amount) public returns(bool){
        require(_bundleId <= bundleId,'Invalid Bundle');
        require(bundle_address.allowance(msg.sender,address(this))>=_amount,'Approval failed');
        Bundle storage b = bundle[_bundleId];
        Data storage d = data[_bundleId];
        require(b.stakingends >= block.timestamp,'Ended');
        User storage us = user[msg.sender];
        require(us.active == true,'Register to participate');
        UserBets storage u = bets[msg.sender][_bundleId];
        require(u.bundles[index] == 0,'Already Betted');
        if(u.betted == false){
            u.balance = bundle_address.balanceOf(msg.sender);
            u.betted = true;
        }
        else{
            require(SafeMath.add(u.totalbet,_amount) <= u.balance,'Threshold Reached');
        }
        us.bundles.push(_bundleId);
        us.balance = SafeMath.add(us.balance,_amount);
        u.bundles[index] = _percent; 
        u.prices[index] = _prices; 
        u.amounts[index] = _amount;
        u.totalbet = u.totalbet + _amount;
        d.user.push(msg.sender);
        bundle_address.transferFrom(msg.sender,address(this),_amount);
        return true;
    }
    
    /* Update user balance. Max 40% can be changed */
    
    function updatebal(address _user,uint256 _bundleId,uint256 _reward,bool _isPositive) public returns(bool){
        require(msg.sender == owner,'Not Owner');
        require(_reward <= 40000000,'Invalid Reward Percent'); 
        User storage us = user[_user];
        require(us.active == true,'Invalid User');
        UserBets storage u = bets[_user][_bundleId];
        require(u.claimed == false,'Already Claimed');
        if(_isPositive == true){
            updateFee(_reward,u.totalbet);
            uint256 temp = SafeMath.mul(_reward,90);
            uint256 reward = SafeMath.div(temp,100);
            uint256 a = SafeMath.mul(u.totalbet,reward);
            uint256 b = SafeMath.div(a,10**8);
            uint256 c = SafeMath.add(u.totalbet,b);
            u.claimed = true;
            us.freebal = SafeMath.add(c,us.freebal);
            us.balance = SafeMath.sub(us.balance,u.totalbet);
        }
        else{
            uint256 a = SafeMath.mul(u.totalbet,_reward);
            uint256 b = SafeMath.div(a,10**8);
            uint256 c = SafeMath.sub(u.totalbet,b);
            u.claimed = true;
            us.freebal = SafeMath.add(c,us.freebal);
            us.balance = SafeMath.sub(us.balance,u.totalbet);
        }
        return true;
    }
    
    /* Update the fee incurred */
    
    function updateFee(uint256 r,uint256 amt) internal{
        uint256 temp = SafeMath.mul(r,1);
        uint256 reward = SafeMath.div(temp,100);
        uint256 a = SafeMath.mul(amt,reward);
        uint256 b = SafeMath.div(a,10**8);
        fee_collected = SafeMath.add(fee_collected,b);
    }
    
    /* Create a new bundle after 3 days */
    
    function createBundle(uint256[10] memory _prices) public returns(bool){
        require(msg.sender == owner,'Not Owner');
        require( block.timestamp > lastbundlecreated +  2 days,'Cannot Create');
        Bundle storage b = bundle[bundleId];
        b.prices = _prices;
        b.startime = block.timestamp;
        lastbundlecreated = block.timestamp;
        lastcreated = block.timestamp;
        b.endtime = SafeMath.add(block.timestamp,2 days);
        b.stakingends = SafeMath.add(block.timestamp,1 days);
        bundleId = SafeMath.add(bundleId,1);
        return true;
    }
    
    /* Update new owner of the contract */
    
    function updateowner(address new_owner) public returns(bool){
        require(msg.sender == owner,'Not an Owner');
        owner = new_owner;
        return true;
    }
    
    /* Update the timestamp of the last creted bundle. this function cannot change the bundle time. Last created variable is for display sake. */
    
    function updatetime(uint256 _timestamp) public returns(bool){
        require(msg.sender == owner,'Not an owner');
        lastcreated =  _timestamp;
    }
    
    /* Allows the user to withdraw his claimable balance from the contract */
    
    function withdraw() public nonReentrant returns(bool){
       User storage us = user[msg.sender];
       require(us.active == true,'Invalid User'); 
       require(us.freebal > 0,'No bal');
       bundle_address.transfer(msg.sender,us.freebal);
       us.freebal = 0;
       return true;
    }
    
    /*  Fetch the information about user. His claimable balance, fixed balance & stuff */
     
    function fetchUser(address _user) public view returns(uint256[] memory _bundles,string memory username,uint256 claimable,uint256 staked_balance, bool active){
        User storage us = user[_user];
        return(us.bundles,us.username,us.freebal,us.balance,us.active);
    }
    
    /* Fetch the information of a BundleId */
    
    function fetchBundle(uint256 _bundleId) public view returns(uint256[10] memory _prices,uint256 _start,uint256 _end,uint256 _staking_ends){
        Bundle storage b = bundle[_bundleId];
        return(b.prices,b.startime,b.endtime,b.stakingends);
    }
    
    /* Fetch the prediction information of each user in each bundle. Pass bundleId and User Address to get the strike price as well as the amount in 18 decimals */
    
    function fetchUserBets(address _user, uint256 _bundleId) public view returns(uint256[10] memory _bundles,uint256[10] memory _prices,uint256[10] memory _amounts,uint256 balance,uint256 totalbet){
        UserBets storage u = bets[_user][_bundleId];
        return (u.bundles,u.prices,u.amounts,u.balance,u.totalbet);
    }
    
    /* Fetch all the user wallet predicted in a bundleId. Pass bundleId and will return an array of betters */
    
    function fetchUserInBundle(uint256 _bundleId) public view returns(address[] memory _betters){
        Data storage d = data[_bundleId];
        return d.user;
    }
    
    /*  
        Only Allow the Developer to withdraw the developer fee
    */
    
    function collectdeveloperfee() public nonReentrant returns(bool){
        require(msg.sender == owner,'To Be Claimed By Developer');
        bundle_address.transfer(msg.sender,fee_collected);
        fee_collected = 0;
        return true;
    }
    
}
function PlaceBet(uint256 index,uint256 _prices,uint256 _percent,uint256 _bundleId,uint256 _amount) public returns(bool){
    require(_bundleId <= bundleId,'Invalid Bundle');
    require(bundle_address.allowance(msg.sender,address(this))>=_amount,'Approval failed');
    Bundle storage b = bundle[_bundleId];
    Data storage d = data[_bundleId];
    require(b.stakingends >= block.timestamp,'Ended');
    User storage us = user[msg.sender];
    require(us.active == true,'Register to participate');
    UserBets storage u = bets[msg.sender][_bundleId];
    require(u.bundles[index] == 0,'Already Betted');
    if(u.betted == false){
        u.balance = bundle_address.balanceOf(msg.sender);
        u.betted = true;
    }
    else{
        require(SafeMath.add(u.totalbet,_amount) <= u.balance,'Threshold Reached');
    }
    us.bundles.push(_bundleId);
    us.balance = SafeMath.add(us.balance,_amount);
    u.bundles[index] = _percent; 
    u.prices[index] = _prices; 
    u.amounts[index] = _amount;
    u.totalbet = u.totalbet + _amount;
    d.user.push(msg.sender);
    bundle_address.transferFrom(msg.sender,address(this),_amount);
    return true;
}

/* For placing a prediction in the bundle. */