// Vulnerability label: UcC1
pragma solidity 0.4.25;

/**
* ETH CRYPTOCURRENCY DISTRIBUTION PROJECT
* 
* Web              - https://333eth.io
* 
* Twitter          - https://twitter.com/333eth_io
* 
* Telegram_channel - https://t.me/Ethereum333
* 
* EN  Telegram_chat: https://t.me/Ethereum333_chat_en
* 
* RU  Telegram_chat: https://t.me/Ethereum333_chat_ru
* 
* KOR Telegram_chat: https://t.me/Ethereum333_chat_kor
* 
* Email:             mailto:support(at sign)333eth.io
* 
* 
* 
* When the timer reaches zero then latest bettor takes the bank. Each bet restart a timer again.
* 
* Bet in 1 ETH - the timer turns on for 3 minutes 33 seconds.
* 
* Bet 0.1ETH - the timer turns on for 6 minutes 33 seconds.
* 
* Bet 0.01 ETH - the timer turns on for 9 minutes 33 seconds.
* You need to send such bet`s amounts. If more was sent, then contract will return the difference to the wallet. For example, sending 0.99 ETH system will perceive as a contribution to 0.1 ETH and difference 0.89
* 
* The game does not have a fraudulent Ponzi scheme. No fraudulent referral programs.
* 
* In the contract of the game realized the refusal of ownership. It is impossible to stop the flow of bets. Bet from smart contracts is prohibited.
* 
* Eth distribution:
* 50% paid to the winner.
* 40% is transferred to the next level of the game with the same rules and so on.
* 10% commission (7.5% of them to shareholders, 2.5% of the administration).
* 
* RECOMMENDED GAS LIMIT: 100000
* 
* RECOMMENDED GAS PRICE: https://ethgasstation.info/
*/


library Zero {
  function requireNotZero(address addr) internal pure {
    require(addr != address(0), "require not zero address");
  }

  function requireNotZero(uint val) internal pure {
    require(val != 0, "require not zero value");
  }

  function notZero(address addr) internal pure returns(bool) {
    return !(addr == address(0));
  }

  function isZero(address addr) internal pure returns(bool) {
    return addr == address(0);
  }

  function isZero(uint a) internal pure returns(bool) {
    return a == 0;
  }

  function notZero(uint a) internal pure returns(bool) {
    return a != 0;
  }
}


library Percent {
  // Solidity automatically throws when dividing by 0
  struct percent {
    uint num;
    uint den;
  }
  
  // storage
  function mul(percent storage p, uint a) internal view returns (uint) {
    if (a == 0) {
      return 0;
    }
    return a*p.num/p.den;
  }

  function div(percent storage p, uint a) internal view returns (uint) {
    return a/p.num*p.den;
  }

  function sub(percent storage p, uint a) internal view returns (uint) {
    uint b = mul(p, a);
    if (b >= a) {
      return 0;
    }
    return a - b;
  }

  function add(percent storage p, uint a) internal view returns (uint) {
    return a + mul(p, a);
  }

  function toMemory(percent storage p) internal view returns (Percent.percent memory) {
    return Percent.percent(p.num, p.den);
  }

  // memory 
  function mmul(percent memory p, uint a) internal pure returns (uint) {
    if (a == 0) {
      return 0;
    }
    return a*p.num/p.den;
  }

  function mdiv(percent memory p, uint a) internal pure returns (uint) {
    return a/p.num*p.den;
  }

  function msub(percent memory p, uint a) internal pure returns (uint) {
    uint b = mmul(p, a);
    if (b >= a) {
      return 0;
    }
    return a - b;
  }

  function madd(percent memory p, uint a) internal pure returns (uint) {
    return a + mmul(p, a);
  }
}

library Address {
  function toAddress(bytes source) internal pure returns(address addr) {
    // solium-disable security/no-inline-assembly
    assembly { addr := mload(add(source,0x14)) }
    return addr;
  }

  function isNotContract(address addr) internal view returns(bool) {
    // solium-disable security/no-inline-assembly
    uint length;
    assembly { length := extcodesize(addr) }
    return length == 0;
  }
}


contract Accessibility {
  address private owner;
  modifier onlyOwner() {
    require(msg.sender == owner, "access denied");
    _;
  }

  constructor() public {
    owner = msg.sender;
  }

  function disown() internal {
    delete owner;
  }
}

/**
 * @title SafeMath
 * @dev Math operations with safety checks that revert on error
 */
library SafeMath {

  /**
  * @dev Multiplies two numbers, reverts on overflow.
  */
  function mul(uint256 _a, uint256 _b) internal pure returns (uint256) {
    // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
    // benefit is lost if 'b' is also tested.
    // See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522
    if (_a == 0) {
      return 0;
    }

    uint256 c = _a * _b;
    require(c / _a == _b);

    return c;
  }

  /**
  * @dev Integer division of two numbers truncating the quotient, reverts on division by zero.
  */
  function div(uint256 _a, uint256 _b) internal pure returns (uint256) {
    require(_b > 0); // Solidity only automatically asserts when dividing by 0
    uint256 c = _a / _b;
    // assert(_a == _b * c + _a % _b); // There is no case in which this doesn't hold

    return c;
  }

  /**
  * @dev Subtracts two numbers, reverts on overflow (i.e. if subtrahend is greater than minuend).
  */
  function sub(uint256 _a, uint256 _b) internal pure returns (uint256) {
    require(_b <= _a);
    uint256 c = _a - _b;

    return c;
  }

  /**
  * @dev Adds two numbers, reverts on overflow.
  */
  function add(uint256 _a, uint256 _b) internal pure returns (uint256) {
    uint256 c = _a + _b;
    require(c >= _a);

    return c;
  }

  /**
  * @dev Divides two numbers and returns the remainder (unsigned integer modulo),
  * reverts when dividing by zero.
  */
  function mod(uint256 a, uint256 b) internal pure returns (uint256) {
    require(b != 0);
    return a % b;
  }
}


