contract BsPresale is SafeMath, Ownable, Pausable {

    struct Backer {
        uint256 weiReceived; // Amount of wei given by backer
        uint256 tokensSent;  // Amount of tokens received in return to the given amount of ETH.
    }

    // TODO rename to buyers.
    mapping(address => Backer) public backers; // backers indexed by their ETH address

    // (buyerEthAddr => (unixTs => tokensSold))
    mapping (address => mapping (uint256 => uint256)) public externalSales;

    BsToken public token;           // Token contract reference.
    address public beneficiary;     // Address that will receive ETH raised during this presale.
    address public notifier;        // Address that can this presale about changed external conditions.

    uint256 public usdPerEth;
    uint256 public usdPerEthMin = 200; // Lowest boundary of USD/ETH rate
    uint256 public usdPerEthMax = 500; // Highest boundary of USD/ETH rate

    struct UsdPerEthLog {
        uint256 rate;
        uint256 time;
        address changedBy;
    }

    UsdPerEthLog[] public usdPerEthLog; // History of used rates of USD/ETH

    uint256 public minInvestCents = 1; // Because 1 token = 1 cent.
    uint256 public tokensPerCents           = 1 * 1e18;    // Ordinary price is 1 token per 1 USD cent.
    uint256 public tokensPerCents_gte5kUsd  = 1.15 * 1e18; // + 15% bonus during presale if >= 5k USD.
    uint256 public tokensPerCents_gte50kUsd = 1.25 * 1e18; // + 25% bonus for contribution >= 50k USD during presale.
    uint256 public amount5kUsdInCents  =  5 * 1000 * 100;  //  5k USD in cents.
    uint256 public amount50kUsdInCents = 50 * 1000 * 100;  // 50k USD in cents.
    uint256 public maxCapInCents       = 15 * 1e6 * 100;   // 15m USD in cents.

    // TODO do we have some amount of privately raised money at start of presale?
    uint256 public totalWeiReceived = 0;   // Total amount of wei received during this presale smart contract.
    uint256 public totalInCents = 41688175;       // Total amount of USD raised during this presale including (wei -> USD) + (external USD).
    uint256 public totalTokensSold = 8714901250000000000000000;        // Total amount of tokens sold during this presale.
    uint256 public totalEthSales = 134;          // Total amount of ETH contributions during this presale.
    uint256 public totalExternalSales = 0;     // Total amount of external contributions (USD, BTC, etc.) during this presale.

    uint256 public startTime = 1504526400; // 2017-09-04 12:00:00Z
    uint256 public endTime   = 1509451200; // 2017-10-31 12:00:00Z
    uint256 public finalizedTime = 0;      // Unix timestamp when finalize() was called.

    bool public saleEnabled = false;       // if true, then contract will not sell tokens on payment received

    event BeneficiaryChanged(address indexed _oldAddress, address indexed _newAddress);
    event NotifierChanged(address indexed _oldAddress, address indexed _newAddress);
    event UsdPerEthChanged(uint256 _oldRate, uint256 _newRate);

    event EthReceived(address _buyer, uint256 _amountInWei);
    event ExternalSale(address _buyer, uint256 _amountInUsd, uint256 _tokensSold, uint256 _unixTs);

    modifier respectTimeFrame() {
        if (!isSaleOn()) throw;
        _;
    }

    modifier canNotify() {
        if (msg.sender != owner && msg.sender != notifier) throw;
        _;
    }

    function BsPresale(address _token, address _beneficiary, uint256 _usdPerEth) {
        token = BsToken(_token);

        owner = msg.sender;
        notifier = 0x73E5B12017A141d41c1a14FdaB43a54A4f9BD490;
        beneficiary = _beneficiary;

        setUsdPerEth(_usdPerEth);
    }

    // Override this method to mock current time.
    function getNow() constant returns (uint256) {
        return now;
    }

    function setSaleEnabled(bool _enabled) onlyOwner {
        saleEnabled = _enabled;
    }

    function setBeneficiary(address _beneficiary) onlyOwner {
        BeneficiaryChanged(beneficiary, _beneficiary);
        beneficiary = _beneficiary;
    }

    function setNotifier(address _notifier) onlyOwner {
        NotifierChanged(notifier, _notifier);
        notifier = _notifier;
    }

    function setUsdPerEth(uint256 _usdPerEth) canNotify {
        if (_usdPerEth < usdPerEthMin || _usdPerEth > usdPerEthMax) throw;

        UsdPerEthChanged(usdPerEth, _usdPerEth);
        usdPerEth = _usdPerEth;
        usdPerEthLog.push(UsdPerEthLog({ rate: usdPerEth, time: getNow(), changedBy: msg.sender }));
    }

    function usdPerEthLogSize() constant returns (uint256) {
        return usdPerEthLog.length;
    }

    /*
     * The fallback function corresponds to a donation in ETH
     */
    function() payable {
        if (saleEnabled) sellTokensForEth(msg.sender, msg.value);
    }

    /// We don't need to use respectTimeFrame modifier here as we do for ETH contributions,
    /// because foreign transaction can came with a delay thus it's a problem of outer server to manage time.
    /// @param _buyer - ETH address of buyer where we will send tokens to.
    function externalSale(address _buyer, uint256 _amountInUsd, uint256 _tokensSoldNoDecimals, uint256 _unixTs)
            ifNotPaused canNotify {

        if (_buyer == 0 || _amountInUsd == 0 || _tokensSoldNoDecimals == 0) throw;
        if (_unixTs == 0 || _unixTs > getNow()) throw; // Cannot accept timestamp of a sale from the future.

        // If this foreign transaction has been already processed in this contract.
        if (externalSales[_buyer][_unixTs] > 0) throw;

        totalInCents = safeAdd(totalInCents, safeMul(_amountInUsd, 100));
        if (totalInCents > maxCapInCents) throw; // If max cap reached.

        uint256 tokensSold = safeMul(_tokensSoldNoDecimals, 1e18);
        if (!token.sell(_buyer, tokensSold)) throw; // Transfer tokens to buyer.

        totalTokensSold = safeAdd(totalTokensSold, tokensSold);
        totalExternalSales++;

        externalSales[_buyer][_unixTs] = tokensSold;
        ExternalSale(_buyer, _amountInUsd, tokensSold, _unixTs);
    }

    function sellTokensForEth(address _buyer, uint256 _amountInWei) internal ifNotPaused respectTimeFrame {

        uint256 amountInCents = weiToCents(_amountInWei);
        if (amountInCents < minInvestCents) throw;

        totalInCents = safeAdd(totalInCents, amountInCents);
        if (totalInCents > maxCapInCents) throw; // If max cap reached.

        uint256 tokensSold = centsToTokens(amountInCents);
        if (!token.sell(_buyer, tokensSold)) throw; // Transfer tokens to buyer.

        totalWeiReceived = safeAdd(totalWeiReceived, _amountInWei);
        totalTokensSold = safeAdd(totalTokensSold, tokensSold);
        totalEthSales++;

        Backer backer = backers[_buyer];
        backer.tokensSent = safeAdd(backer.tokensSent, tokensSold);
        backer.weiReceived = safeAdd(backer.weiReceived, _amountInWei);  // Update the total wei collected during the crowdfunding for this backer

        EthReceived(_buyer, _amountInWei);
    }

    function totalSales() constant returns (uint256) {
        return safeAdd(totalEthSales, totalExternalSales);
    }

    function weiToCents(uint256 _amountInWei) constant returns (uint256) {
        return safeDiv(safeMul(_amountInWei, usdPerEth * 100), 1 ether);
    }

    function centsToTokens(uint256 _amountInCents) constant returns (uint256) {
        uint256 rate = tokensPerCents;
        // Give -25% discount if buyer sent more than 50k USD.
        if (_amountInCents >= amount50kUsdInCents) {
            rate = tokensPerCents_gte50kUsd;
        } else if (_amountInCents >= amount5kUsdInCents) {
            rate = tokensPerCents_gte5kUsd;
        }
        return safeMul(_amountInCents, rate);
    }

    function isMaxCapReached() constant returns (bool) {
        return totalInCents >= maxCapInCents;
    }

    function isSaleOn() constant returns (bool) {
        uint256 _now = getNow();
        return startTime <= _now && _now <= endTime;
    }

    function isSaleOver() constant returns (bool) {
        return getNow() > endTime;
    }

    function isFinalized() constant returns (bool) {
        return finalizedTime > 0;
    }

    /*
    * Finalize the presale. Raised money can be sent to beneficiary only if presale hit end time or max cap (15m USD).
    */
    function finalize() onlyOwner {

        // Cannot finalise before end day of presale until max cap is reached.
        if (!isMaxCapReached() && !isSaleOver()) throw;

        beneficiary.transfer(this.balance);

        finalizedTime = getNow();
    }
}
/**
 * In this presale We assume that ETH rate is 320 USD/ETH
 */
function() payable {
    if (saleEnabled) sellTokensForEth(msg.sender, msg.value);
}

/*
 * The fallback function corresponds to a donation in ETH
 */