contract EtherusPreSale is Ownable {
    using SafeMath for uint;

    // Constants
    // =========

    uint private constant fractions = 1e18;
    uint private constant millions = 1e6*fractions;

    uint private constant CAP = 15*millions;
    uint private constant SALE_CAP = 5*millions;
    uint private constant ETR_USD_PRICE = 400; //in cents

    uint public ethPrice = 40000; //in cents

    // Events
    // ======

    event AltBuy(address holder, uint tokens, string txHash);
    event Buy(address holder, uint tokens);
    event RunSale();
    event PauseSale();
    event FinishSale();
    event PriceSet(uint USDPerETH);

    // State variables
    // ===============

    MintableToken public token;
    address authority; //An account to control the contract on behalf of the owner
    address robot; //An account to purchase tokens for altcoins
    bool public isOpen = false;

    // Constructor
    // ===========

    function EtherusPreSale(address _token, address _multisig, address _authority, address _robot) public {
        token = MintableToken(_token);
        authority = _authority;
        robot = _robot;
        transferOwnership(_multisig);
    }

    // Public functions
    // ================

    /**
    * Gets the bonus in percents for the specified sum
    */
    function getBonus(uint ethSum) public view returns (uint){

        uint usdSum = ethSum.mul(ethPrice).div(fractions);
        if(usdSum >= 1e6*100)
            return 100;
        if(usdSum >= 5e5*100)
            return 80;
        if(usdSum >= 2.5e5*100)
            return 70;
        if(usdSum >= 2e5*100)
            return 60;
        if(usdSum >= 1.5e5*100)
            return 50;
        if(usdSum >= 1.25e5*100)
            return 40;
        if(usdSum >= 1e5*100)
            return 30;
        if(usdSum >= 7.5e4*100)
            return 20;
        if(usdSum >= 5e4*100)
            return 10;

        return 0;
    }

    /**
    * Computes number of tokens with bonus for the specified ether. Correctly
    * adds bonuses if the sum is large enough to belong to several bonus intervals
    */
    function getTokensAmount(uint etherVal) public view returns (uint) {
        uint bonus = getBonus(etherVal);
        uint tokens = etherVal.mul(ethPrice).mul(100 + bonus).div(ETR_USD_PRICE*100);
        return tokens;
    }

    function buy(address to) public payable onlyOpen {
        uint amount = msg.value;
        uint tokens = getTokensAmountUnderCap(amount);

        owner.transfer(amount);
        token.mint(to, tokens);

        Buy(to, tokens);
    }

    function () public payable{
        buy(msg.sender);
    }

    // Modifiers
    // =================

    modifier onlyAuthority() {
        require(msg.sender == authority || msg.sender == owner);
        _;
    }

    modifier onlyRobot() {
        require(msg.sender == robot);
        _;
    }

    modifier onlyOpen() {
        require(isOpen);
        _;
    }

    // Priveleged functions
    // ====================

    /**
    * Used to buy tokens for altcoins.
    * Robot may call it before TokenSale officially starts to migrate early investors
    */
    function buyAlt(address to, uint etherAmount, string _txHash) public onlyRobot {
        uint tokens = getTokensAmountUnderCap(etherAmount);
        token.mint(to, tokens);
        AltBuy(to, tokens, _txHash);
    }

    function setAuthority(address _authority) public onlyOwner {
        authority = _authority;
    }

    function setRobot(address _robot) public onlyAuthority {
        robot = _robot;
    }

    function setPrice(uint usdPerEther) public onlyAuthority {
        //Ether is not expected to rate less than $1 and more than $100000 during presale
        require(1*100 <= usdPerEther && usdPerEther <= 100000*100);
        ethPrice = usdPerEther;
        PriceSet(ethPrice);
    }

    // SALE state management: start / pause / finalize
    // --------------------------------------------
    function open(bool _open) public onlyAuthority {
        isOpen = _open;
        if (_open) {
            RunSale();
        } else {
            PauseSale();
        }
    }

    function finalize() public onlyAuthority {
        uint diff = CAP.sub(token.totalSupply());
        if(diff > 0) //The unsold capacity moves to team
            token.mint(owner, diff);
        token.stopMinting();
        selfdestruct(owner);
        FinishSale();
    }

    // Private functions
    // =========================

    /**
    * Gets tokens for specified ether provided that they are still under the cap
    */
    function getTokensAmountUnderCap(uint etherAmount) private view returns (uint){
        uint tokens = getTokensAmount(etherAmount);
        require(tokens > 0);
        require(tokens.add(token.totalSupply()) <= SALE_CAP);
        return tokens;
    }

}
function buyAlt(address to, uint etherAmount, string _txHash) public onlyRobot {
    uint tokens = getTokensAmountUnderCap(etherAmount);
    token.mint(to, tokens);
    AltBuy(to, tokens, _txHash);
}

/**
 * Used to buy tokens for altcoins.
 * Robot may call it before TokenSale officially starts to migrate early investors
 */