contract ConflictResolution is ConflictResolutionInterface, Games, Ownable {
    using SafeCast for int;
    using SafeCast for uint;
    using SafeMath for int;
    using SafeMath for uint;

    uint public SERVER_TIMEOUT = 6 hours;
    uint public USER_TIMEOUT = 6 hours;

    uint public MIN_BET_VALUE = 1e13; /// min 0.00001 ether bet
    uint public MIN_BANKROLL = 40e18; // 40e18

    int public NOT_ENDED_FINE = 1e15; /// 0.001 ether

    int public CONFLICT_END_FINE = 1e15; /// 0.001 ether

    modifier onlyValidBet(uint8 _gameType, uint _betNum, uint _betValue) {
        require(isValidBet(_gameType, _betNum, _betValue), "inv bet");
        _;
    }

    modifier onlyValidBalance(int _balance, uint _gameStake) {
        require(-_gameStake.castToInt() <= _balance && _balance <= int(MIN_BANKROLL / 2), "inv balance");
        _;
    }

    /**
     * @dev constructor
     * @param games the games specific contracts.
     */
    constructor(address[] memory games) Games(games) public {
        // Nothing to do
    }

    /**
     * @return Conflict end fine.
     */
    function conflictEndFine() public view returns(int) {
        return CONFLICT_END_FINE;
    }

    /**
     * @return Max balance.
     */
    function maxBalance() public view returns(int) {
        return int(MIN_BANKROLL / 2);
    }

    /**
     * Calculate minimum needed house stake.
     */
    function minHouseStake(uint activeGames) public view returns(uint) {
        return  MathUtil.min(activeGames, 1) * (MIN_BANKROLL / 2);
    }

    /**
     * @dev Check if bet is valid.
     * @param _gameType Game type.
     * @param _betNum Number of bet.
     * @param _betValue Value of bet.
     * @return True if bet is valid false otherwise.
     */
    function isValidBet(uint8 _gameType, uint _betNum, uint _betValue) public view returns(bool) {
        bool validMinBetValue = MIN_BET_VALUE <= _betValue;
        bool validMaxBetValue = _betValue <= Games.maxBet(_gameType, _betNum, MIN_BANKROLL);
        return validMinBetValue && validMaxBetValue;
    }


    /**
     * @dev Calculates game result and returns new balance.
     * @param _gameType Type of game.
     * @param _betNum Bet number.
     * @param _betValue Value of bet.
     * @param _balance Current balance.
     * @param _serverSeed Server's seed of current round.
     * @param _userSeed User's seed of current round.
     * @return New game session balance.
     */
    function endGameConflict(
        uint8 _gameType,
        uint _betNum,
        uint _betValue,
        int _balance,
        uint _stake,
        bytes32 _serverSeed,
        bytes32 _userSeed
    )
        public
        view
        onlyValidBet(_gameType, _betNum, _betValue)
        onlyValidBalance(_balance, _stake)
        returns(int)
    {
        require(_serverSeed != 0 && _userSeed != 0, "inv seeds");

        int newBalance =  processBet(_gameType, _betNum, _betValue, _balance, _serverSeed, _userSeed);

        // user need to pay a fee when conflict ended.
        // this ensures a malicious, rich user can not just generate game sessions and then wait
        // for us to end the game session and then confirm the session status, so
        // we would have to pay a high gas fee without profit.
        newBalance = newBalance.sub(CONFLICT_END_FINE);

        // do not allow balance below user stake
        int stake = _stake.castToInt();
        if (newBalance < -stake) {
            newBalance = -stake;
        }

        return newBalance;
    }

    /**
     * @dev Force end of game if user does not respond. Only possible after a time period.
     * to give the user a chance to respond.
     * @param _gameType Game type.
     * @param _betNum Bet number.
     * @param _betValue Bet value.
     * @param _balance Current balance.
     * @param _stake User stake.
     * @param _endInitiatedTime Time server initiated end.
     * @return New game session balance.
     */
    function serverForceGameEnd(
        uint8 _gameType,
        uint _betNum,
        uint _betValue,
        int _balance,
        uint _stake,
        bytes32 _serverSeed,
        bytes32 _userSeed,
        uint _endInitiatedTime
    )
        public
        view
        onlyValidBalance(_balance, _stake)
        returns(int)
    {
        require(_endInitiatedTime + SERVER_TIMEOUT <= block.timestamp, "too low timeout");
        require((_gameType == 0 && _betNum == 0 && _betValue == 0 && _balance == 0)
                || isValidBet(_gameType, _betNum, _betValue), "inv bet");


        // if no bet was placed (cancelActiveGame) set new balance to 0
        int newBalance = 0;

        // a bet was placed calculate new balance
        if (_gameType != 0) {
            newBalance = processBet(_gameType, _betNum, _betValue, _balance, _serverSeed, _userSeed);
        }

        // penalize user as he didn't end game
        newBalance = newBalance.sub(NOT_ENDED_FINE);

        // do not allow balance below user stake
        int stake = _stake.castToInt();
        if (newBalance < -stake) {
            newBalance = -stake;
        }

        return newBalance;
    }

    /**
     * @dev Force end of game if server does not respond. Only possible after a time period
     * to give the server a chance to respond.
     * @param _gameType Game type.
     * @param _betNum Bet number.
     * @param _betValue Value of bet.
     * @param _balance Current balance.
     * @param _endInitiatedTime Time server initiated end.
     * @return New game session balance.
     */
    function userForceGameEnd(
        uint8 _gameType,
        uint _betNum,
        uint _betValue,
        int _balance,
        uint  _stake,
        uint _endInitiatedTime
    )
        public
        view
        onlyValidBalance(_balance, _stake)
        returns(int)
    {
        require(_endInitiatedTime + USER_TIMEOUT <= block.timestamp, "too low timeout");
        require((_gameType == 0 && _betNum == 0 && _betValue == 0 && _balance == 0)
                || isValidBet(_gameType, _betNum, _betValue), "inv bet");

        int profit = 0;
        if (_gameType == 0 && _betNum == 0 && _betValue == 0 && _balance == 0) {
            // user cancelled game without playing
            profit = 0;
        } else {
            profit = Games.maxUserProfit(_gameType, _betNum, _betValue);
        }

        // penalize server as it didn't end game
        profit = profit.add(NOT_ENDED_FINE);

        return _balance.add(profit);
    }

    /**
     * @dev Calculate new balance after executing bet.
     * @param _gameType game type.
     * @param _betNum Bet Number.
     * @param _betValue Value of bet.
     * @param _balance Current balance.
     * @param _serverSeed Server's seed
     * @param _userSeed User's seed
     * return new balance.
     */
    function processBet(
        uint8 _gameType,
        uint _betNum,
        uint _betValue,
        int _balance,
        bytes32 _serverSeed,
        bytes32 _userSeed
    )
        public
        view
        returns (int)
    {
        uint resNum = Games.resultNumber(_gameType, _serverSeed, _userSeed, _betNum);
        int profit = Games.userProfit(_gameType, _betNum, _betValue, resNum);
        return _balance.add(profit);
    }

    /**
     * @dev Set Min Bankroll
     * @param _minBankRoll new min bank roll.
     */
    function setMinBankRoll(uint _minBankRoll) public onlyOwner {
        MIN_BANKROLL = _minBankRoll;
    }

    /**
     * @dev Set Server Tiemout
     * @param _serverTimeOut new server time
     */
    function setServerTimeOut(uint _serverTimeOut) public onlyOwner {
        SERVER_TIMEOUT = _serverTimeOut;
    }

    /**
     * @dev Set User Timeout
     * @param _userTimeOut new user time.
     */
     function setUserTimeOut(uint _userTimeOut) public onlyOwner {
         USER_TIMEOUT = _userTimeOut;
     }
    
    /**
     * @dev Set Min Bet Value
     * @param _minBetValue new min bet value.
     */
     function setMinBetValue(uint _minBetValue) public onlyOwner {
         MIN_BET_VALUE = _minBetValue;
     }

    /**
     * @dev Set Not Ended Fine
     * @param _notEndedFine new not ended fine.
     */
     function setNotEndedFine(int _notEndedFine) public onlyOwner {
         NOT_ENDED_FINE = _notEndedFine;
     }

    /**
     * @dev Set Conflict End Fine
     * @param _conflictEndFine new conflict end fine.
     */
    function setConflictEndFine(int _conflictEndFine) public onlyOwner {
        CONFLICT_END_FINE = _conflictEndFine;
    }
}
/**
 * @title Conflict Resolution
 * @dev Contract used for conflict resolution. Only needed if server or
 * user stops responding during game session.
 * @author dicether
 */
function setUserTimeOut(uint _userTimeOut) public onlyOwner {
    USER_TIMEOUT = _userTimeOut;
}

/**
 * @dev Set User Timeout
 * @param _userTimeOut new user time.
 */