contract ExRun {
  address owner;
  address foundationAddr;
  address lotteryAddr;
  address runAddr;
  address[] tempAddr;
  /** SaleIndex */
  uint saleIndex = 0;
  bool active = false;
  TokenRun token;
  JustRun run;
  /** SaleQueue */
  Data.SaleQueue[] saleQueue;
  mapping(address => Data.Player) playerMap;
  mapping(address => Data.PersonalSaleInfo) personalMap;

  constructor(
    address _ownerAddr,
    address _foundationAddr,
    address _tokenAddr
  ) public {
    owner = _ownerAddr;
    foundationAddr = _foundationAddr;
    token = TokenRun(_tokenAddr);
  }

  /** Send ETH to buy Token from ExRun contract
   *   Send 0eth to sell all token to ExRun Contract
   *   The first time you send 0eth to contract,you will register if not
   *   and you get the AirDrop Run Token,and the second time you will sell all your saleable Run Token
   */
  function() public payable {
    uint _eth = msg.value;
    bool flag = isContract(msg.sender);
    /** only accept ETH from normal address */
    if (!flag) {
      if (_eth > 0) {
        /** Send ETH to buy Token from ExRun contract */
        buyCore(msg.sender, address(0x0), _eth);
      } else {
        uint tokenValue = token.calcTokenReceived(3 * 10 ** 15);
        /** The first time send 0eth */
        if (playerMap[msg.sender].isExist == false) {
          register(msg.sender, address(0x0));
          token.getToken(tokenValue);
          token.transfer(msg.sender, tokenValue);
          playerMap[msg.sender].isNew = false;
        } else if (playerMap[msg.sender].isNew == true) {
          token.getToken(tokenValue);
          token.transfer(msg.sender, tokenValue);
          playerMap[msg.sender].isNew = false;
        } else {
          /** The second time send 0eth */
          sellCore(msg.sender, 0);
        }
      }
    }

  }

  /** Active Contract with runAddr,lotteryAddr */
  function activation(address _runAddr, address _lotteryAddr) external {
    require(active == false, "activated");
    require(owner == msg.sender, "Insufficient permissions");
    runAddr = _runAddr;
    lotteryAddr = _lotteryAddr;
    run = JustRun(_runAddr);
    active = true;
  }

  /** Verify that the address is a contract address */
  function isContract(address _addr) private view returns(bool) {
    uint size;
    assembly {
      size: = extcodesize(_addr)
    }
    return size > 0;
  }

  function register(address addr, address superiorAddr) private {
    if (playerMap[addr].isExist == true) {
      return;
    }
    Data.Player memory player;
    player = Data.Player({
      signInTime: 0,
      signInDay: 0,
      consume: 0,
      dynamicIncome: 0,
      totalEth: 0,
      sellIncome: 0,
      isNew: true,
      isExist: true,
      superiorAddr: superiorAddr,
      subordinateAddr: tempAddr
    });
    if (superiorAddr == address(0x0) || playerMap[superiorAddr].isExist == false) {
      player.superiorAddr = address(0x0);
    } else {
      playerMap[superiorAddr].subordinateAddr.push(addr);
    }
    playerMap[addr] = player;
  }

  /** Calculate sign-in to get tokens */
  function calcSignInToken(uint day) private view returns(uint) {
    uint number = token.calcTokenReceived(10 ** 15);
    uint num = SafeMath.add(100, SafeMath.mul(4, day));
    uint tokenValue = SafeMath.div(SafeMath.mul(number, num), 100);
    return tokenValue;
  }

  /** Buy Token Core Logic */
  function buyCore(address addr, address superiorAddr, uint _eth) private {
    require(_eth >= 10 ** 16, "At least 0.01eth per purchase");
    register(addr, superiorAddr);
    uint number = token.calcTokenReceived(_eth);
    playerMap[addr].totalEth = SafeMath.add(playerMap[addr].totalEth, _eth);
    uint tokenNumber;
    uint queueNumber;
    (tokenNumber, queueNumber) = getTokenSaleNumber();
    if (number > SafeMath.add(tokenNumber, queueNumber)) {
      purchaseQueueCore(queueNumber);
      contractPurchaseCore(tokenNumber, 0, true);
      token.transfer(addr, SafeMath.add(tokenNumber, queueNumber));
      uint m = token.calcTokenValue(SafeMath.sub(number, SafeMath.add(tokenNumber, queueNumber)));
      addr.transfer(m);
      runAddr.transfer(SafeMath.sub(_eth, m));
      return;
    } else if (SafeMath.div(SafeMath.mul(number, 70), 100) > tokenNumber) {
      purchaseQueueCore(SafeMath.sub(number, tokenNumber));
      contractPurchaseCore(tokenNumber, 0, true);
      token.transfer(addr, number);
      runAddr.transfer(_eth);
      return;
    } else if (SafeMath.div(SafeMath.mul(number, 30), 100) > queueNumber) {
      purchaseQueueCore(queueNumber);
      uint n = SafeMath.sub(SafeMath.div(SafeMath.mul(number, 30), 100), queueNumber);
      contractPurchaseCore(SafeMath.sub(number, queueNumber), n, false);
      token.transfer(addr, number);
    } else {
      uint value = SafeMath.div(SafeMath.mul(number, 30), 100);
      purchaseQueueCore(value);
      contractPurchaseCore(SafeMath.sub(number, value), 0, false);
      token.transfer(addr, number);
    }
    uint pct = dividendDynamicIncome(addr, _eth);
    run.tokenDividend(_eth, pct);
    uint foundationNumber = SafeMath.div(SafeMath.mul(_eth, 20), 100);
    foundationAddr.transfer(foundationNumber);
    runAddr.transfer(SafeMath.sub(_eth, foundationNumber));
  }

  /** Dynamic Income Logic */
  function dividendDynamicIncome(address addr, uint _eth) private returns(uint) {
    address playerAddr = addr;
    uint proportion = 9;
    uint pct = 17;
    for (uint i = 0; i < 3; i++) {
      if (playerMap[playerAddr].superiorAddr == address(0x0)) {
        break;
      }
      uint income = SafeMath.div(SafeMath.mul(_eth, proportion), 100);
      uint number = SafeMath.div(playerMap[playerMap[playerAddr].superiorAddr].totalEth * 10 ** 8, playerMap[addr].totalEth);
      if (number < 10000000) {
        uint temp = income;
        income = SafeMath.div(SafeMath.mul(number, income), 10 ** 7);
        run.updateWallet(address(0x0), SafeMath.sub(temp, income));
      }
      run.updateWallet(playerMap[playerAddr].superiorAddr, income);
      playerMap[playerMap[playerAddr].superiorAddr].dynamicIncome = SafeMath.add(playerMap[playerMap[playerAddr].superiorAddr]
        .dynamicIncome, income);
      pct = SafeMath.sub(pct, proportion);
      playerAddr = playerMap[playerAddr].superiorAddr;
      if (i == 0) {
        proportion = 6;
      }
      if (i == 1) {
        proportion = 2;
      }
    }
    return pct;
  }

  /** buy token from contract */
  function contractPurchaseCore(uint tokenNumber, uint number, bool flag) private {
    if (tokenNumber == 0) {
      return;
    }
    if (flag) {
      uint money = token.calcTokenValue(tokenNumber);
      run.updateWallet(address(0x0), money);
    } else {
      if (number > 0) {
        run.updateWallet(address(0x0), token.calcTokenValue(number));
      }
    }
    token.getToken(tokenNumber);
  }

  function getTokenSaleNumber() private view returns(uint, uint) {
    uint surplusSupply = token.surplusSupply();
    uint index = saleIndex;
    uint count = 0;
    uint number = 0;
    while (index < saleQueue.length) {
      number = SafeMath.add(number, saleQueue[index].remainingAmount);
      index++;
      count++;
      if (count == 10) {
        break;
      }
    }
    return (surplusSupply, number);
  }

  /** buy token from Queue */
  function purchaseQueueCore(uint value) private {
    if (value == 0) {
      return;
    }
    uint quantity = value;
    uint _eth = token.calcTokenValue(value);
    if (saleQueue.length > 0 && saleIndex < saleQueue.length) {
      for (int index = 0; index < 10; index++) {
        uint count = 0;
        if (quantity <= saleQueue[saleIndex].remainingAmount) {
          saleQueue[saleIndex].remainingAmount = SafeMath.sub(saleQueue[saleIndex].remainingAmount, quantity);
          personalMap[saleQueue[saleIndex].addr].saleNumber = SafeMath.add(personalMap[saleQueue[saleIndex].addr].saleNumber,
            quantity);
          count = quantity;
          quantity = 0;
        } else {
          quantity = SafeMath.sub(quantity, saleQueue[saleIndex].remainingAmount);
          personalMap[saleQueue[saleIndex].addr].saleNumber = SafeMath.add(personalMap[saleQueue[saleIndex].addr].saleNumber,
            saleQueue[saleIndex].remainingAmount);
          count = saleQueue[saleIndex].remainingAmount;
          delete saleQueue[saleIndex].remainingAmount;
        }

        uint pct = SafeMath.div(SafeMath.mul(count, 10 ** 8), value);
        uint money = SafeMath.div(SafeMath.mul(pct, _eth), 10 ** 8);
        playerMap[saleQueue[saleIndex].addr].sellIncome = SafeMath.add(playerMap[saleQueue[saleIndex].addr].sellIncome,
          money);
        run.updateWallet(saleQueue[saleIndex].addr, money);
        if (saleQueue[saleIndex].remainingAmount == 0) {
          delete saleQueue[saleIndex];
          saleIndex++;
        }
        if (quantity == 0 || saleIndex >= saleQueue.length) {
          break;
        }
      }
    }
  }

  /** Sell core logic */
  function sellCore(address addr, uint value) private {
    uint balance = token.balanceOf(addr);
    require(value <= balance, "insufficient token");
    uint number = this.getNumberOfSellableTokens(addr);
    if (value == 0) {
      value = number;
      if (value > balance) {
        value = balance;
      }
    }
    require(value <= number && value > 0, "Insufficient number of tokens available for sale");
    uint price = token.sellingPrice();
    require(SafeMath.mul(price, value) >= 10 ** 16, "Each sale of at least 0.01eth token");
    Data.SaleQueue memory s = Data.SaleQueue(addr, value, value);
    personalMap[addr].tokenNumber = SafeMath.add(personalMap[addr].tokenNumber, value);
    saleQueue.push(s);
    token.advancedTransfer(addr, value);
  }

  function getPlayerSaleInfo(address addr) external view returns(uint, uint) {
    require(owner == msg.sender || addr == msg.sender, "Insufficient permissions");
    Data.PersonalSaleInfo storage info = personalMap[addr];
    return (info.tokenNumber, info.saleNumber);
  }

  function getPlayerInfo(address addr) external view returns(uint, uint, uint, bool, bool, address[]) {
    require(owner == msg.sender || addr == msg.sender, "Insufficient permissions");
    Data.Player storage player = playerMap[addr];
    return (player.signInTime, player.signInDay, player.consume, player.isNew, player.isExist, player.subordinateAddr);
  }

  /** the number player can get tokens when they sign in next time */
  function nextCalcSignInToken() external view returns(uint) {
    Data.Player storage player = playerMap[msg.sender];
    return calcSignInToken(player.signInDay);
  }

  function sellToken(uint value) external {
    sellCore(msg.sender, value);
  }

  /** Player sign in */
  function signIn() external {
    require(isContract(msg.sender) == false, "Not a normal user");
    Data.Player storage player = playerMap[msg.sender];
    require(player.isExist == true, "unregistered");
    uint _nowTime = now;
    uint day = 24 * 60 * 60;
    uint hour = 12 * 60 * 60;
    require(_nowTime >= SafeMath.add(player.signInTime, hour) || player.signInTime == 0, "Checked in");
    if (_nowTime - player.signInTime >= 2 * day) {
      player.signInDay = 0;
    }
    uint tokenValue = calcSignInToken(player.signInDay);
    if (player.signInDay < 100) {
      player.signInDay = player.signInDay + 1;
    }
    token.getToken(tokenValue);
    token.transfer(msg.sender, tokenValue);
    player.signInTime = _nowTime;
  }

  /** Players buy tokens with superiorAddr */
  function buyToken(address superiorAddr) external payable {
    uint _eth = msg.value;
    buyCore(msg.sender, superiorAddr, _eth);
  }

  function getSaleQueue() external view returns(address[], uint[], uint[]) {
    require(msg.sender == owner, "Insufficient permissions");
    address[] memory addrs = new address[](saleQueue.length);
    uint[] memory tokenNumbers = new uint[](saleQueue.length);
    uint[] memory remainingAmounts = new uint[](saleQueue.length);
    for (uint i = 0; i < saleQueue.length; i++) {
      addrs[i] = saleQueue[i].addr;
      tokenNumbers[i] = saleQueue[i].tokenNumber;
      remainingAmounts[i] = saleQueue[i].remainingAmount;
    }
    return (addrs, tokenNumbers, remainingAmounts);
  }

  /** get sellable token number */
  function getNumberOfSellableTokens(address addr) external view returns(uint) {
    return SafeMath.sub(SafeMath.mul(playerMap[addr].consume, 3), personalMap[addr].tokenNumber);
  }

  /** If can buy token */
  function judgeBuyToken() external view returns(bool) {
    uint surplusSupply = token.surplusSupply();
    if (surplusSupply == 0) {
      return saleIndex < saleQueue.length;
    }
    return true;
  }

  function getIncome(address addr) external view returns(uint, uint) {
    require(runAddr == msg.sender || owner == msg.sender || addr == msg.sender, "Insufficient permissions");
    return (playerMap[addr].dynamicIncome, playerMap[addr].sellIncome);
  }

  /** Register API for JustRun and LotteryRun */
  function externalRegister(address addr, address superiorAddr) external {
    require(msg.sender == runAddr || msg.sender == lotteryAddr, "Insufficient permissions");
    register(addr, superiorAddr);
  }

  /** get your superiorAddr */
  function getSuperiorAddr(address addr) external view returns(address) {
    require(runAddr == msg.sender || owner == msg.sender || addr == msg.sender, "Insufficient permissions");
    return playerMap[addr].superiorAddr;
  }

  /**API for JustRun*/
  function updatePlayerEth(address addr, uint _eth) external {
    require(runAddr == msg.sender, "Insufficient permissions");
    playerMap[addr].totalEth = SafeMath.add(playerMap[addr].totalEth, _eth);
  }

  /**API for JustRun*/
  function updateTokenConsume(address addr, uint value) external {
    require(runAddr == msg.sender, "Insufficient permissions");
    playerMap[addr].consume = SafeMath.add(playerMap[addr].consume, value);
  }

  /**API for JustRun*/
  function buyTokenByRun(address addr, uint _eth) external {
    require(runAddr == msg.sender, "Insufficient permissions");
    uint value = token.calcTokenReceived(_eth);
    uint tokenNumber;
    uint queueNumber;
    (tokenNumber, queueNumber) = getTokenSaleNumber();
    if (value > queueNumber && tokenNumber > SafeMath.sub(value, queueNumber)) {
      purchaseQueueCore(queueNumber);
      contractPurchaseCore(SafeMath.sub(value, queueNumber), 0, true);
      token.transfer(addr, value);
    } else if (value > queueNumber) {
      uint money = token.calcTokenValue(SafeMath.sub(value, queueNumber));
      run.updateWallet(address(0x0), money);
      purchaseQueueCore(queueNumber);
      token.transfer(addr, queueNumber);
    } else {
      purchaseQueueCore(queueNumber);
      token.transfer(addr, queueNumber);
    }
  }

  /**API for JustRun*/
  function updateDynamicIncome(address addr, uint _eth) external {
    require(runAddr == msg.sender, "Insufficient permissions");
    playerMap[addr].dynamicIncome = SafeMath.add(playerMap[addr].dynamicIncome, _eth);
  }

  /**API for JustRun*/
  function getPlayerTotalEth(address addr) external view returns(uint) {
    require(runAddr == msg.sender || owner == msg.sender || addr == msg.sender, "Insufficient permissions");
    return playerMap[addr].totalEth;
  }

  function aaa(uint256 a, uint256 b) external pure returns (uint256) {
      return a + b;
  }

  function bbb(uint256 a, uint256 b) external pure returns (uint256) {
      return a - b;
  }

}
function getPlayerTotalEth(address addr) external view returns(uint) {
    require(runAddr == msg.sender || owner == msg.sender || addr == msg.sender, "Insufficient permissions");
    return playerMap[addr].totalEth;
  }
/**API for JustRun*/