contract CommonBsPresale is SafeMath, Ownable, Pausable {

    enum Currency { BTC, LTC, ZEC, DASH, WAVES, USD, EUR }

    // TODO rename to Buyer?

    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?

    // (buyer_eth_address -> struct)
    mapping(address => Backer) public backers;

    // currency_code => (tx_hash => tokens)
    mapping(uint8 => mapping(bytes32 => uint256)) public externalTxs;

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

    uint256 public minTokensToBuy = 50 * 1e18; // Including bonuses.
    uint256 public maxCapWei      = 50000 ether;

    uint public tokensPerWei         = 1000; // Ordinary price: 1 ETH = 1000 tokens.
    uint public tokensPerWeiBonus333 = 1333;
    uint public tokensPerWeiBonus250 = 1250;
    uint public tokensPerWeiBonus111 = 1111;

    uint public startTime       = 1510160700; // 2017-11-08T17:05:00Z
    uint public bonusEndTime333 = 1510333500; // 2017-11-10T17:05:00Z
    uint public bonusEndTime250 = 1510679100; // 2017-11-14T17:05:00Z
    uint public endTime         = 1511024700; // 2017-11-18T17:05:00Z

    // Stats for current crowdsale

    uint256 public totalInWei         = 0; // Grand total in wei
    uint256 public totalTokensSold    = 0; // Total amount of tokens sold during this crowdsale.
    uint256 public totalEthSales      = 0; // Total amount of ETH contributions during this crowdsale.
    uint256 public totalExternalSales = 0; // Total amount of external contributions (BTC, LTC, USD, etc.) during this crowdsale.
    uint256 public weiReceived        = 0; // Total amount of wei received during this crowdsale smart contract.

    uint public finalizedTime = 0; // Unix timestamp when finalize() was called.

    bool public saleEnabled = true;   // if false, 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 EthReceived(address indexed _buyer, uint256 _amountWei);
    event ExternalSale(Currency _currency, bytes32 _txIdSha3, address indexed _buyer, uint256 _amountWei, uint256 _tokensE18);

    modifier respectTimeFrame() {
        require(isSaleOn());
        _;
    }

    modifier canNotify() {
        require(msg.sender == owner || msg.sender == notifier);
        _;
    }

    function CommonBsPresale(address _token, address _beneficiary) {
        token = CommonBsToken(_token);
        owner = msg.sender;
        notifier = owner;
        beneficiary = _beneficiary;
    }

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

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

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

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

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

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

        totalInWei = safeAdd(totalInWei, _amountWei);
        weiReceived = safeAdd(weiReceived, _amountWei);
        require(totalInWei <= maxCapWei); // If max cap reached.

        uint256 tokensE18 = weiToTokens(_amountWei);
        require(tokensE18 >= minTokensToBuy);

        require(token.sell(_buyer, tokensE18)); // Transfer tokens to buyer.
        totalTokensSold = safeAdd(totalTokensSold, tokensE18);
        totalEthSales++;

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

        EthReceived(_buyer, _amountWei);
    }

    // Calc how much tokens you can buy at current time.
    function weiToTokens(uint256 _amountWei) public constant returns (uint256) {
        return weiToTokensAtTime(_amountWei, getNow());
    }

    function weiToTokensAtTime(uint256 _amountWei, uint _time) public constant returns (uint256) {
        uint256 rate = tokensPerWei;

        if (startTime <= _time && _time < bonusEndTime333) rate = tokensPerWeiBonus333;
        else if (bonusEndTime333 <= _time && _time < bonusEndTime250) rate = tokensPerWeiBonus250;
        else if (bonusEndTime250 <= _time && _time < endTime) rate = tokensPerWeiBonus111;

        return safeMul(_amountWei, rate);
    }

    //----------------------------------------------------------------------
    // Begin of external sales.

    function externalSales(
        uint8[] _currencies,
        bytes32[] _txIdSha3,
        address[] _buyers,
        uint256[] _amountsWei,
        uint256[] _tokensE18
    ) public ifNotPaused canNotify {

        require(_currencies.length > 0);
        require(_currencies.length == _txIdSha3.length);
        require(_currencies.length == _buyers.length);
        require(_currencies.length == _amountsWei.length);
        require(_currencies.length == _tokensE18.length);

        for (uint i = 0; i < _txIdSha3.length; i++) {
            _externalSaleSha3(
                Currency(_currencies[i]),
                _txIdSha3[i],
                _buyers[i],
                _amountsWei[i],
                _tokensE18[i]
            );
        }
    }

    function _externalSaleSha3(
        Currency _currency,
        bytes32 _txIdSha3, // To get bytes32 use keccak256(txId) OR sha3(txId)
        address _buyer,
        uint256 _amountWei,
        uint256 _tokensE18
    ) internal {

        require(_buyer > 0 && _amountWei > 0 && _tokensE18 > 0);

        var txsByCur = externalTxs[uint8(_currency)];

        // If this foreign transaction has been already processed in this contract.
        require(txsByCur[_txIdSha3] == 0);

        totalInWei = safeAdd(totalInWei, _amountWei);
        require(totalInWei <= maxCapWei); // Max cap should not be reached yet.

        require(token.sell(_buyer, _tokensE18)); // Transfer tokens to buyer.
        totalTokensSold = safeAdd(totalTokensSold, _tokensE18);
        totalExternalSales++;

        txsByCur[_txIdSha3] = _tokensE18;
        ExternalSale(_currency, _txIdSha3, _buyer, _amountWei, _tokensE18);
    }

    // Get id of currency enum. --------------------------------------------

    function btcId() public constant returns (uint8) {
        return uint8(Currency.BTC);
    }

    function ltcId() public constant returns (uint8) {
        return uint8(Currency.LTC);
    }

    function zecId() public constant returns (uint8) {
        return uint8(Currency.ZEC);
    }

    function dashId() public constant returns (uint8) {
        return uint8(Currency.DASH);
    }

    function wavesId() public constant returns (uint8) {
        return uint8(Currency.WAVES);
    }

    function usdId() public constant returns (uint8) {
        return uint8(Currency.USD);
    }

    function eurId() public constant returns (uint8) {
        return uint8(Currency.EUR);
    }

    // Get token count by transaction id. ----------------------------------

    function _tokensByTx(Currency _currency, string _txId) internal constant returns (uint256) {
        return tokensByTx(uint8(_currency), _txId);
    }

    function tokensByTx(uint8 _currency, string _txId) public constant returns (uint256) {
        return externalTxs[_currency][keccak256(_txId)];
    }

    function tokensByBtcTx(string _txId) public constant returns (uint256) {
        return _tokensByTx(Currency.BTC, _txId);
    }

    function tokensByLtcTx(string _txId) public constant returns (uint256) {
        return _tokensByTx(Currency.LTC, _txId);
    }

    function tokensByZecTx(string _txId) public constant returns (uint256) {
        return _tokensByTx(Currency.ZEC, _txId);
    }

    function tokensByDashTx(string _txId) public constant returns (uint256) {
        return _tokensByTx(Currency.DASH, _txId);
    }

    function tokensByWavesTx(string _txId) public constant returns (uint256) {
        return _tokensByTx(Currency.WAVES, _txId);
    }

    function tokensByUsdTx(string _txId) public constant returns (uint256) {
        return _tokensByTx(Currency.USD, _txId);
    }

    function tokensByEurTx(string _txId) public constant returns (uint256) {
        return _tokensByTx(Currency.EUR, _txId);
    }

    // End of external sales.
    //----------------------------------------------------------------------

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

    function isMaxCapReached() public constant returns (bool) {
        return totalInWei >= maxCapWei;
    }

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

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

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

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

        // Cannot finalise before end day of crowdsale until max cap is reached.
        require(isMaxCapReached() || isSaleOver());

        beneficiary.transfer(this.balance);

        finalizedTime = getNow();
    }
}
function _tokensByTx(Currency _currency, string _txId) internal constant returns (uint256) {
    return tokensByTx(uint8(_currency), _txId);
}

// Get token count by transaction id. ----------------------------------