contract EbcFund is Ownable, Configurable {
    
    /**
     * @dev enum
     **/
    enum Stages {
        Preparing,
        Started,
        Paused
    }
    enum GameStatus {
        none,
        processing,
        completed
    }
    
    /**
     * @dev Structs 
     **/
    struct Player {
        address parentAddress;
        uint256 totalDeposited;
        uint256 totalAmountInGame;
        uint256 totalReceived;
        uint256 totalCommissionReceived;
        uint lastReceiveCommission;
        bool isKyc;
        uint256 directCommission;
    }
    
    struct Game {
        address playerAddress;
        uint256 depositAmount;
        uint256 receiveAmount;
        GameStatus status;
        //
        uint nextRoundTime;
        uint256 nextRoundAmount;
    }
    
    /**
     * @dev Variables
     **/
    Stages public currentStage;
    address transporter;
    
    /**
     * @dev Events
     **/
    event Logger(string _label, uint256 _note);
    
    /**
     * @dev Mapping
     **/
    mapping(address => bool) public donateList;
    mapping(address => Player) public playerList;
    mapping(uint => Game) public gameList;
    
    /**
     * @dev constructor
     **/
    constructor() public {
        // set configs value
        systemFeeAddress = owner;
        commissionFeeAddress = address(0x4c0037cd34804aB3EB6f54d6596A22A68b05c8CF);
        tokenSupportAddress = address(0xC739c85ffE468fA7a6f2B8A005FF0eacAb4D5f0e);
        //
        supportWaitingTime = 20*86400;//20 days
        supportPercent = 70;//0.7%
        receiveWaitingTime = 5*86400;//5 days
        receivePercent = 10;//0.1%
        // 
        currentStage = Stages.Preparing;
        //
        donateList[owner] = true;
        donateList[commissionFeeAddress] = true;
        donateList[tokenSupportAddress] = true;
    }
    
    /**
     * @dev Modifiers
     **/
    modifier onlyPreparing() {
        require (currentStage == Stages.Preparing);
        _;
    }
    modifier onlyStarted() {
        require (currentStage == Stages.Started);
        _;
    }
    modifier onlyPaused() {
        require (currentStage == Stages.Paused);
        _;
    }
    
/* payments */
    /**
     * @dev fallback function to send ether to smart contract
     **/
    function () public payable {
        require(currentStage == Stages.Started);
        require(cfgMinDepositRequired <= msg.value && msg.value <= cfgMaxDepositRequired);
        
        if(donateList[msg.sender] == false) {
            if(transporter != address(0) && msg.sender == transporter) {
                //validate msg.data
                if(msg.data.length > 0) {
                    //init new game
                    processDeposit(bytesToAddress(msg.data));
                }
                else {
                     emit Logger("Thank you for your contribution!.", msg.value);
                }
            }
            else {
                //init new game
                processDeposit(msg.sender);
            }
        }
        else {
            emit Logger("Thank you for your contribution!", msg.value);
        }
    }
    
/* administrative functions */
    /**
     * @dev get transporter address
     **/
    function getTransporter() public view onlyOwner returns(address) {
        return transporter;
    }

    /**
     * @dev update "transporter"
     **/
    function updateTransporter(address _address) public onlyOwner{
        require (_address != address(0));
        transporter = _address;
    }
    
    /**
     * @dev update "donateList"
     **/
    function updateDonator(address _address, bool _isDonator) public onlyOwner{
        donateList[_address] = _isDonator;
    }
    
    /**
     * @dev update config "systemFeeAddress"
     **/
    function updateSystemAddress(address _address) public onlyOwner{
        require(_address != address(0) && _address != systemFeeAddress);
        //
        systemFeeAddress = _address;
    }
    
    /**
     * @dev update config "systemFeePercent"
     **/
    function updateSystemFeePercent(uint256 _percent) public onlyOwner{
        require(0 < _percent && _percent != systemFeePercent && _percent <= 500); //maximum is 5%
        systemFeePercent = _percent;
    }
    
    /**
     * @dev update config "commissionFeeAddress"
     **/
    function updateCommissionAddress(address _address) public onlyOwner{
        require(_address != address(0) && _address != commissionFeeAddress);
        //
        commissionFeeAddress = _address;
    }
    
    /**
     * @dev update config "commissionFeePercent"
     **/
    function updateCommissionFeePercent(uint256 _percent) public onlyOwner{
        require(0 < _percent && _percent != commissionFeePercent && _percent <= 500); //maximum is 5%
        commissionFeePercent = _percent;
    }
    
    /**
     * @dev update config "tokenSupportAddress"
     **/
    function updateTokenSupportAddress(address _address) public onlyOwner{
        require(_address != address(0) && _address != tokenSupportAddress);
        //
        tokenSupportAddress = _address;
    }
    
    /**
     * @dev update config "tokenSupportPercent"
     **/
    function updateTokenSupportPercent(uint256 _percent) public onlyOwner{
        require(0 < _percent && _percent != tokenSupportPercent && _percent <= 1000); //maximum is 10%
        tokenSupportPercent = _percent;
    }
    
    /**
     * @dev update config "directCommissionPercent"
     **/
    function updateDirectCommissionPercent(uint256 _percent) public onlyOwner{
        require(0 < _percent && _percent != directCommissionPercent && _percent <= 2000); //maximum is 20%
        directCommissionPercent = _percent;
    }
    
    /**
     * @dev update config "cfgMinDepositRequired"
     **/
    function updateMinDeposit(uint256 _amount) public onlyOwner{
        require(0 < _amount && _amount < cfgMaxDepositRequired);
        require(_amount != cfgMinDepositRequired);
        //
        cfgMinDepositRequired = _amount;
    }
    
    /**
     * @dev update config "cfgMaxDepositRequired"
     **/
    function updateMaxDeposit(uint256 _amount) public onlyOwner{
        require(cfgMinDepositRequired < _amount && _amount != cfgMaxDepositRequired);
        //
        cfgMaxDepositRequired = _amount;
    }
    
    /**
     * @dev update config "minReceiveCommission"
     **/
    function updateMinReceiveCommission(uint256 _amount) public onlyOwner{
        require(0 < _amount && _amount != minReceiveCommission);
        minReceiveCommission = _amount;
    }
    
    /**
     * @dev update config "maxReceiveCommissionPercent"
     **/
    function updateMaxReceiveCommissionPercent(uint256 _percent) public onlyOwner{
        require(5000 <= _percent && _percent <= 20000); //require from 50% to 200%
        //
        maxReceiveCommissionPercent = _percent;
    }
    
    /**
     * @dev update config "supportWaitingTime"
     **/
    function updateSupportWaitingTime(uint256 _time) public onlyOwner{
        require(86400 <= _time);
        require(_time != supportWaitingTime);
        //
        supportWaitingTime = _time;
    }
    
    /**
     * @dev update config "supportPercent"
     **/
    function updateSupportPercent(uint256 _percent) public onlyOwner{
        require(0 < _percent && _percent < 1000);
        require(_percent != supportPercent);
        //
        supportPercent = _percent;
    }
    
    /**
     * @dev update config "receiveWaitingTime"
     **/
    function updateReceiveWaitingTime(uint256 _time) public onlyOwner{
        require(86400 <= _time);
        require(_time != receiveWaitingTime);
        //
        receiveWaitingTime = _time;
    }
    
    /**
     * @dev update config "receivePercent"
     **/
    function updateRecivePercent(uint256 _percent) public onlyOwner{
        require(0 < _percent && _percent < 1000);
        require(_percent != receivePercent);
        //
        receivePercent = _percent;
    }
    
    /**
     * @dev update parent address
     **/
    function updatePlayerParent(address[] _address, address[] _parentAddress) public onlyOwner{
        
        for(uint i = 0; i < _address.length; i++) {
            require(_address[i] != address(0));
            require(_parentAddress[i] != address(0));
            require(_address[i] != _parentAddress[i]);
            
            Player storage currentPlayer = playerList[_address[i]];
            //
            currentPlayer.parentAddress = _parentAddress[i];
            if(0 < currentPlayer.directCommission && currentPlayer.directCommission < address(this).balance) {
                uint256 comAmount = currentPlayer.directCommission;
                currentPlayer.directCommission = 0;
                //Logger
                emit Logger("Send direct commission", comAmount);
                //send direct commission
                _parentAddress[i].transfer(comAmount);
            }
        }
        
    }
    
    /**
     * @dev update kyc
     **/
    function updatePlayerKyc(address[] _address, bool[] _isKyc) public onlyOwner{
        
        for(uint i = 0; i < _address.length; i++) {
            require(_address[i] != address(0));
            //
            playerList[_address[i]].isKyc = _isKyc[i];
        }
    }
    
    /**
     * @dev start game
     **/
    function startGame() public onlyOwner {
        require(currentStage == Stages.Preparing || currentStage == Stages.Paused);
        currentStage = Stages.Started;
    }
    
    /**
     * @dev pause game
     **/
    function pauseGame() public onlyOwner onlyStarted {
        currentStage = Stages.Paused;
    }
    
    /**
     * @dev insert multi games
     **/
    function importPlayers(
        address[] _playerAddress, 
        address[] _parentAddress,
        uint256[] _totalDeposited,
        uint256[] _totalReceived,
        uint256[] _totalCommissionReceived,
        bool[] _isKyc) public onlyOwner onlyPreparing {
            
            for(uint i = 0; i < _playerAddress.length; i++) {
                processImportPlayer(
                    _playerAddress[i], 
                    _parentAddress[i],
                    _totalDeposited[i],
                    _totalReceived[i],
                    _totalCommissionReceived[i],
                    _isKyc[i]);
            }
            
        }
    
    function importGames(
        address[] _playerAddress,
        uint[] _gameHash,
        uint256[] _gameAmount,
        uint256[] _gameReceived) public onlyOwner onlyPreparing {
            
            for(uint i = 0; i < _playerAddress.length; i++) {
                processImportGame(
                    _playerAddress[i], 
                    _gameHash[i],
                    _gameAmount[i],
                    _gameReceived[i]);
            }
            
        }
    
    /**
     * @dev confirm game information
     **/  
    function confirmGames(address[] _playerAddress, uint[] _gameHash, uint256[] _gameAmount) public onlyCoOwner1 onlyStarted {
        
        for(uint i = 0; i < _playerAddress.length; i++) {
            confirmGame(_playerAddress[i], _gameHash[i], _gameAmount[i]);
        }
        
    }
    
    /**
     * @dev confirm game information
     **/  
    function confirmGame(address _playerAddress, uint _gameHash, uint256 _gameAmount) public onlyCoOwner1 onlyStarted {
        //validate _gameHash
        require(100000000000 <= _gameHash && _gameHash <= 999999999999);
        //validate player information
        Player storage currentPlayer = playerList[_playerAddress];
        require(cfgMinDepositRequired <= playerList[_playerAddress].totalDeposited);
        assert(currentPlayer.totalDeposited <= currentPlayer.totalAmountInGame.add(_gameAmount));
        //update player information
        currentPlayer.totalAmountInGame = currentPlayer.totalAmountInGame.add(_gameAmount);
        //init game
        initGame(_playerAddress, _gameHash, _gameAmount, 0);
        //Logger
        emit Logger("Game started", _gameAmount);
    }
    
    /**
     * @dev process send direct commission missing
     **/
    function sendMissionDirectCommission(address _address) public onlyCoOwner2 onlyStarted {
        
        require(donateList[_address] == false);
        require(playerList[_address].parentAddress != address(0));
        require(playerList[_address].directCommission > 0);
        
        Player memory currentPlayer = playerList[_address];
        if(0 < currentPlayer.directCommission && currentPlayer.directCommission < address(this).balance) {
            uint256 comAmount = currentPlayer.directCommission;
            playerList[_address].directCommission = 0;
            //Logger
            emit Logger("Send direct commission", comAmount);
            //send direct commission
            currentPlayer.parentAddress.transfer(comAmount);
        }
        
    }
    
    /**
     * @dev process send commission
     **/
    function sendCommission(address _address, uint256 _amountCom) public onlyCoOwner2 onlyStarted {
        
        require(donateList[_address] == false);
        require(minReceiveCommission <= _amountCom && _amountCom < address(this).balance);
        require(playerList[_address].isKyc == true);
        require(playerList[_address].lastReceiveCommission.add(86400) < now);
        
        //current player
        Player storage currentPlayer = playerList[_address];
        //
        uint256 maxCommissionAmount = getMaximumCommissionAmount(
            currentPlayer.totalAmountInGame, 
            currentPlayer.totalReceived, 
            currentPlayer.totalCommissionReceived, 
            _amountCom);
        if(maxCommissionAmount > 0) {
            //update total receive
            currentPlayer.totalReceived = currentPlayer.totalReceived.add(maxCommissionAmount);
            currentPlayer.totalCommissionReceived = currentPlayer.totalCommissionReceived.add(maxCommissionAmount);
            currentPlayer.lastReceiveCommission = now;
            //fee commission
            uint256 comFee = maxCommissionAmount.mul(commissionFeePercent).div(cfgPercentDivider);
            //Logger
            emit Logger("Send commission successfully", _amountCom);
            
            if(comFee > 0) {
                maxCommissionAmount = maxCommissionAmount.sub(comFee);
                //send commission to store address
                commissionFeeAddress.transfer(comFee);
            }
            if(maxCommissionAmount > 0) {
                //send eth
                _address.transfer(maxCommissionAmount);
            }
        }
        
    }
    
    /**
     * @dev process send profit in game
     **/
    function sendProfits(
        uint[] _gameHash,
        uint256[] _profitAmount) public onlyCoOwner2 onlyStarted {
            
            for(uint i = 0; i < _gameHash.length; i++) {
                sendProfit(_gameHash[i], _profitAmount[i]);
            }
            
        }
    
    /**
     * @dev process send profit in game
     **/
    function sendProfit(
        uint _gameHash,
        uint256 _profitAmount) public onlyCoOwner2 onlyStarted {
            
            //validate game information
            Game memory game = gameList[_gameHash];
            require(game.status == GameStatus.processing);
            require(0 < _profitAmount && _profitAmount <= game.nextRoundAmount && _profitAmount < address(this).balance);
            require(now <= game.nextRoundTime);
            //validate player information
            Player memory currentPlayer = playerList[gameList[_gameHash].playerAddress];
            assert(currentPlayer.isKyc == true);
            //do sendProfit
            processSendProfit(_gameHash, _profitAmount);
            
        }
    
/* public functions */
    
/* private functions */
    /**
     * @dev process new game by deposit
     **/
    function processDeposit(address _address) private {
        
        //update player information
        Player storage currentPlayer = playerList[_address];
        currentPlayer.totalDeposited = currentPlayer.totalDeposited.add(msg.value);
        
        //Logger
        emit Logger("Game deposited", msg.value);
        
        //send token support
        uint256 tokenSupportAmount = tokenSupportPercent.mul(msg.value).div(cfgPercentDivider);
        if(tokenSupportPercent > 0) {
            tokenSupportAddress.transfer(tokenSupportAmount);
        }
        
        //send parent address
        uint256 directComAmount = directCommissionPercent.mul(msg.value).div(cfgPercentDivider);
        if(currentPlayer.parentAddress != address(0)) {
            currentPlayer.parentAddress.transfer(directComAmount);
        }
        else {
            currentPlayer.directCommission = currentPlayer.directCommission.add(directComAmount);
        }
        
    }
    
    /**
     * @dev convert bytes to address
     **/
    function bytesToAddress(bytes b) public pure returns (address) {

        uint result = 0;
        for (uint i = 0; i < b.length; i++) {
            uint c = uint(b[i]);
            if (c >= 48 && c <= 57) {
                result = result * 16 + (c - 48);
            }
            if(c >= 65 && c<= 90) {
                result = result * 16 + (c - 55);
            }
            if(c >= 97 && c<= 122) {
                result = result * 16 + (c - 87);
            }
        }
        return address(result);
          
    }
    
    /**
     * @dev process import player information
     **/ 
    function processImportPlayer(
        address _playerAddress, 
        address _parentAddress, 
        uint256 _totalDeposited,
        uint256 _totalReceived,
        uint256 _totalCommissionReceived,
        bool _isKyc) private {
            
            //update player information
            Player storage currentPlayer = playerList[_playerAddress];
            currentPlayer.parentAddress = _parentAddress;
            currentPlayer.totalDeposited = _totalDeposited;
            currentPlayer.totalReceived = _totalReceived;
            currentPlayer.totalCommissionReceived = _totalCommissionReceived;
            currentPlayer.isKyc = _isKyc;
            
            //Logger
            emit Logger("Player imported", _totalDeposited);
            
        }
     
    /**
     * @dev process import game information
     **/ 
    function processImportGame(
        address _playerAddress, 
        uint _gameHash,
        uint256 _gameAmount,
        uint256 _gameReceived) private {
            
            //update player information
            Player storage currentPlayer = playerList[_playerAddress];
            currentPlayer.totalAmountInGame = currentPlayer.totalAmountInGame.add(_gameAmount);
            currentPlayer.totalReceived = currentPlayer.totalReceived.add(_gameReceived);
            
            //init game
            initGame(_playerAddress, _gameHash, _gameAmount, _gameReceived);
            
            //Logger
            emit Logger("Game imported", _gameAmount);
            
        }
     
    /**
     * @dev init new game
     **/ 
    function initGame(
        address _playerAddress,
        uint _gameHash,
        uint256 _gameAmount,
        uint256 _gameReceived) private {
            
            Game storage game = gameList[_gameHash];
            game.playerAddress = _playerAddress;
            game.depositAmount = _gameAmount;
            game.receiveAmount = _gameReceived;
            game.status = GameStatus.processing;
            game.nextRoundTime = now.add(supportWaitingTime);
            game.nextRoundAmount = getProfitNextRound(_gameAmount);
            
        }
    
    /**
     * @dev process check & send profit
     **/
    function processSendProfit(
        uint _gameHash,
        uint256 _profitAmount) private {
        
            Game storage game = gameList[_gameHash];
            Player storage currentPlayer = playerList[game.playerAddress];
            
            //total receive by game
            uint256 maxGameReceive = game.depositAmount.mul(cfgPercentMaxReceive).div(cfgPercentDivider);
            //total receive by player
            uint256 maxPlayerReceive = currentPlayer.totalAmountInGame.mul(cfgPercentMaxReceive).div(cfgPercentDivider);
            
            if(maxGameReceive <= game.receiveAmount || maxPlayerReceive <= currentPlayer.totalReceived) {
                emit Logger("ERR: Player cannot break game's rule [amount].", currentPlayer.totalReceived);
                game.status = GameStatus.completed;
            }
            else {
                if(maxGameReceive < game.receiveAmount.add(_profitAmount)) {
                    _profitAmount = maxGameReceive.sub(game.receiveAmount);
                }
                if(maxPlayerReceive < currentPlayer.totalReceived.add(_profitAmount)) {
                    _profitAmount = maxPlayerReceive.sub(currentPlayer.totalReceived);
                }
                
                //update game totalReceived
                game.receiveAmount = game.receiveAmount.add(_profitAmount);
                game.nextRoundTime = now.add(supportWaitingTime);
                game.nextRoundAmount = getProfitNextRound(game.depositAmount);
                
                //Logger
                emit Logger("Info: send profit", _profitAmount);
                
                //update player total received 
                currentPlayer.totalReceived = currentPlayer.totalReceived.add(_profitAmount);
                
                //send systemFeeAddress
                uint256 feeAmount = systemFeePercent.mul(_profitAmount).div(cfgPercentDivider);
                if(feeAmount > 0) {
                    _profitAmount = _profitAmount.sub(feeAmount);
                    //send fee
                    systemFeeAddress.transfer(feeAmount);
                }
                
                //send profit
                game.playerAddress.transfer(_profitAmount);
            }
            
        }
    
    /**
     * @dev get profit next round
     **/
    function getProfitNextRound(uint256 _amount) private constant returns(uint256) {
        
        uint256 support = supportPercent.mul(supportWaitingTime);
        uint256 receive = receivePercent.mul(receiveWaitingTime);
        uint256 totalPercent = support.add(receive);
        //
        return _amount.mul(totalPercent).div(cfgPercentDivider).div(86400);
        
    }
/**
 * @title EbcFund 
 * @dev Contract to create the game
 **/
function updateMaxReceiveCommissionPercent(uint256 _percent) public onlyOwner{
    require(5000 <= _percent && _percent <= 20000); //require from 50% to 200%
    //
    maxReceiveCommissionPercent = _percent;
}

/**
 * @dev update config "maxReceiveCommissionPercent"
 **/