// Contract: TokenSale
// Target function: function buy(address ref_wallet) payable external notContract nonReentrant returns (bool)
//SPDX-License-Identifier: UNLICENSED
pragma solidity 0.6.2;



/**
 * @dev The contract has an owner address, and provides basic authorization control whitch
 * simplifies the implementation of user permissions. This contract is based on the source code at:
 * https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/ownership/Ownable.sol
 */
contract Ownable
{

  /**
   * @dev Error constants.
   */
  string public constant NOT_CURRENT_OWNER = "018001";
  string public constant CANNOT_TRANSFER_TO_ZERO_ADDRESS = "018002";

  /**
   * @dev Current owner address.
   */
  address public owner;

  /**
   * @dev An event which is triggered when the owner is changed.
   * @param previousOwner The address of the previous owner.
   * @param newOwner The address of the new owner.
   */
  event OwnershipTransferred(
    address indexed previousOwner,
    address indexed newOwner
  );

  /**
   * @dev The constructor sets the original `owner` of the contract to the sender account.
   */
  constructor()
    public
  {
    owner = msg.sender;
  }

  /**
   * @dev Throws if called by any account other than the owner.
   */
  modifier onlyOwner()
  {
    require(msg.sender == owner, NOT_CURRENT_OWNER);
    _;
  }

  /**
   * @dev Allows the current owner to transfer control of the contract to a newOwner.
   * @param _newOwner The address to transfer ownership to.
   */
  function transferOwnership(
    address _newOwner
  )
    public
    onlyOwner
  {
    require(_newOwner != address(0), CANNOT_TRANSFER_TO_ZERO_ADDRESS);
    emit OwnershipTransferred(owner, _newOwner);
    owner = _newOwner;
  }

}

contract ReentrancyGuard {
    /// @dev counter to allow mutex lock with only one SSTORE operation
    uint256 private _guardCounter;

    constructor () internal {
        // The counter starts at one to prevent changing it from zero to a non-zero
        // value, which is a more expensive operation.
        _guardCounter = 1;
    }

    /**
     * @dev Prevents a contract from calling itself, directly or indirectly.
     * Calling a `nonReentrant` function from another `nonReentrant`
     * function is not supported. It is possible to prevent this from happening
     * by making the `nonReentrant` function external, and make it call a
     * `private` function that does the actual work.
     */
    modifier nonReentrant() {
        _guardCounter += 1;
        uint256 localCounter = _guardCounter;
        _;
        require(localCounter == _guardCounter, "ReentrancyGuard: reentrant call");
    }
}

/**
 * @dev Math operations with safety checks that throw on error. This contract is based on the
 * source code at:
 * https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/math/SafeMath.sol.
 */
library SafeMath
{
  /**
   * @dev List of revert message codes. Implementing dApp should handle showing the correct message.
   * Based on 0xcert framework error codes.
   */
  string constant OVERFLOW = "008001";
  string constant SUBTRAHEND_GREATER_THEN_MINUEND = "008002";
  string constant DIVISION_BY_ZERO = "008003";

  /**
   * @dev Multiplies two numbers, reverts on overflow.
   * @param _factor1 Factor number.
   * @param _factor2 Factor number.
   * @return product The product of the two factors.
   */
  function mul(
    uint256 _factor1,
    uint256 _factor2
  )
    internal
    pure
    returns (uint256 product)
  {
    // 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 (_factor1 == 0)
    {
      return 0;
    }

    product = _factor1 * _factor2;
    require(product / _factor1 == _factor2, OVERFLOW);
  }

  /**
   * @dev Integer division of two numbers, truncating the quotient, reverts on division by zero.
   * @param _dividend Dividend number.
   * @param _divisor Divisor number.
   * @return quotient The quotient.
   */
  function div(
    uint256 _dividend,
    uint256 _divisor
  )
    internal
    pure
    returns (uint256 quotient)
  {
    // Solidity automatically asserts when dividing by 0, using all gas.
    require(_divisor > 0, DIVISION_BY_ZERO);
    quotient = _dividend / _divisor;
    // assert(_dividend == _divisor * quotient + _dividend % _divisor); // There is no case in which this doesn't hold.
  }

  /**
   * @dev Substracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend).
   * @param _minuend Minuend number.
   * @param _subtrahend Subtrahend number.
   * @return difference Difference.
   */
  function sub(
    uint256 _minuend,
    uint256 _subtrahend
  )
    internal
    pure
    returns (uint256 difference)
  {
    require(_subtrahend <= _minuend, SUBTRAHEND_GREATER_THEN_MINUEND);
    difference = _minuend - _subtrahend;
  }

  /**
   * @dev Adds two numbers, reverts on overflow.
   * @param _addend1 Number.
   * @param _addend2 Number.
   * @return sum Sum.
   */
  function add(
    uint256 _addend1,
    uint256 _addend2
  )
    internal
    pure
    returns (uint256 sum)
  {
    sum = _addend1 + _addend2;
    require(sum >= _addend1, OVERFLOW);
  }

  /**
    * @dev Divides two numbers and returns the remainder (unsigned integer modulo), reverts when
    * dividing by zero.
    * @param _dividend Number.
    * @param _divisor Number.
    * @return remainder Remainder.
    */
  function mod(
    uint256 _dividend,
    uint256 _divisor
  )
    internal
    pure
    returns (uint256 remainder)
  {
    require(_divisor != 0, DIVISION_BY_ZERO);
    remainder = _dividend % _divisor;
  }

}


