contract Bank is Ownable {

    using SafeMath for uint256;

    mapping (uint256 => mapping (address => uint256)) public depositDragons;
    mapping (uint256 => mapping (address => uint256)) public depositHamsters;

    address payable public team = 0x7De1eFb9E5035784FB931433c8a884588929338E;

    uint256 public currentDeadline;
    uint256 public currentRound = 1;
    uint256 public lastDeadline;
    uint256 public defaultCurrentDeadlineInHours = 24;
    uint256 public defaultLastDeadlineInHours = 48;
    uint256 public countOfDragons;
    uint256 public countOfHamsters;
    uint256 public totalSupplyOfHamsters;
    uint256 public totalSupplyOfDragons;
    uint256 public totalDeexSupplyOfHamsters;
    uint256 public totalDeexSupplyOfDragons;
    uint256 public probabilityOfHamsters;
    uint256 public probabilityOfDragons;
    address public lastHero;
    address public lastHeroHistory;
    uint256 public jackPot;
    uint256 public winner;
    uint256 public withdrawn;
    uint256 public withdrawnDeex;
    uint256 public remainder;
    uint256 public remainderDeex;
    uint256 public rate = 1;
    uint256 public rateModifier = 0;
    uint256 public tokenReturn;

    uint256 public lastTotalSupplyOfHamsters;
    uint256 public lastTotalSupplyOfDragons;
    uint256 public lastTotalDeexSupplyOfHamsters;
    uint256 public lastTotalDeexSupplyOfDragons;
    uint256 public lastProbabilityOfHamsters;
    uint256 public lastProbabilityOfDragons;
    address public lastRoundHero;
    uint256 public lastJackPot;
    uint256 public lastWinner;
    uint256 public lastBalance;
    uint256 public lastBalanceDeex;
    uint256 public lastCountOfDragons;
    uint256 public lastCountOfHamsters;
    uint256 public lastWithdrawn;
    uint256 public lastWithdrawnDeex;


    bool public finished = false;

    Dragons public DragonsContract;
    Hamsters public HamstersContract;
    DEEX public DEEXContract;

    /**
    * @dev Setter token rate.
    * @param _rate this value for change percent relation rate to count of tokens.
    * @param _rateModifier this value for change math operation under tokens.
    */
    function setRateToken(uint256 _rate, uint256 _rateModifier) public onlyOwner returns(uint256){
        rate = _rate;
        rateModifier = _rateModifier;
    }

    /**
    * @dev Setter round time.
    * @param _currentDeadlineInHours this value current deadline in hours.
    * @param _lastDeadlineInHours this value last deadline in hours.
    */
    function _setRoundTime(uint _currentDeadlineInHours, uint _lastDeadlineInHours) internal {
        defaultCurrentDeadlineInHours = _currentDeadlineInHours;
        defaultLastDeadlineInHours = _lastDeadlineInHours;
        currentDeadline = block.timestamp + 60 * 60 * _currentDeadlineInHours;
        lastDeadline = block.timestamp + 60 * 60 * _lastDeadlineInHours;
    }

    /**
    * @dev Setter round time.
    * @param _currentDeadlineInHours this value current deadline in hours.
    * @param _lastDeadlineInHours this value last deadline in hours.
    */
    function setRoundTime(uint _currentDeadlineInHours, uint _lastDeadlineInHours) public onlyOwner {
        _setRoundTime(_currentDeadlineInHours, _lastDeadlineInHours);
    }


    /**
    * @dev Setter the DEEX contract address. Address can be set at once.
    * @param _DEEXAddress Address of the DEEX contract
    */
    function setDEEXAddress(address payable _DEEXAddress) public {
        require(address(DEEXContract) == address(0x0));
        DEEXContract = DEEX(_DEEXAddress);
    }

    /**
    * @dev Setter the Dragons contract address. Address can be set at once.
    * @param _DragonsAddress Address of the Dragons contract
    */
    function setDragonsAddress(address payable _DragonsAddress) external {
        require(address(DragonsContract) == address(0x0));
        DragonsContract = Dragons(_DragonsAddress);
    }

    /**
    * @dev Setter the Hamsters contract address. Address can be set at once.
    * @param _HamstersAddress Address of the Hamsters contract
    */
    function setHamstersAddress(address payable _HamstersAddress) external {
        require(address(HamstersContract) == address(0x0));
        HamstersContract = Hamsters(_HamstersAddress);
    }

    /**
    * @dev Getting time from blockchain for timer
    */
    function getNow() view public returns(uint){
        return block.timestamp;
    }

    /**
    * @dev Getting state of game. True - game continue, False - game stopped
    */
    function getState() view public returns(bool) {
        if (block.timestamp > currentDeadline) {
            return false;
        }
        return true;
    }

    /**
    * @dev Setting info about participant from Dragons or Hamsters contract
    * @param _lastHero Address of participant
    * @param _deposit Amount of deposit
    */
    function setInfo(address _lastHero, uint256 _deposit) public {
        require(address(DragonsContract) == msg.sender || address(HamstersContract) == msg.sender);

        if (address(DragonsContract) == msg.sender) {
            require(depositHamsters[currentRound][_lastHero] == 0, "You are already in Hamsters team");
            if (depositDragons[currentRound][_lastHero] == 0)
                countOfDragons++;
            totalSupplyOfDragons = totalSupplyOfDragons.add(_deposit.mul(90).div(100));
            depositDragons[currentRound][_lastHero] = depositDragons[currentRound][_lastHero].add(_deposit.mul(90).div(100));
        }

        if (address(HamstersContract) == msg.sender) {
            require(depositDragons[currentRound][_lastHero] == 0, "You are already in Dragons team");
            if (depositHamsters[currentRound][_lastHero] == 0)
                countOfHamsters++;
            totalSupplyOfHamsters = totalSupplyOfHamsters.add(_deposit.mul(90).div(100));
            depositHamsters[currentRound][_lastHero] = depositHamsters[currentRound][_lastHero].add(_deposit.mul(90).div(100));
        }

        lastHero = _lastHero;

        if (currentDeadline.add(120) <= lastDeadline) {
            currentDeadline = currentDeadline.add(120);
        } else {
            currentDeadline = lastDeadline;
        }

        jackPot += _deposit.mul(10).div(100);

        calculateProbability();
    }

    function estimateTokenPercent(uint256 _difference) public view returns(uint256){
        if (rateModifier == 0) {
            return _difference.mul(rate);
        } else {
            return _difference.div(rate);
        }
    }

    /**
    * @dev Calculation probability for team's win
    */
    function calculateProbability() public {
        require(winner == 0 && getState());

        totalDeexSupplyOfHamsters = DEEXContract.balanceOf(address(HamstersContract));
        totalDeexSupplyOfDragons = DEEXContract.balanceOf(address(DragonsContract));
        uint256 percent = (totalSupplyOfHamsters.add(totalSupplyOfDragons)).div(100);

        if (totalDeexSupplyOfHamsters < 1) {
            totalDeexSupplyOfHamsters = 0;
        }

        if (totalDeexSupplyOfDragons < 1) {
            totalDeexSupplyOfDragons = 0;
        }

        if (totalDeexSupplyOfHamsters <= totalDeexSupplyOfDragons) {
            uint256 difference = totalDeexSupplyOfDragons.sub(totalDeexSupplyOfHamsters).mul(100);
            probabilityOfDragons = totalSupplyOfDragons.mul(100).div(percent).add(estimateTokenPercent(difference));
  
            if (probabilityOfDragons > 8000) {
                probabilityOfDragons = 8000;
            }
            if (probabilityOfDragons < 2000) {
                probabilityOfDragons = 2000;
            }
            probabilityOfHamsters = 10000 - probabilityOfDragons;
        } else {
            uint256 difference = totalDeexSupplyOfHamsters.sub(totalDeexSupplyOfDragons).mul(100);
            probabilityOfHamsters = totalSupplyOfHamsters.mul(100).div(percent).add(estimateTokenPercent(difference));

            if (probabilityOfHamsters > 8000) {
                probabilityOfHamsters = 8000;
            }
            if (probabilityOfHamsters < 2000) {
                probabilityOfHamsters = 2000;
            }
            probabilityOfDragons = 10000 - probabilityOfHamsters;
        }

        totalDeexSupplyOfHamsters = DEEXContract.balanceOf(address(HamstersContract));
        totalDeexSupplyOfDragons = DEEXContract.balanceOf(address(DragonsContract));
    }

    /**
    * @dev Getting winner team
    */
    function getWinners() public {
        require(winner == 0 && !getState());
        uint256 seed1 = address(this).balance;
        uint256 seed2 = totalSupplyOfHamsters;
        uint256 seed3 = totalSupplyOfDragons;
        uint256 seed4 = totalDeexSupplyOfHamsters;
        uint256 seed5 = totalDeexSupplyOfHamsters;
        uint256 seed6 = block.difficulty;
        uint256 seed7 = block.timestamp;

        bytes32 randomHash = keccak256(abi.encodePacked(seed1, seed2, seed3, seed4, seed5, seed6, seed7));
        uint randomNumber = uint(randomHash);

        if (randomNumber == 0){
            randomNumber = 1;
        }

        uint winningNumber = randomNumber % 10000;

        if (1 <= winningNumber && winningNumber <= probabilityOfDragons){
            winner = 1;
        }

        if (probabilityOfDragons < winningNumber && winningNumber <= 10000){
            winner = 2;
        }

        if (DEEXContract.balanceOf(address(HamstersContract)) > 0)
            DEEXContract.transferFrom(
                address(HamstersContract),
                address(this),
                DEEXContract.balanceOf(address(HamstersContract))
            );

        if (DEEXContract.balanceOf(address(DragonsContract)) > 0)
            DEEXContract.transferFrom(
                address(DragonsContract),
                address(this),
                DEEXContract.balanceOf(address(DragonsContract))
            );

        lastTotalSupplyOfHamsters = totalSupplyOfHamsters;
        lastTotalSupplyOfDragons = totalSupplyOfDragons;
        lastTotalDeexSupplyOfDragons = totalDeexSupplyOfDragons;
        lastTotalDeexSupplyOfHamsters = totalDeexSupplyOfHamsters;
        lastRoundHero = lastHero;
        lastJackPot = jackPot;
        lastWinner = winner;
        lastCountOfDragons = countOfDragons;
        lastCountOfHamsters = countOfHamsters;
        lastWithdrawn = withdrawn;
        lastWithdrawnDeex = withdrawnDeex;

        if (lastBalance > lastWithdrawn){
            remainder = lastBalance.sub(lastWithdrawn);
           team.transfer(remainder);
        }

        lastBalance = lastTotalSupplyOfDragons.add(lastTotalSupplyOfHamsters).add(lastJackPot);

        if (lastBalanceDeex > lastWithdrawnDeex){
            remainderDeex = lastBalanceDeex.sub(lastWithdrawnDeex);
            tokenReturn = (totalDeexSupplyOfDragons.add(totalDeexSupplyOfHamsters)).mul(20).div(100).add(remainderDeex);
            DEEXContract.transfer(team, tokenReturn);
        }

        lastBalanceDeex = DEEXContract.balanceOf(address(this));

        totalSupplyOfHamsters = 0;
        totalSupplyOfDragons = 0;
        totalDeexSupplyOfHamsters = 0;
        totalDeexSupplyOfDragons = 0;
        remainder = 0;
        remainderDeex = 0;
        jackPot = 0;

        withdrawn = 0;
        winner = 0;
        withdrawnDeex = 0;
        countOfDragons = 0;
        countOfHamsters = 0;
        probabilityOfHamsters = 0;
        probabilityOfDragons = 0;

        _setRoundTime(defaultCurrentDeadlineInHours, defaultLastDeadlineInHours);
        currentRound++;
    }

    /**
    * @dev Payable function for take prize
    */
    function () external payable {
        if (msg.value == 0){
            require(depositDragons[currentRound - 1][msg.sender] > 0 || depositHamsters[currentRound - 1][msg.sender] > 0);

            uint payout = 0;
            uint payoutDeex = 0;

            if (lastWinner == 1 && depositDragons[currentRound - 1][msg.sender] > 0) {
                payout = calculateLastETHPrize(msg.sender);
            }
            if (lastWinner == 2 && depositHamsters[currentRound - 1][msg.sender] > 0) {
                payout = calculateLastETHPrize(msg.sender);
            }

            if (payout > 0) {
                depositDragons[currentRound - 1][msg.sender] = 0;
                depositHamsters[currentRound - 1][msg.sender] = 0;
                withdrawn = withdrawn.add(payout);
                msg.sender.transfer(payout);
            }

            if ((lastWinner == 1 && depositDragons[currentRound - 1][msg.sender] == 0) || (lastWinner == 2 && depositHamsters[currentRound - 1][msg.sender] == 0)) {
                payoutDeex = calculateLastDeexPrize(msg.sender);
                withdrawnDeex = withdrawnDeex.add(payoutDeex);
                DEEXContract.transfer(msg.sender, payoutDeex);
            }

            if (msg.sender == lastRoundHero) {
                lastHeroHistory = lastRoundHero;
                lastRoundHero = address(0x0);
                withdrawn = withdrawn.add(lastJackPot);
                msg.sender.transfer(lastJackPot);
            }
        }
    }

    /**
    * @dev Getting ETH prize of participant
    * @param participant Address of participant
    */
    function calculateETHPrize(address participant) public view returns(uint) {

        uint payout = 0;
        uint256 totalSupply = (totalSupplyOfDragons.add(totalSupplyOfHamsters));

        if (depositDragons[currentRound][participant] > 0) {
            payout = totalSupply.mul(depositDragons[currentRound][participant]).div(totalSupplyOfDragons);
        }

        if (depositHamsters[currentRound][participant] > 0) {
            payout = totalSupply.mul(depositHamsters[currentRound][participant]).div(totalSupplyOfHamsters);
        }

        return payout;
    }

    /**
    * @dev Getting Deex Token prize of participant
    * @param participant Address of participant
    */
    function calculateDeexPrize(address participant) public view returns(uint) {

        uint payout = 0;
        uint totalSupply = (totalDeexSupplyOfDragons.add(totalDeexSupplyOfHamsters)).mul(80).div(100);

        if (depositDragons[currentRound][participant] > 0) {
            payout = totalSupply.mul(depositDragons[currentRound][participant]).div(totalSupplyOfDragons);
        }

        if (depositHamsters[currentRound][participant] > 0) {
            payout = totalSupply.mul(depositHamsters[currentRound][participant]).div(totalSupplyOfHamsters);
        }

        return payout;
    }

    /**
    * @dev Getting ETH prize of _lastParticipant
    * @param _lastParticipant Address of _lastParticipant
    */
    function calculateLastETHPrize(address _lastParticipant) public view returns(uint) {

        uint payout = 0;
        uint256 totalSupply = (lastTotalSupplyOfDragons.add(lastTotalSupplyOfHamsters));

        if (depositDragons[currentRound - 1][_lastParticipant] > 0) {
            payout = totalSupply.mul(depositDragons[currentRound - 1][_lastParticipant]).div(lastTotalSupplyOfDragons);
        }

        if (depositHamsters[currentRound - 1][_lastParticipant] > 0) {
            payout = totalSupply.mul(depositHamsters[currentRound - 1][_lastParticipant]).div(lastTotalSupplyOfHamsters);
        }

        return payout;
    }

    /**
    * @dev Getting Deex Token prize of _lastParticipant
    * @param _lastParticipant Address of _lastParticipant
    */
    function calculateLastDeexPrize(address _lastParticipant) public view returns(uint) {

        uint payout = 0;
        uint totalSupply = (lastTotalDeexSupplyOfDragons.add(lastTotalDeexSupplyOfHamsters)).mul(80).div(100);

        if (depositDragons[currentRound - 1][_lastParticipant] > 0) {
            payout = totalSupply.mul(depositDragons[currentRound - 1][_lastParticipant]).div(lastTotalSupplyOfDragons);
        }

        if (depositHamsters[currentRound - 1][_lastParticipant] > 0) {
            payout = totalSupply.mul(depositHamsters[currentRound - 1][_lastParticipant]).div(lastTotalSupplyOfHamsters);
        }

        return payout;
    }
}
/*
 * @title Bank
 * @dev Bank contract which contained all ETH from Dragons and Hamsters teams.
 * When time in blockchain will be grater then current deadline or last deadline need call getWinner function
 * then participants able get prizes.
 *
 * Last participant(last hero) win 10% from all bank
 *
 * - To get prize send 0 ETH to this contract
 */
function setDragonsAddress(address payable _DragonsAddress) external {
    require(address(DragonsContract) == address(0x0));
    DragonsContract = Dragons(_DragonsAddress);
}

/**
 * @dev Setter the Dragons contract address. Address can be set at once.
 * @param _DragonsAddress Address of the Dragons contract
 */