contract Dispatcher is BrokerManager {

    enum GameState {Initialized, Started, Finished, Cancelled}

    struct GameTeam {
        uint32 userId;
        uint32 sponsorId;
        uint64 prizeSum;
        uint16 userFee;
    }

    struct Game {
        GameState state;
        uint64 entryFee;
        uint32 serviceFee;
        uint32 registrationDueDate;

        bytes32 teamsHash;
        bytes32 statsHash;

        uint32 teamsNumber;
        uint64 awardSent;
    }

    /** balance manager **/
    BalanceManager public balanceManager;
    /** player teams **/
    mapping(uint32 => mapping(uint48 => GameTeam)) public teams;
    /** games **/
    mapping(uint32 => Game) public games;

    constructor(address _balanceManagerAddress) public {
        balanceManager = BalanceManager(_balanceManagerAddress);
    }

    /**
     * Create new game
     */
    function createGame(
        uint32 _gameId,
        uint64 _entryFee,
        uint32 _serviceFee,
        uint32 _registrationDueDate
    )
    external
    onlyServer
    {
        require(
            games[_gameId].entryFee == 0
            && _gameId > 0
            && _entryFee > 0
            && _registrationDueDate > 0
        );
        games[_gameId] = Game(GameState.Initialized, _entryFee, _serviceFee, _registrationDueDate, 0x0, 0x0, 0, 0);
    }

    /**
     * Participate game
     */
    function participateGame(
        uint32 _gameId,
        uint32 _teamId,
        uint32 _userId,
        uint32 _sponsorId
    )
    external
    onlyServer
    {
        Game storage game = games[_gameId];
        require(
            _gameId > 0
            && game.state == GameState.Initialized
            && _teamId > 0
            && _userId > 0
            && teams[_gameId][_teamId].userId == 0
            && game.registrationDueDate > uint32(now)
        );

        uint16 userFee = 0;
        if (_sponsorId > 0) {
            require(balanceManager.balances(_sponsorId) >= game.entryFee && investTerms[_sponsorId][_userId].amount > game.entryFee);
            balanceManager.spendUserBalance(_sponsorId, game.entryFee);
            investTerms[_sponsorId][_userId].amount -= game.entryFee;
            userFee = investTerms[_sponsorId][_userId].userFee;
        }
        else {
            require(balanceManager.balances(_userId) >= game.entryFee);
            balanceManager.spendUserBalance(_userId, game.entryFee);
        }

        teams[_gameId][_teamId] = GameTeam(_userId, _sponsorId, 0, userFee);
        game.teamsNumber++;
    }

    /**
     * Stop participate game, store teams hash
     */
    function startGame(uint32 _gameId, bytes32 _hash) external onlyServer {
        Game storage game = games[_gameId];
        require(
            game.state == GameState.Initialized
            && _gameId > 0
        && _hash != 0x0
        );

        game.teamsHash = _hash;
        game.state = GameState.Started;
    }

    /**
     * Cancel game
     */
    function cancelGame(uint32 _gameId) external onlyServer {
        Game storage game = games[_gameId];
        require(
            _gameId > 0
            && game.state < GameState.Finished
        );
        game.state = GameState.Cancelled;
    }

    /**
     * Finish game, store stats hash
     */
    function finishGame(uint32 _gameId, bytes32 _hash) external onlyServer {
        Game storage game = games[_gameId];
        require(
            _gameId > 0
            && game.state < GameState.Finished
        && _hash != 0x0
        );
        game.statsHash = _hash;
        game.state = GameState.Finished;
    }

    /**
     * Reward winners
     */
    function winners(uint32 _gameId, uint32[] _teamIds, uint64[] _teamPrizes) external onlyServer {
        Game storage game = games[_gameId];
        require(game.state == GameState.Finished);

        uint64 sumPrize = 0;
        for (uint32 i = 0; i < _teamPrizes.length; i++)
            sumPrize += _teamPrizes[i];

        require(uint(sumPrize + game.awardSent) <= uint(game.entryFee * game.teamsNumber));

        for (i = 0; i < _teamIds.length; i++) {
            uint32 teamId = _teamIds[i];
            GameTeam storage team = teams[_gameId][teamId];
            uint32 userId = team.userId;

            if (team.prizeSum == 0) {
                if (team.sponsorId > 0) {
                    uint64 userFee = team.userFee * _teamPrizes[i] / 100;
                    balanceManager.addUserBalance(team.sponsorId, userFee);
                    balanceManager.addUserBalance(userId, _teamPrizes[i] - userFee);
                    team.prizeSum = _teamPrizes[i];
                } else {
                    balanceManager.addUserBalance(userId, _teamPrizes[i]);
                    team.prizeSum = _teamPrizes[i];
                }
            }
        }
    }

    /**
     * Refund money for cancelled game
     */
    function refundCancelledGame(uint32 _gameId, uint32[] _teamIds) external onlyServer {
        Game storage game = games[_gameId];
        require(game.state == GameState.Cancelled);

        for (uint32 i = 0; i < _teamIds.length; i++) {
            uint32 teamId = _teamIds[i];
            GameTeam storage team = teams[_gameId][teamId];

            require(teams[_gameId][teamId].prizeSum == 0);

            if (team.prizeSum == 0) {
                if (team.sponsorId > 0) {
                    balanceManager.addUserBalance(team.sponsorId, game.entryFee);
                } else {
                    balanceManager.addUserBalance(team.userId, game.entryFee);
                }
                team.prizeSum = game.entryFee;
            }
        }
    }
}
function participateGame(
    uint32 _gameId,
    uint32 _teamId,
    uint32 _userId,
    uint32 _sponsorId
)
external
onlyServer
{
    Game storage game = games[_gameId];
    require(
        _gameId > 0
        && game.state == GameState.Initialized
        && _teamId > 0
        && _userId > 0
        && teams[_gameId][_teamId].userId == 0
        && game.registrationDueDate > uint32(now)
    );

    uint16 userFee = 0;
    if (_sponsorId > 0) {
        require(balanceManager.balances(_sponsorId) >= game.entryFee && investTerms[_sponsorId][_userId].amount > game.entryFee);
        balanceManager.spendUserBalance(_sponsorId, game.entryFee);
        investTerms[_sponsorId][_userId].amount -= game.entryFee;
        userFee = investTerms[_sponsorId][_userId].userFee;
    }
    else {
        require(balanceManager.balances(_userId) >= game.entryFee);
        balanceManager.spendUserBalance(_userId, game.entryFee);
    }

    teams[_gameId][_teamId] = GameTeam(_userId, _sponsorId, 0, userFee);
    game.teamsNumber++;
}

/**
 * Participate game
 */