contract CrowdSale is Ownable {
    using SafeMath for uint256;

    event Payment(
        address wallet,
        uint date,
        uint256 amountEth,
        uint256 amountCoin,
        uint8 bonusPercent
    );

    uint constant internal MIN_TOKEN_AMOUNT = 5000;
    uint constant internal SECONDS_IN_DAY = 86400; // 24 * 60 * 60
    uint constant internal SECONDS_IN_YEAR = 31557600; // ( 365 * 24 + 6 ) * 60 * 60
    int8 constant internal PHASE_NOT_STARTED = -5;
    int8 constant internal PHASE_BEFORE_PRESALE = -4;
    int8 constant internal PHASE_BETWEEN_PRESALE_ICO = -3;
    int8 constant internal PHASE_ICO_FINISHED = -2;
    int8 constant internal PHASE_FINISHED = -1;
    int8 constant internal PHASE_PRESALE = 0;
    int8 constant internal PHASE_ICO_1 = 1;
    int8 constant internal PHASE_ICO_2 = 2;
    int8 constant internal PHASE_ICO_3 = 3;
    int8 constant internal PHASE_ICO_4 = 4;
    int8 constant internal PHASE_ICO_5 = 5;

    address internal manager;

    EyeToken internal token;
    address internal base_wallet;
    uint256 internal dec_mul;
    address internal vest_1;
    address internal vest_2;
    address internal vest_3;
    address internal vest_4;

    int8 internal phase_i; // see PHASE_XXX

    uint internal presale_start = 1533020400; // 2018-07-31 07:00 UTC
    uint internal presale_end = 1534316400; // 2018-08-15 07:00 UTC
    uint internal ico_start = 1537254000; // 2018-09-18 07:00 UTC
    uint internal ico_phase_1_days = 7;
    uint internal ico_phase_2_days = 7;
    uint internal ico_phase_3_days = 7;
    uint internal ico_phase_4_days = 7;
    uint internal ico_phase_5_days = 7;
    uint internal ico_phase_1_end;
    uint internal ico_phase_2_end;
    uint internal ico_phase_3_end;
    uint internal ico_phase_4_end;
    uint internal ico_phase_5_end;
    uint8[6] public bonus_percents = [50, 40, 30, 20, 10, 0];
    uint internal finish_date;
    uint public exchange_rate;  //  tokens in one ethereum * 1000
    uint256 public lastPayerOverflow = 0;

    /**
     * @dev Crowd-sale constructor
     */
    function CrowdSale() Ownable() public {
        phase_i = PHASE_NOT_STARTED;
        manager = address(0);
    }

    /**
     * @dev Allow only for owner or manager
     */
    modifier onlyOwnerOrManager(){
        require(msg.sender == owner || (msg.sender == manager && manager != address(0)), "Invalid owner or manager");
        _;
    }

    /**
     * @dev Returns current manager
     */
    function getManager() public view onlyOwnerOrManager returns (address) {
        return manager;
    }

    /**
     * @dev Sets new manager
     * @param _manager New manager
     */
    function setManager(address _manager) public onlyOwner {
        manager = _manager;
    }

    /**
     * @dev Set exchange rate
     * @param _rate New exchange rate
     *
     * executed by CRM
     */
    function setRate(uint _rate) public onlyOwnerOrManager {
        require(_rate > 0, "Invalid exchange rate");
        exchange_rate = _rate;
    }

    function _addPayment(address wallet, uint256 amountEth, uint256 amountCoin, uint8 bonusPercent) internal {
        emit Payment(wallet, now, amountEth, amountCoin, bonusPercent);
    }

    /**
     * @dev Start crowd-sale
     * @param _token Coin's contract
     * @param _rate current exchange rate
     */
    function start(address _token, uint256 _rate) public onlyOwnerOrManager {
        require(_rate > 0, "Invalid exchange rate");
        assert(phase_i == PHASE_NOT_STARTED);

        token = EyeToken(_token);
        base_wallet = token.wallet();
        dec_mul = 10 ** uint256(token.decimals());

        // Organizasional expenses
        address org_exp = 0x45709fcBeb5D133bFA336d8c70FFFF98eE815359;
        // Early birds
        address ear_brd = 0xE640b346E1d9A1eb3F809608a8a92f041D02F3BE;
        // Community development
        address com_dev = 0xdC2c6398F7a9cF2CbdCfEcB37CF732f486642316;
        // Special coins
        address special = 0x1dBcDb11c6C05a4EA541227fBdEeB02d6492BD07;
        // Team lock
        vest_1 = 0xC49d11a05aF6D5BDeBfd18E0010516D9840f3610;
        vest_2 = 0x7Fd486029C8D81f4894e4ef0D460c2bD97187aeF;
        vest_3 = 0xcCcC86e1086015AEE865165f6f93a82dE591Cb3C;
        vest_4 = 0xd7569317e6af13D4d3832613F930cc5b7cecaE6e;

        token.transferICO(org_exp, 600000000 * dec_mul);
        token.transferICO(ear_brd, 1000000000 * dec_mul);
        token.transferICO(com_dev, 1000000000 * dec_mul);
        token.transferICO(special, 800000000 * dec_mul);
        token.transferICO(vest_1, 500000000 * dec_mul);
        token.transferICO(vest_2, 500000000 * dec_mul);
        token.transferICO(vest_3, 500000000 * dec_mul);
        token.transferICO(vest_4, 500000000 * dec_mul);

        exchange_rate = _rate;

        phase_i = PHASE_BEFORE_PRESALE;
        _updatePhaseTimes();
    }

    /**
     * @dev Finalize ICO
     */
    function _finalizeICO() internal {
        assert(phase_i != PHASE_NOT_STARTED && phase_i != PHASE_FINISHED);
        phase_i = PHASE_ICO_FINISHED;
        uint curr_date = now;
        finish_date = (curr_date < ico_phase_5_end ? ico_phase_5_end : curr_date).add(SECONDS_IN_DAY * 10);
    }

    /**
     * @dev Finalize crowd-sale
     */
    function _finalize() internal {
        assert(phase_i != PHASE_NOT_STARTED && phase_i != PHASE_FINISHED);

        uint date = now.add(SECONDS_IN_YEAR);
        token.freeze(vest_1, date);
        date = date.add(SECONDS_IN_YEAR);
        token.freeze(vest_2, date);
        date = date.add(SECONDS_IN_YEAR);
        token.freeze(vest_3, date);
        date = date.add(SECONDS_IN_YEAR);
        token.freeze(vest_4, date);

        token.finalizeICO();
        token.transferOwnership(base_wallet);

        phase_i = PHASE_FINISHED;
    }

    /**
     * @dev Finalize crowd-sale
     */
    function finalize() public onlyOwner {
        _finalize();
    }

    function _calcPhase() internal view returns (int8) {
        if (phase_i == PHASE_FINISHED || phase_i == PHASE_NOT_STARTED)
            return phase_i;
        uint curr_date = now;
        if (curr_date >= ico_phase_5_end || token.balanceOf(base_wallet) == 0)
            return PHASE_ICO_FINISHED;
        if (curr_date < presale_start)
            return PHASE_BEFORE_PRESALE;
        if (curr_date <= presale_end)
            return PHASE_PRESALE;
        if (curr_date < ico_start)
            return PHASE_BETWEEN_PRESALE_ICO;
        if (curr_date < ico_phase_1_end)
            return PHASE_ICO_1;
        if (curr_date < ico_phase_2_end)
            return PHASE_ICO_2;
        if (curr_date < ico_phase_3_end)
            return PHASE_ICO_3;
        if (curr_date < ico_phase_4_end)
            return PHASE_ICO_4;
        return PHASE_ICO_5;
    }

    function phase() public view returns (int8) {
        return _calcPhase();
    }

    /**
     * @dev Recalculate phase
     */
    function _updatePhase(bool check_can_sale) internal {
        uint curr_date = now;
        if (phase_i == PHASE_ICO_FINISHED) {
            if (curr_date >= finish_date)
                _finalize();
        }
        else
            if (phase_i != PHASE_NOT_STARTED && phase_i != PHASE_FINISHED) {
                int8 new_phase = _calcPhase();
                if (new_phase == PHASE_ICO_FINISHED && phase_i != PHASE_ICO_FINISHED)
                    _finalizeICO();
                else
                    phase_i = new_phase;
            }
        if (check_can_sale)
            assert(phase_i >= 0);
    }

    /**
     * @dev Update phase end times
     */
    function _updatePhaseTimes() internal {
        assert(phase_i != PHASE_NOT_STARTED && phase_i != PHASE_FINISHED);
        if (phase_i < PHASE_ICO_1)
            ico_phase_1_end = ico_start.add(SECONDS_IN_DAY.mul(ico_phase_1_days));
        if (phase_i < PHASE_ICO_2)
            ico_phase_2_end = ico_phase_1_end.add(SECONDS_IN_DAY.mul(ico_phase_2_days));
        if (phase_i < PHASE_ICO_3)
            ico_phase_3_end = ico_phase_2_end.add(SECONDS_IN_DAY.mul(ico_phase_3_days));
        if (phase_i < PHASE_ICO_4)
            ico_phase_4_end = ico_phase_3_end.add(SECONDS_IN_DAY.mul(ico_phase_4_days));
        if (phase_i < PHASE_ICO_5)
            ico_phase_5_end = ico_phase_4_end.add(SECONDS_IN_DAY.mul(ico_phase_5_days));
        if (phase_i != PHASE_ICO_FINISHED)
            finish_date = ico_phase_5_end.add(SECONDS_IN_DAY.mul(10));
        _updatePhase(false);
    }

    /**
     * @dev Send tokens to the specified address
     *
     * @param _to Address sent to
     * @param _amount_coin Amount of tockens
     * @return excess coins
     *
     * executed by CRM
     */
    function transferICO(address _to, uint256 _amount_coin) public onlyOwnerOrManager {
        _updatePhase(true);
        uint256 remainedCoin = token.balanceOf(base_wallet);
        require(remainedCoin >= _amount_coin, "Not enough coins");
        token.transferICO(_to, _amount_coin);
        if (remainedCoin == _amount_coin)
            _finalizeICO();
    }

    /**
     * @dev Default contract function. Buy tokens by sending ethereums
     */
    function() public payable {
        _updatePhase(true);
        address sender = msg.sender;
        uint256 amountEth = msg.value;
        uint256 remainedCoin = token.balanceOf(base_wallet);
        if (remainedCoin == 0) {
            sender.transfer(amountEth);
            _finalizeICO();
        } else {
            uint8 percent = bonus_percents[uint256(phase_i)];
            uint256 amountCoin = calcTokensFromEth(amountEth);
            assert(amountCoin >= MIN_TOKEN_AMOUNT);
            if (amountCoin > remainedCoin) {
                lastPayerOverflow = amountCoin.sub(remainedCoin);
                amountCoin = remainedCoin;
            }
            base_wallet.transfer(amountEth);
            token.transferICO(sender, amountCoin);
            _addPayment(sender, amountEth, amountCoin, percent);
            if (amountCoin == remainedCoin)
                _finalizeICO();
        }
    }

    function calcTokensFromEth(uint256 ethAmount) internal view returns (uint256) {
        uint8 percent = bonus_percents[uint256(phase_i)];
        uint256 bonusRate = uint256(percent).add(100);
        uint256 totalCoins = ethAmount.mul(exchange_rate).div(1000);
        uint256 totalFullCoins = (totalCoins.add(dec_mul.div(2))).div(dec_mul);
        uint256 tokensWithBonusX100 = bonusRate.mul(totalFullCoins);
        uint256 fullCoins = (tokensWithBonusX100.add(50)).div(100);
        return fullCoins.mul(dec_mul);
    }

    /**
     * @dev Freeze the account
     * @param _accounts Given accounts
     *
     * executed by CRM
     */
    function freeze(address[] _accounts) public onlyOwnerOrManager {
        assert(phase_i != PHASE_NOT_STARTED && phase_i != PHASE_FINISHED);
        uint i;
        for (i = 0; i < _accounts.length; i++) {
            require(_accounts[i] != address(0), "Zero address");
            require(_accounts[i] != base_wallet, "Freeze self");
        }
        for (i = 0; i < _accounts.length; i++) {
            token.freeze(_accounts[i]);
        }
    }

    /**
     * @dev Unfreeze the account
     * @param _accounts Given accounts
     */
    function unfreeze(address[] _accounts) public onlyOwnerOrManager {
        assert(phase_i != PHASE_NOT_STARTED && phase_i != PHASE_FINISHED);
        uint i;
        for (i = 0; i < _accounts.length; i++) {
            require(_accounts[i] != address(0), "Zero address");
            require(_accounts[i] != base_wallet, "Freeze self");
        }
        for (i = 0; i < _accounts.length; i++) {
            token.unfreeze(_accounts[i]);
        }
    }

    /**
     * @dev get ICO times
     * @return presale_start, presale_end, ico_start, ico_phase_1_end, ico_phase_2_end, ico_phase_3_end, ico_phase_4_end, ico_phase_5_end
     */
    function getTimes() public view returns (uint, uint, uint, uint, uint, uint, uint, uint) {
        return (presale_start, presale_end, ico_start, ico_phase_1_end, ico_phase_2_end, ico_phase_3_end, ico_phase_4_end, ico_phase_5_end);
    }

    /**
     * @dev Sets start and end dates for pre-sale phase_i
     * @param _presale_start Pre-sale sart date
     * @param _presale_end Pre-sale end date
     */
    function setPresaleDates(uint _presale_start, uint _presale_end) public onlyOwnerOrManager {
        _updatePhase(false);
        assert(phase_i == PHASE_BEFORE_PRESALE);
        // require(_presale_start >= now);
        require(_presale_start < _presale_end);
        require(_presale_end < ico_start);
        presale_start = _presale_start;
        presale_end = _presale_end;
    }

    /**
     * @dev Sets start date for ICO phases
     * @param _ico_start ICO start date
     * @param _ico_1_days Days of ICO phase 1
     * @param _ico_2_days Days of ICO phase 2
     * @param _ico_3_days Days of ICO phase 3
     * @param _ico_4_days Days of ICO phase 4
     * @param _ico_5_days Days of ICO phase 5
     */
    function setICODates(uint _ico_start, uint _ico_1_days, uint _ico_2_days, uint _ico_3_days, uint _ico_4_days, uint _ico_5_days) public onlyOwnerOrManager {
        _updatePhase(false);
        assert(phase_i != PHASE_FINISHED && phase_i != PHASE_ICO_FINISHED && phase_i < PHASE_ICO_1);
        require(presale_end < _ico_start);
        ico_start = _ico_start;
        ico_phase_1_days = _ico_1_days;
        ico_phase_2_days = _ico_2_days;
        ico_phase_3_days = _ico_3_days;
        ico_phase_4_days = _ico_4_days;
        ico_phase_5_days = _ico_5_days;
        _updatePhaseTimes();
    }
}
/**
 * @title Crowd-sale
 *
 * @dev Crowd-sale contract for tokens
 */
function _updatePhaseTimes() internal {
    assert(phase_i != PHASE_NOT_STARTED && phase_i != PHASE_FINISHED);
    if (phase_i < PHASE_ICO_1)
        ico_phase_1_end = ico_start.add(SECONDS_IN_DAY.mul(ico_phase_1_days));
    if (phase_i < PHASE_ICO_2)
        ico_phase_2_end = ico_phase_1_end.add(SECONDS_IN_DAY.mul(ico_phase_2_days));
    if (phase_i < PHASE_ICO_3)
        ico_phase_3_end = ico_phase_2_end.add(SECONDS_IN_DAY.mul(ico_phase_3_days));
    if (phase_i < PHASE_ICO_4)
        ico_phase_4_end = ico_phase_3_end.add(SECONDS_IN_DAY.mul(ico_phase_4_days));
    if (phase_i < PHASE_ICO_5)
        ico_phase_5_end = ico_phase_4_end.add(SECONDS_IN_DAY.mul(ico_phase_5_days));
    if (phase_i != PHASE_ICO_FINISHED)
        finish_date = ico_phase_5_end.add(SECONDS_IN_DAY.mul(10));
    _updatePhase(false);
}

/**
 * @dev Update phase end times
 */