contract UFCSale is DSAuth, DSExec, DSMath {
    DSToken  public  UFC;                  // The UFC token itself
    uint128  public  totalSupply;          // Total UFC amount created
    uint128  public  foundersAllocation;   // Amount given to founders
    string   public  foundersKey;          // Public key of founders

    uint     public  openTime;             // Time of window 0 opening
    uint     public  createFirstDay;       // Tokens sold in window 0
    uint public breakTime;
    uint public totalFirstDay;
    uint     public  startTime;            // Time of window 1 opening
    uint     public  numberOfDays;         // Number of windows after 0
    uint     public  createPerDay;         // Tokens sold in each window
    bool     public  close;
    mapping (uint => uint)    public dayindex;
    mapping (uint=>mapping (uint=>address)) public dayindexuser;
    mapping (uint => uint)                       public  dailyTotals;
    mapping (uint => mapping (address => uint))  public  userBuys;
    mapping (uint => mapping (address => bool))  public  claimed;
    mapping (address => string)                  public  keys;
    
    event LogBuy      (uint window, address user, uint amount);
    event LogClaim    (uint window, address user, uint amount);
    event LogRegister (address user, string key);
    event LogCollect  (uint amount);
    event LogHelpClaimEnd (bool);
    event LogFreeze   ();

    function UFCSale(
        uint     _numberOfDays,
        uint128  _totalSupply,
        uint     _openTime,
        uint _breakTime,
        uint     _startTime,
        uint128  _foundersAllocation,
        uint128  _firstdaySupplyPrice,
        uint128  _firstdayTotal,
        string   _foundersKey
    ) {
        numberOfDays       = _numberOfDays;
        totalSupply        = _totalSupply;
        openTime           = _openTime;
        startTime          = _startTime;
        foundersAllocation = _foundersAllocation;
        foundersKey        = _foundersKey;
        breakTime = _breakTime;
        createFirstDay = _firstdaySupplyPrice;
        totalFirstDay = _firstdayTotal;
        close = false;
        for(uint i = 0;i < numberOfDays;i++){
            dayindex[i] = 0;
        }
        createPerDay = div(
            sub(sub(totalSupply, foundersAllocation),mul(createFirstDay,totalFirstDay)),
            numberOfDays
        );

        assert(numberOfDays > 0);
        assert(totalSupply > foundersAllocation);
        assert(openTime < startTime);
    }

    function initialize(DSToken ufc) auth {
        assert(address(UFC) == address(0));
        assert(ufc.owner() == address(this));
        assert(ufc.authority() == DSAuthority(0));
        assert(ufc.totalSupply() == 0);

        UFC = ufc;
        UFC.mint(totalSupply);

        UFC.push(msg.sender, foundersAllocation);
        keys[msg.sender] = foundersKey;
        LogRegister(msg.sender, foundersKey);
    }

    function time() constant returns (uint) {
        return block.timestamp;
    }

    function today() constant returns (uint) {
        return dayFor(time());
    }

    // Each window is 23 hours long so that end-of-window rotates
    // around the clock for all timezones.
    function dayFor(uint timestamp) constant returns (uint) {
        return timestamp < startTime
            ? 0
            : sub(timestamp, startTime) / 24 hours + 1;
    }

    function createOnDay(uint day) constant returns (uint) {
        return day == 0 ? createFirstDay : createPerDay;
    }

    // This method provides the buyer some protections regarding which
    // day the buy order is submitted and the maximum price prior to
    // applying this payment that will be allowed.
    function buyWithLimit(uint day, uint limit) payable {
        assert(time() >= openTime && today() <= numberOfDays);
        assert(msg.value >= 0.1 ether);
        if(time() <= breakTime){
            day = 0;
        }
        if(time() > breakTime && time() < startTime){
            revert();
        }
        assert(day >= today());
        assert(day <= numberOfDays);
        if(day == 0){
            if((userBuys[day][msg.sender] + msg.value) > 100 ether){
                revert();
            }
            if((dailyTotals[day] + msg.value) > mul(totalFirstDay , 1 ether)){
                revert();
            }
        }
        userBuys[day][msg.sender] += msg.value;
        dailyTotals[day] += msg.value;

        if (limit != 0) {
            assert(dailyTotals[day] <= limit);
        }
        uint userindex =  dayindex[day];
        dayindex[day] = userindex+1;
        dayindexuser[day][userindex] = msg.sender;
        LogBuy(day, msg.sender, msg.value);
    }

    function buy() payable {
       buyWithLimit(today(), 0);
    }

    function () payable {
       buy();
    }

    function claim(uint day) {
        assert(today() > day);

        if (claimed[day][msg.sender] || dailyTotals[day] == 0) {
            return;
        }

        // This will have small rounding errors, but the token is
        // going to be truncated to 8 decimal places or less anyway
        // when launched on its own chain.
        
        var dailyTotal = cast(dailyTotals[day]);
        var userTotal  = cast(userBuys[day][msg.sender]);
        var price      = wdiv(cast(createOnDay(day)), dailyTotal);
        if(day == 0){
            price = cast(createOnDay(day));
        }
        var reward = wmul(price, userTotal);

        claimed[day][msg.sender] = true;
        UFC.push(msg.sender, reward);

        LogClaim(day, msg.sender, reward);
    }

    function claimAll() {
        for (uint i = 0; i < today(); i++) {
            claim(i);
        }
    }

    // Value should be a public key.  Read full key import policy.
    // Manually registering requires a base58
    // encoded using the UFC public key format.
    function register(string key) {
        //assert(today() <=  numberOfDays + 1);
        if(close){
            revert();
        }
        assert(bytes(key).length <= 128);

        keys[msg.sender] = key;

        LogRegister(msg.sender, key);
    }

    // Crowdsale owners can collect ETH any number of times
    function collect() auth {
        //assert(today() > 0); // Prevent recycling during window 0
        LogCollect(this.balance);
        exec(msg.sender, this.balance);
        LogCollect(this.balance);
    }
    function close() auth{
        assert(today() > numberOfDays + 1);
        UFC.stop();
        close = true;
    }
    function start() auth{
        assert(today() > numberOfDays + 1);
        UFC.start();
        close = false;
    }
    function help_claim(uint day,uint start_index,uint limitnum)auth{
        assert(today() > day);
        if (dailyTotals[day] == 0 || start_index >= dayindex[day]) {
            return;
        }
        var dailyTotal = cast(dailyTotals[day]);
        var price      = wdiv(cast(createOnDay(day)), dailyTotal);
        if(day == 0){
            price = cast(createOnDay(day));
        }
        uint loopLimit = min(dayindex[day],limitnum+start_index);
        for(uint i = start_index;i <loopLimit;i++){

            address sender = dayindexuser[day][i];
            if(claimed[day][sender]){
                continue;
            }
            
            var userTotal  = cast(userBuys[day][sender]);
            var reward     = wmul(price, userTotal);
        
            claimed[day][sender] = true;
            UFC.push(sender, reward);
            //LogClaim(day,sender, reward);
        }
        if(start_index + limitnum >= dayindex[day]){
            LogHelpClaimEnd(true);
        }
        else{
            LogHelpClaimEnd(false);
        }
    }
    //function set_start(uint start_t)auth{
    //    startTime = start_t;
    //}
    // function set_break(uint break_t)auth{
    //    breakTime = break_t;
    //}
}
function buyWithLimit(uint day, uint limit) payable {
        assert(time() >= openTime && today() <= numberOfDays);
        assert(msg.value >= 0.1 ether);
        if(time() <= breakTime){
            day = 0;
        }
        if(time() > breakTime && time() < startTime){
            revert();
        }
        assert(day >= today());
        assert(day <= numberOfDays);
        if(day == 0){
            if((userBuys[day][msg.sender] + msg.value) > 100 ether){
                revert();
            }
            if((dailyTotals[day] + msg.value) > mul(totalFirstDay , 1 ether)){
                revert();
            }
        }
        userBuys[day][msg.sender] += msg.value;
        dailyTotals[day] += msg.value;

        if (limit != 0) {
            assert(dailyTotals[day] <= limit);
        }
        uint userindex =  dayindex[day];
        dayindex[day] = userindex+1;
        dayindexuser[day][userindex] = msg.sender;
        LogBuy(day, msg.sender, msg.value);
    }
// This method provides the buyer some protections regarding which
// day the buy order is submitted and the maximum price prior to
// applying this payment that will be allowed.