contract StartGame is IStartGame, ICommonGame, IPrizeLibrary, IMinMaxPrize, IGameLengthLibrary, IGameManager, IBlockRandomLibrary {
    bool internal previousCalcRegular;

    constructor(uint _repeatBlock, uint _addonBlock) public
    {
        assert(_repeatBlock <= 250);
        repeatBlock = _repeatBlock;
        addonBlock = _addonBlock;
        calcNextGameId();
        defaultId = nextGameId;
    }

    // The manual ones have odd ids
    function startOwnFixed(uint gameId, uint length, uint addon, uint prize) public admin payable {
        require(msg.value > 0);
        require(!gameExists(gameId));
        require(gameId % 2 == 1);

        // the game length can be of any value, but for our security it should not be too small
        require(length >= getMinGameLength());
        // addon should just be not too large
        // the prize should be in the desired ratio
        require(prize >= getMinPrize() && prize <= getWholePrize());

        updateRandom();
        startGameInternal(gameId, length, addon, prize);
        profitedBet(gameId);
    }

    function randomValueWithMinPrize() internal view returns(uint) {
        if (!startOnlyMinPrizes() && isRandomAvailable()) {
            return getRandomValue();
        }

        return 0;
    }

    function startGameDetermine(uint gameId) internal {
        uint random = randomValueWithMinPrize();
        startGameInternal(gameId, calcGameLength(random), calcGameAddon(random), calculatePrize(random, getMinPrize(), getMaxPrize()));
    }

    function betInGame(uint gameId) public payable {
        require(msg.value > 0, "Bet should be not zero");
        updateRandom();

        if (!gameExists(gameId)) {
            require(canStartGame(), "Game cannot be started");
            // well creation should be allowed
            // one can only launch the start game with the possible game id
            require(startGameId() == gameId, "No such scheduled game");
            // can only be started after the corresponding block has passed

            startGameDetermine(gameId);
            updateDefaultGame(gameId);
            calcNextGameId();
        }

        profitedBet(gameId);
    }

    function profitedBet(uint gameId) internal {
        bool profited = gameProfited(gameId);
        betInGameInternal(gameId, msg.value);

        // the profited status changed
        // the own games are not counted
        if (profited != gameProfited(gameId)) {
            if (startProfitedGamesAllowed() && (gameId % 2 == 0 || autoCreationAfterOwnAllowed())) {
                createFastGamesCount++;
                if (!isRandomAvailable() && previousCalcRegular && createFastGamesCount == 1) {
                    calcNextGameId();
                }
                emit FastGamesChanged(createFastGamesCount);
            }
            profitedCount++;
            emit GameProfitedEvent(gameId);
        }
    }
        
    uint public repeatBlock;
    uint public addonBlock;

    function getRepeatBlock() public view returns(uint) {
        return repeatBlock;
    }
    function getAddonBlock() public view returns(uint) {
        return addonBlock;
    }

    function alterRepeatBlock(uint _repeatBlock) admin public {
        assert(_repeatBlock < 250);
        repeatBlock = _repeatBlock;
        emit RepeatBlockAltered(repeatBlock);
    }

    function alterAddonBlock(uint _addonBlock) admin public {
        addonBlock = _addonBlock;
        emit RepeatAddonBlockAltered(addonBlock);
    }

    uint internal nextGameId;
    uint internal defaultId;
    uint internal profitedCount;
    uint internal createFastGamesCount;

    function getProfitedCount() public view returns(uint) {
        return profitedCount;
    }

    function getCreateFastGamesCount() public view returns(uint) {
        return createFastGamesCount;
    }

    function setCreateFastGamesCount(uint count) public admin {
        createFastGamesCount = count;
        emit FastGamesChanged(createFastGamesCount);
    }

    function recalcNextGameId() public admin {
        if (!isRandomAvailable()) {
            calcNextGameId();
        } else {
            revert("You cannot recalculate, unless the prize has expired");
        }
    }

    function calcNextGameId() internal {
        uint ngi;

        previousCalcRegular = createFastGamesCount == 0;

        if (createFastGamesCount > 0) {
            ngi = block.number + addonBlock;
            createFastGamesCount--;
        } else {
            // always positive. ok
            ngi = block.number + (repeatBlock - block.number % repeatBlock);
        }

        // The main idea is that the automatic tourneys have even ids.
        // The manual ones have odd ids
        if (ngi % 2 == 1) {
            ngi++;
        }

        nextGameId = ngi;
        setRandomBlock(nextGameId);
        updateDefaultGame(nextGameId);
        emit NextGameIdCalculated(nextGameId);
    }

    function canStartGame() public view returns(bool) {
        return randomBlockPassed() && creationAllowed();
    }

    function startGameId() public view returns(uint) {
        return nextGameId;
    }

    function startPrizeValue() public view returns(uint) {
        return calculatePrize(randomValueWithMinPrize(), getMinPrize(), getMaxPrize());
    }

    function startGameLength() public view returns(uint) {
        return calcGameLength(randomValueWithMinPrize());
    }

    function startGameAddon() public view returns(uint) {
        return calcGameAddon(randomValueWithMinPrize());
    }

    function getStartGameStatus() public view returns(bool, uint, uint, uint, uint) {
        uint random = randomValueWithMinPrize();
        return (
            canStartGame(), 
            nextGameId, 
            calculatePrize(random, getMinPrize(), getMaxPrize()),
            calcGameLength(random),
            calcGameAddon(random));
    }

    function updateDefaultGame(uint gameId) internal {
        if (finishedGame(defaultId) || !gameExists(defaultId)) {
            defaultId = gameId;
            emit DefaultGameUpdated(defaultId);
        } 
    }

    function defaultGameId() public view returns(uint) {
        if (!finishedGame(defaultId) && gameExists(defaultId)) return defaultId;
        if (canStartGame()) return startGameId();
        return 0;
    }

    function defaultGameAvailable() public view returns(bool) {
        return !finishedGame(defaultId) && gameExists(defaultId) || canStartGame();
    }

    mapping (address => uint) transferGames;

    function getTransferProfitedGame(address participant) public view returns(uint) {
        if (finishedGame(transferGames[participant]) && getWinner(transferGames[participant]) == participant) {
            return transferGames[participant];
        }

        return 0;
    }

    function getTransferProfitedGame() internal view returns(uint) {
        return getTransferProfitedGame(msg.sender);
    }

    function processTransfer() internal {
        uint tpg = getTransferProfitedGame();
        // there is no game to withdraw
        if (tpg == 0) {
            if (!finishedGame(defaultId) && gameExists(defaultId)) {
                betInGame(defaultId);
            } else {
                betInGame(startGameId());
            }
            transferGames[msg.sender] = defaultId;
        } else {
            transferGames[msg.sender] = 0;
            withdrawPrizeInternal(tpg, msg.value);
        } 

        emit TransferBet(msg.sender, msg.value);
    }

    function processTransferInteraction() internal {
        if (transferInteractionsAllowed()) {
            processTransfer();
        } else {
            revert();
        }
    }
}
function startOwnFixed(uint gameId, uint length, uint addon, uint prize) public admin payable {
    require(msg.value > 0);
    require(!gameExists(gameId));
    require(gameId % 2 == 1);

    // the game length can be of any value, but for our security it should not be too small
    require(length >= getMinGameLength());
    // addon should just be not too large
    // the prize should be in the desired ratio
    require(prize >= getMinPrize() && prize <= getWholePrize());

    updateRandom();
    startGameInternal(gameId, length, addon, prize);
    profitedBet(gameId);
}

// The manual ones have odd ids