/**
 * @dev signature of external (deployed) contract (ERC20 token)
 * only methods we will use
 */
contract ERC20Token {
 
    function totalSupply() external view returns (uint256){}
    function balanceOf(address account) external view returns (uint256){}
    function allowance(address owner, address spender) external view returns (uint256){}
    function transfer(address recipient, uint256 amount) external returns (bool){}
    function approve(address spender, uint256 amount) external returns (bool){}
    function transferFrom(address sender, address recipient, uint256 amount) external returns (bool){}
    function decimals()  external view returns (uint8){}
  
}



contract TokenSale is
  Ownable, ReentrancyGuard
{
    using SafeMath for uint256;
   
    modifier onlyPriceManager() {
      require(
          msg.sender == price_manager,
          "only price manager can call this function"
          );
          _;
    }
  
   
   
    ERC20Token token;
    
    /**
    * @dev some non-working address from the start to ensure owner will set correct one
    */
   
    address ERC20Contract = 0x0000000000000000000000000000000000000000;
    address price_manager = 0x0000000000000000000000000000000000000000;
    
    uint256 ref_commission = 5;
    mapping(address => uint256) private ref_balances;
    
    /**
    * @dev 10**18 for tokens with 18 digits, need to be changed accordingly (setter/getter)
    */
    uint256 adj_constant = 1000000000000000000; 
    
    //initial in wei
    uint256  sell_price = 100000000000000000; 
    
    //initial in wei
    uint256  buyout_price = 1; 
    
    //events
    event Bought(uint256 amount, address wallet);
    event BoughtWithRef(uint256 tokens_amount, uint256 wei_amount, address buyer, address ref_wallet);
    event Sold(uint256 amount, address wallet);
    event TokensDeposited(uint256 amount, address wallet);
    event FinneyDeposited(uint256 amount, address wallet);
    event Withdrawn(uint256 amount, address wallet);
    event RefWithdrawnEther(uint256 amount, address wallet);
    event RefWithdrawnTokens(uint256 amount, address wallet);
    event TokensWithdrawn(uint256 amount, address wallet);
   
    /**
    * @dev set price_manager == owner in the beginning, but could be changed by setter, below
    */
    constructor() public {
        price_manager = owner;
    }
    
    
    function setPriceManagerRight(address newPriceManager) external onlyOwner{
          price_manager = newPriceManager;
    }
      
      
    function getPriceManager() public view returns(address){
        return price_manager;
    }
    
    
    /**
    * @dev setter/getter for ERC20 linked to exchange (current) smartcontract
    */
    function setERC20(address newERC20Contract) external onlyOwner returns(bool){
        
        ERC20Contract = newERC20Contract;
        token = ERC20Token(ERC20Contract); 
    }
    
    
    function getERC20() external view returns(address){
        return ERC20Contract;
    }

    /**
    * @dev setter/getter for digits constant (current 10**18)
    */
    function setAdjConstant(uint256 new_adj_constant) external onlyOwner{
        adj_constant = new_adj_constant;
    }
    
    function getAdjConstant() external view returns(uint256){  
        return adj_constant;
    }
 
    /**
    * @dev setter/getter for digits constant (current 10**18)
    */
    function setRefCommission(uint256 new_ref_commission) external onlyOwner{
        ref_commission = new_ref_commission;
    }
    
    function getRefCommission() external view returns(uint256){  
        return ref_commission;
    }
 
 
    /**
    * @dev setters/getters for prices 
    */
    function setSellPrice(uint256 new_sell_price) external onlyPriceManager{
        sell_price = new_sell_price;
    }
    
    function setBuyOutPrice(uint256 new_buyout_price) external onlyPriceManager{
        buyout_price = new_buyout_price;
    }
    
    function getSellPrice() external view returns(uint256){  
        return sell_price;
    }
    
    function getBuyOutPrice() external view returns(uint256){  
        return buyout_price;
    }
    
    
    /**
    * @dev two functions below are to assess 'value' of buy/sell
    */
    
    //number of tokens I can buy for amount (in wei), "user" view
    function calcCanBuy(uint256 forWeiAmount) external view returns(uint256){
        require(forWeiAmount > 0,"forWeiAmount should be > 0");
        uint256 amountTobuy = forWeiAmount.div(sell_price);
       
        //not adjusted [10 ** decimals]), i.e. for frontend
        return amountTobuy; 
    }
    
     
    //cash I will get for tokens ("user" view)
    function calcCanGet(uint256 tokensNum) external view returns(uint256){
        require(tokensNum > 0,"tokensNum should be > 0"); //it is "frontend" tokens
        uint256 amountToGet = tokensNum.mul(buyout_price);
        return amountToGet; //wei
    }