contract Syndicate {

	//Math data.
	using SafeMath for uint;   //decrlare safe math library usage

	//Stablecoin referrence data.
	address public eursAddress = 0xdB25f211AB05b1c97D595516F45794528a807ad8;   //EURS stablecoin for EUR currency
	address public bgbpAddress = 0xC9a2C4868F0f96fAaa739b59934Dc9cB304112ec;   //BGBP stablecoin for GBP currency
	address public usdtAddress = 0xdAC17F958D2ee523a2206206994597C13D831ec7;   //USDT stablecoin for USD currency
	uint256 public decimalCorrection;   //additional decimals (beyond cents/pennies) used in stablecoin, options = 1 (EURS), 1000000 (BGBP), 10000 (USDT)

    //Admin data.
    address public admin;   //admin's address
    uint256 public adminShare = 40;   //admin's share percent from profit generated by syndicate
    uint256 public adminProfit = 0;   //total net profit which belongs to admin
    
    //Angel data.
    address public angel;   //angel's (syndicate owner's) address
    uint256 public angelInvestment = 0;   //total amount of money angel has invested
    uint256 public angelProfit = 0;   //total net profit which belongs to angel

    //Syndicate data.
    uint256 public pendingSyndicateBalance = 0;   //syndicate's new balance after settling latest bets (not enforced by contract yet)
    int256 public pendingSyndicateProfit = 0;   //syndicate's new profit after settling latest bets (not enforced by contract yet)
    uint256 public syndicateBalance = 0;   //syndicate's current, contract enforced balance
    int256 public syndicateProfit = 0;   //syndicate's current, contract enforced profit
    uint256 public distributionWaitTime;   //time (30 days) which needs to pass before monthly earnings are distributed among angel and admin
    uint256 public closureWaitTime;   //time (47h) which angel needs to wait before he/she can close syndicate after closure announcement
    
    //User data.
    struct Agreement {
        uint256 lsBalance;   //user's current, contract enforced LoopSyndicate balance
        uint256 lsInplay;   //user's current, contract enforced LoopSyndicate inplay balance
        uint256 bookieBalance;   //user's bookmakers current, contract enforced total balance
        uint256 bookieInplay;   //user's bookmakers current, contract enforced total inplay balance
        uint256 userBankroll;   //user's current, contract enforced total capital
        uint256 userProfit;   //total guaranteed profit user generated from placing bets and enforced by contract
    }
    mapping(address => Agreement) public userAgreements;
    
    //Betting data.
    struct Betting {
        uint256 lsBalance;   //user's LoopSyndicate new balance after settling latest bets placed by user (not enforced by contract yet)
        uint256 lsInplay;   //user's LoopSyndicate new inplay balance after settling latest bets placed by user (not enforced by contract yet)
        uint256 bookieBalance;   //user's bookmakers new total balance after settling latest bets placed by user (not enforced by contract yet)
        uint256 bookieInplay;   //user's bookmakers new total inplay balance after settling latest bets placed by user (not enforced by contract yet)
        uint256 userBankroll;   //user's new total capital after settling latest bets placed by user (not enforced by contract yet)
        uint256 userProfit;   //user's new guaranteed profit after settling latest bets placed by user (not enforced by contract yet)
        uint256 pubTime;   //publishing time of latest betting data
    }
    mapping(address => Betting) public bettingResults;

    /*!new betting result's won't take actual effect, i.e. being set in "Agreement" data, if either user or angel 
    rejects this new betting result's data proposed by admin by breaking contract!*/
    
    //Contract agreements status data.
    bool public syndicateActive = true;   //angel's consent to "buy" users' value bets and in exchange provide guaranteed profit to them
    mapping(address => bool) public brokenAgreements;   //list of users who broke contract agreement from their side
    
    //Set admin and angel addresses as well as currency choice on smart contract deployment.
    constructor(address _angel, uint256 _currency) public {
        //Admin and angel addresses.
        admin = msg.sender;
        angel = _angel;
        //EUR
        if (_currency == 1) {
            decimalCorrection = 1;
        }
        //GBP
        else if (_currency == 2) {
            decimalCorrection = 1000000;
        }
        //USD
        else if (_currency == 3) {
            decimalCorrection = 10000;
        }
    }

    //Create admin modifier to ensure that only admin can call certain functions.
    modifier onlyAdmin() {
        require(msg.sender == admin, "only admin allowed to call this function");
        _;
    }

    //Event which is being emitted when user or angel breaks contract agreement.
    event AgreementBreak (
        address _from
    );

    //Event which is being emitted when angel announces his/her syndicate closure.
    event ClosureAnnouncement (
        address _from
    );

    //Call EURS smart contract transferFrom function.
    function eursTransferFrom(address _from, address _to, uint256 _coins) internal {
    	EURSToken stablecoin = EURSToken(eursAddress);
    	require(stablecoin.transferFrom(_from, _to, _coins), "error at EURS stablecoin transferFrom function");
    }

    //Call BGBP smart contract transferFrom function.
    function bgbpTransferFrom(address _from, address _to, uint256 _coins) internal {
    	BGBPToken stablecoin = BGBPToken(bgbpAddress);
    	require(stablecoin.transferFrom(_from, _to, _coins), "error at BGBP stablecoin transferFrom function");
    }

    //Call USDT smart contract transferFrom function.
    function usdtTransferFrom(address _from, address _to, uint256 _coins) internal {
    	USDTToken stablecoin = USDTToken(usdtAddress);
    	require(stablecoin.transferFrom(_from, _to, _coins), "error at USDT stablecoin transferFrom function");
    }

    //Call EURS smart contract transfer function.
    function eursTransfer(address _to, uint256 _coins) internal {
    	EURSToken stablecoin = EURSToken(eursAddress);
    	require(stablecoin.transfer(_to, _coins), "error at EURS stablecoin transfer function");
    }

    //Call BGBP smart contract transfer function.
    function bgbpTransfer(address _to, uint256 _coins) internal {
    	BGBPToken stablecoin = BGBPToken(bgbpAddress);
    	require(stablecoin.transfer(_to, _coins), "error at BGBP stablecoin transfer function");
    }

    //Call USDT smart contract transfer function.
    function usdtTransfer(address _to, uint256 _coins) internal {
    	USDTToken stablecoin = USDTToken(usdtAddress);
    	require(stablecoin.transfer(_to, _coins), "error at USDT stablecoin transfer function");
    }
    
    //Allow angel to deposit into syndicate.
    function angelDeposit(address _from, uint256 _value) public {
        //Make sure angel's deposit value is at least 100 units (cents/pennies).
        require(_value >= 100, "deposit value less than minimum (100 cents/pennies)");
        //Make sure angel address matches.
        require(_from == angel, "from address does not match angel address");
        //Convert cents/pennies into coins.
        uint256 _coins = decimalCorrection.mul(_value);
        //Make transfer from angel's private wallet into Syndicate contract and make sure transfer was successful.
        address _to = address(this);
        if (decimalCorrection == 1) {
        	eursTransferFrom(_from, _to, _coins);
        }
        else if (decimalCorrection == 1000000) {
        	bgbpTransferFrom(_from, _to, _coins);
        }
        else if (decimalCorrection == 10000) {
        	usdtTransferFrom(_from, _to, _coins);
        }
        //Update syndicate's balance value.
        syndicateBalance += _value;
        //Update angel's investment value.
        angelInvestment += _value;
    }
    
    //Allow user to make deposit.
    function userDeposit(address _from, uint256 _value) public {
        //Make sure syndicate is still active.
        require(syndicateActive, "angel has closed this syndicate");
        //Make sure user's deposit value is at least 100 units (cents/pennies).
        require(_value >= 100, "deposit value must be minimum 100 cents/pennies");
        //Convert cents/pennies into coins.
        uint256 _coins = decimalCorrection.mul(_value);
        //Make deposit and make sure it was successful.
        address _to = address(this);
        if (decimalCorrection == 1) {
        	eursTransferFrom(_from, _to, _coins);
        }
        else if (decimalCorrection == 1000000) {
        	bgbpTransferFrom(_from, _to, _coins);
        }
        else if (decimalCorrection == 10000) {
        	usdtTransferFrom(_from, _to, _coins);
        }
        //Update user's balance.
        Agreement memory _agreement = userAgreements[_from];
        _agreement.lsBalance = _agreement.lsBalance + _value;
        userAgreements[_from] = _agreement;
        //Update user's betting results because of new deposit.
        Betting memory _result = bettingResults[_from];
        _result.lsBalance = _result.lsBalance + _value;
        bettingResults[_from] = _result;
    }
    
    //Display user's latest betting results.
    function displayResults(address _user, uint256 _lsBalance, uint256 _lsInplay, uint256 _bookieBalance, uint256 _bookieInplay, uint256 _userBankroll, uint256 _userProfit) public onlyAdmin {
        //Make sure syndicate is still active.
        require(syndicateActive, "angel has closed this syndicate");
        //Make sure user has active contract agreement.
        require(!(brokenAgreements[_user]), "user has broken agreement");
        //Make sure user has balance (if user does not have balance then betting was not possible).
        Agreement memory _agreement = userAgreements[_user];
        require(_agreement.lsBalance > 0 || _agreement.lsInplay > 0, "user does not have balance");
        //Make sure previous betting results were applied.
        Betting memory _result = bettingResults[_user];
        require(_result.pubTime == 0, "previous betting results were not applied");
        //Calculate syndicate's pending profit or loss based on new betting results.
       	uint256 _prevTotalLoopBalance = _result.lsBalance + _result.lsInplay;
       	uint256 _newTotalLoopBalance = _lsBalance + _lsInplay;
        int256 _profit = int(_prevTotalLoopBalance) - int(_newTotalLoopBalance);
        //Update syndicate's pending balance and profit.
        pendingSyndicateBalance += uint(_profit);
        pendingSyndicateProfit += _profit;
        //Set new betting results data.
        _result.lsBalance = _lsBalance;
        _result.lsInplay = _lsInplay;
        _result.bookieBalance = _bookieBalance;
        _result.bookieInplay = _bookieInplay;
        _result.userBankroll = _userBankroll;
        _result.userProfit = _userProfit;
        _result.pubTime = block.timestamp;
        bettingResults[_user] = _result;
    }

    //Allow angel to break his/her syndicate's contract in case of disagreement.
    function breakSyndicate() public {
        //Make sure angel is calling this function.
        require(msg.sender == angel, "msg sender address does not match angel address");
        /*By breaking contract agreement angel rejects any changes to be made to syndicate's balance value but in exchange all 
        profit angel generated will be wiped out as a punishment and 10000 cents/pennies will be payed by angel as 
        a fine. Admin's profit share is also wiped out.*/
        syndicateActive = false;
        //Apply 10000 cents/pennies fine to angel.
        if (syndicateBalance >= 10000) {
            syndicateBalance -= 10000;
            syndicateProfit -= 10000;
        }
        else {
            syndicateBalance = 0;
            syndicateProfit = 0;
        }
        //Wipe out all profit if such exists.
        if (syndicateProfit > 0) {
            syndicateBalance -= uint(syndicateProfit);
            syndicateProfit = 0;
        }
        //Emit signal when angel breaks agreement.
        emit AgreementBreak(msg.sender);
    }
    
    //Allow user to break contract agreement.
    function breakAgreement() public {
        /*By setting broken agreements to true user rejects any changes to be made to his/her current Agreement 
        data and agrees to stop receiving guaranteed profit service.*/
        brokenAgreements[msg.sender] = true;
        //Emit signal when user breaks agreement.
        emit AgreementBreak(msg.sender);
    }
    
    //Confirm latest user betting results as new contract agreement.
    function setNewAgreement(address _user) public onlyAdmin returns(bool) {
        //Make sure syndicate is still active.
        require(syndicateActive, "angel has closed this syndicate");
        //Make sure user has active contract agreement.
        require(!(brokenAgreements[_user]), "user has broken agreement");
        //Make sure user has balance (if user does not have balance then betting was not possible).
        Agreement memory _agreement = userAgreements[_user];
        require(_agreement.lsBalance > 0 || _agreement.lsInplay > 0, "user does not have balance");
        /*Make sure 23h passed since betting results were displayed where user had enough time to reject betting 
        results and break agreement.*/
        Betting memory _result = bettingResults[_user];
        uint256 _waitLimit = _result.pubTime + 82800;   //there are 82,800 seconds in 23h
        if (_result.pubTime == 0 || block.timestamp < _waitLimit) {
            return false;
        }
        //Calculate syndicate's profit or loss based on new betting results.
       	uint256 _prevTotalLoopBalance = _agreement.lsBalance + _agreement.lsInplay;
       	uint256 _newTotalLoopBalance = _result.lsBalance + _result.lsInplay;
        int256 _profit = int(_prevTotalLoopBalance) - int(_newTotalLoopBalance);
        //Update syndicate's balance and profit.
        syndicateBalance += uint(_profit);
        syndicateProfit += _profit;
        //Set user's new accepted agreement data.
        _agreement.lsBalance = _result.lsBalance;
        _agreement.lsInplay = _result.lsInplay;
        _agreement.bookieBalance = _result.bookieBalance;
        _agreement.bookieInplay = _result.bookieInplay;
        _agreement.userBankroll = _result.userBankroll;
        _agreement.userProfit = _result.userProfit;
        userAgreements[_user] = _agreement;
        //Reset user's latest betting results.
        bettingResults[_user] = Betting(0, 0, 0, 0, 0, 0, 0);
        return true;
    }
    
    //Allow user to withdraw his/her balance.
    function userWithdraw(uint256 _value) public {
        Agreement memory _agreement = userAgreements[msg.sender];
        Betting memory _result = bettingResults[msg.sender];
        /*If user broke contract agreement then void all active bets and allow him/her to withdraw balance as 
        recorded in last valid agreement.*/
        if (brokenAgreements[msg.sender]) {
            _value = _agreement.lsBalance + _agreement.lsInplay;
            _agreement.lsBalance = _agreement.lsBalance + _agreement.lsInplay;
            _agreement.lsInplay = 0;
        }
        //Otherwise validate withdrawal request.
        else {
            /*By making withdrawal user accepts last betting results to be enforced in contract as new agreement.*/
            //Make sure user has enough balance.
            require(_result.lsBalance >= _value, "user balance is less than withdrawal value");
            //Calculate syndicate's profit or loss based on new betting results.
	       	uint256 _prevTotalLoopBalance = _agreement.lsBalance + _agreement.lsInplay;
	       	uint256 _newTotalLoopBalance = _result.lsBalance + _result.lsInplay;
	        int256 _profit = int(_prevTotalLoopBalance) - int(_newTotalLoopBalance);
	        //Update syndicate's balance and profit.
	        syndicateBalance += uint(_profit);
	        syndicateProfit += _profit;
            //Set user's new accepted agreement data.
            _agreement.lsBalance = _result.lsBalance;
            _agreement.lsInplay = _result.lsInplay;
            _agreement.bookieBalance = _result.bookieBalance;
            _agreement.bookieInplay = _result.bookieInplay;
            _agreement.userBankroll = _result.userBankroll;
            _agreement.userProfit = _result.userProfit;
        }
        //Convert cents/pennies into coins.
        uint256 _coins = decimalCorrection.mul(_value);
        //Make transfer from contract into user's private wallet and make sure it was successful.
        if (decimalCorrection == 1) {
        	eursTransfer(msg.sender, _coins);
        }
        else if (decimalCorrection == 1000000) {
        	bgbpTransfer(msg.sender, _coins);
        }
        else if (decimalCorrection == 10000) {
        	usdtTransfer(msg.sender, _coins);
        }
        //Update user's balance.
        _agreement.lsBalance = _agreement.lsBalance - _value;
        userAgreements[msg.sender] = _agreement;
        //Set user's latest betting result's data same as agreement data because of new withdrawal.
        _result.lsBalance = _agreement.lsBalance;
        bettingResults[msg.sender] = _result;
    }
    
    //Distribute monthly earnings.
    function distributeEarnings() public returns(bool) {
        //Make sure 30 days passed since last earnings distribution time.
        require(block.timestamp > distributionWaitTime, "month did not pass since last earnings distribution");
        //Make sure profit is at least 100 cents/pennies, otherwise don't bother to distribute earnings.
        if (syndicateProfit < 100) {
            return false;
        }
        //Let admin receive his share.
        uint256 _share = uint(syndicateProfit) * adminShare / 100;
        adminProfit += _share;
        syndicateBalance -= _share;
        syndicateProfit -= int(_share);
        //Let angel to receive the rest.
        angelProfit += uint(syndicateProfit);
        syndicateBalance -= uint(syndicateProfit);
        syndicateProfit = 0;
        //Reset distribution wait time.
        distributionWaitTime = block.timestamp + 2592000;   //there are 2,592,000 seconds in 30 days
        return true;
    }
    
    //Allow angel to withdraw profit.
    function angelWithdraw() public {
        //Make sure angel is making withdrawal.
        require(msg.sender == angel, "msg sender address does not match angel address");
        //Make sure angel has profit.
        require(angelProfit > 0, "angel does not have profit to withdraw");
        //Convert cents/pennies into coins.
        uint256 _coins = decimalCorrection * angelProfit;
        //Make transfer.
        if (decimalCorrection == 1) {
        	eursTransfer(msg.sender, _coins);
        }
        else if (decimalCorrection == 1000000) {
        	bgbpTransfer(msg.sender, _coins);
        }
        else if (decimalCorrection == 10000) {
        	usdtTransfer(msg.sender, _coins);
        }
        //Update angel's profit value.
        angelProfit = 0;
    }
    
    //Allow angel to reinvest profit back into syndicate.
    function angelReinvest() public {
        //Make sure angel is reinvesting profit.
        require(msg.sender == angel, "msg sender address does not match angel address");
        //Make sure angel has profit.
        require(angelProfit > 0, "angel does not have profit to reinvest");
        //Update syndicate balance value.
        syndicateBalance += angelProfit;
        //Set angel profit to zero.
        angelProfit = 0;
    }
    
    //Allow admin to withdraw profit.
    function adminWithdraw(address _to) public onlyAdmin {
        //Make sure admin has profit.
        require(adminProfit > 0, "admin does not have profit share");
        //Convert cents/pennies into coins.
        uint256 _coins = decimalCorrection * adminProfit;
        //Make transfe.
        if (decimalCorrection == 1) {
        	eursTransfer(_to, _coins);
        }
        else if (decimalCorrection == 1000000) {
        	bgbpTransfer(_to, _coins);
        }
        else if (decimalCorrection == 10000) {
        	usdtTransfer(_to, _coins);
        }
        //Update admin's profit value.
        adminProfit = 0;
    }

    //Announce syndicate's closure.
    function announceClosure() public {
        //Make sure angel is making announcement.
        require(msg.sender == angel, "msg sender address does not match angel address");
        //Allow closure after 47 hours from current time.
        closureWaitTime = block.timestamp + 169200;   //there are 169,200 seconds in 47 hours
        //Emit signal when angel announces sydicate's closure.
        emit ClosureAnnouncement(msg.sender);
    }

    //Close angel's syndicate.
    function closeSyndicate() public {
        //Make sure 2 days passed since closure announcement.
        require(block.timestamp > closureWaitTime && closureWaitTime != 0, "2 days did not pass since closure announcement");
        //Subtract admin share from profit, if such exists.
        if (syndicateProfit >= 100) {
            uint256 _share = uint(syndicateProfit) * adminShare / 100;
            adminProfit += _share;
            syndicateBalance -= _share;
            syndicateProfit = 0;
        }
        //Convert cents/pennies into coins.
        uint256 _coins = decimalCorrection * syndicateBalance;
        //Make transfer.
        if (decimalCorrection == 1) {
        	eursTransfer(angel, _coins);
        }
        else if (decimalCorrection == 1000000) {
        	bgbpTransfer(angel, _coins);
        }
        else if (decimalCorrection == 10000) {
        	usdtTransfer(angel, _coins);
        }
        //Update syndicate's balance value.
        syndicateBalance = 0;
        //Reset angel investment value and disable syndicate.
        angelInvestment = 0;
        syndicateActive = false;
        //Reset closure wait time.
        closureWaitTime = 0;
    }
    
}
//Main contract.
function breakAgreement() public {
    /*By setting broken agreements to true user rejects any changes to be made to his/her current Agreement 
    data and agrees to stop receiving guaranteed profit service.*/
    brokenAgreements[msg.sender] = true;
    //Emit signal when user breaks agreement.
    emit AgreementBreak(msg.sender);
}

//Allow user to break contract agreement.