library Timer {
  using SafeMath for uint;
  struct timer {
    uint duration;
    uint startup;
  }
  function start(timer storage t, uint duration) internal {
    t.startup = now;
    t.duration = duration;
  }

  function timeLeft(timer storage t) internal view returns (uint) {
    if (now >= t.startup.add(t.duration)) {
      return 0;
    }
    return (t.startup+t.duration).sub(now);
  }
}


library Bet {
  struct bet {
    address bettor;
    uint amount;
    uint excess;
    uint duration;
  }

  function New(address bettor, uint value) internal pure returns(bet memory b ) {
    
    (uint[3] memory vals, uint[3] memory durs) = bets();
    if (value >= vals[0]) {
      b.amount = vals[0];
      b.duration = durs[0];
    } else if (vals[1] <= value && value < vals[0]) {
      b.amount = vals[1];
      b.duration = durs[1];
    } else if (vals[2] <= value && value < vals[1]) {
      b.amount = vals[2];
      b.duration = durs[2];
    } else {
      return b;
    }

    b.bettor = bettor;
    b.excess = value - b.amount;
  }

  function bets() internal pure returns(uint[3] memory vals, uint[3] memory durs) {
    (vals[0], vals[1], vals[2]) = (1 ether, 0.1 ether, 0.01 ether); 
    (durs[0], durs[1], durs[2]) = (3 minutes + 33 seconds, 6 minutes + 33 seconds, 9 minutes + 33 seconds);
  }

  function transferExcess(bet memory b) internal {
    b.bettor.transfer(b.excess);
  }
}



contract LastHero is Accessibility {
  using Percent for Percent.percent;
  using Timer for Timer.timer;
  using Address for address;
  using Bet for Bet.bet;
  using Zero for *;
  
  Percent.percent private m_bankPercent = Percent.percent(50,100);
  Percent.percent private m_nextLevelPercent = Percent.percent(40,100);
  Percent.percent private m_adminsPercent = Percent.percent(10,100);
  
  uint public nextLevelBankAmount;
  uint public bankAmount;
  uint public level;
  address public bettor;
  address public adminsAddress;
  Timer.timer private m_timer;

  modifier notFromContract() {
    require(msg.sender.isNotContract(), "only externally accounts");
    _;
  }

  event LogSendExcessOfEther(address indexed addr, uint excess, uint when);
  event LogNewWinner(address indexed addr, uint indexed level, uint amount, uint when);
  event LogNewLevel(uint indexed level, uint bankAmount, uint when);
  event LogNewBet(address indexed addr, uint indexed amount, uint duration, uint indexed level, uint when);
  event LogDisown(uint when);


  constructor() public {
    level = 1;
    emit LogNewLevel(level, address(this).balance, now);
    adminsAddress = msg.sender;
    m_timer.duration = uint(-1);
  }

  function() public payable {
    doBet();
  }

  function doDisown() public onlyOwner {
    disown();
    emit LogDisown(now);
  }

  function setAdminsAddress(address addr) public onlyOwner {
    addr.requireNotZero();
    adminsAddress = addr;
  }

  function bankPercent() public view returns(uint numerator, uint denominator) {
    (numerator, denominator) = (m_bankPercent.num, m_bankPercent.den);
  }

  function nextLevelPercent() public view returns(uint numerator, uint denominator) {
    (numerator, denominator) = (m_nextLevelPercent.num, m_nextLevelPercent.den);
  }

  function adminsPercent() public view returns(uint numerator, uint denominator) {
    (numerator, denominator) = (m_adminsPercent.num, m_adminsPercent.den);
  }

  function timeLeft() public view returns(uint duration) {
    duration = m_timer.timeLeft();
  }

  function timerInfo() public view returns(uint startup, uint duration) {
    (startup, duration) = (m_timer.startup, m_timer.duration);
  }

  function durationForBetAmount(uint betAmount) public view returns(uint duration) {
    Bet.bet memory bet = Bet.New(msg.sender, betAmount);
    duration = bet.duration;
  }

  function availableBets() public view returns(uint[3] memory vals, uint[3] memory durs) {
    (vals, durs) = Bet.bets();
  }

  function doBet() public payable notFromContract {

    // send ether to bettor if needed
    if (m_timer.timeLeft().isZero()) {
      bettor.transfer(bankAmount);
      emit LogNewWinner(bettor, level, bankAmount, now);

      bankAmount = nextLevelBankAmount;
      nextLevelBankAmount = 0;
      level++;
      emit LogNewLevel(level, bankAmount, now);
    }

    Bet.bet memory bet = Bet.New(msg.sender, msg.value);
    bet.amount.requireNotZero();

    // send bet`s excess of ether if needed
    if (bet.excess.notZero()) {
      bet.transferExcess();
      emit LogSendExcessOfEther(bet.bettor, bet.excess, now);
    }

    // commision
    nextLevelBankAmount += m_nextLevelPercent.mul(bet.amount);
    bankAmount += m_bankPercent.mul(bet.amount);
    adminsAddress.send(m_adminsPercent.mul(bet.amount));
  
    m_timer.start(bet.duration);
    bettor = bet.bettor;

    emit LogNewBet(bet.bettor, bet.amount, bet.duration, level, now);
  }
}