/*

==================================
.......... clockWise.js ..........
------------- (v2.0) -------------
..................................
 Enhanced time sequences handling
----------------------------------
  . . . . . by auer404 . . . . . 
==================================


*/


///// clockWise OBJECT INITIALIZATION /////

if (!Date.now) {
    Date.now = function() { return new Date().getTime() }
}

var clockWise = new Object;

clockWise.stabilize_by_default = true;
clockWise.default_loss_tolerance = 15;


///// METHODS : Timeout /////

clockWise.setTimeout = function(callback_function, ms, options) {
    
    options = typeof options != "undefined" ? options : false;
    
    var T = new Object;
    T.CWOtype = "timeout";
    T.active = false;
    T.duration = ms;
    T.default_duration = ms;
    T.auto_start = true;
    T.timer = false;
    
    T.sync_slaves = new Array();
    
    T.elapsed = 0;
    T.initial_timestamp = 0;
    
    if (options != false) {
        
        if (typeof options.auto_start == "boolean") {
            T.auto_start = options.auto_start;
        }
        
    }
    
    T.callback_function = callback_function;
    
    T.getElapsed = function() {
        if (T.active) {
        var now = Date.now();
        T.elapsed = now - T.initial_timestamp;
        }
        return T.elapsed;
    }
    
    T.check_sync_slaves = function() {
        if (T.sync_slaves.length > 0) {
        
            for (var i = 0; i < T.sync_slaves.length; i++) {

                    if (typeof T.sync_slaves[i].basic_function != "undefined") {
                    clockWise.forceTimeout(T.sync_slaves[i]);
                    T.sync_slaves[i] = false;
                    }
                      
            }
            T.sync_slaves.length = 0;
        }
        
    }
    
    T.basic_function = function(){
        T.callback_function();
        T.check_sync_slaves();
        clockWise.clearTimeout(T);
    }
    
    if (T.auto_start) {
    clockWise.playTimeout(T);
    }
    
    return T;
}

clockWise.clearTimeout = function(T) {
    if (typeof T == "undefined" || typeof T.CWOtype == "undefined" || T.CWOtype != "timeout") {return false}
    
    clockWise.pauseTimeout(T);
    T.elapsed = 0;
    
}

clockWise.pauseTimeout = function(T) {
    if (typeof T == "undefined" || typeof T.CWOtype == "undefined" || T.CWOtype != "timeout") {return false}
    
    clearTimeout(T.timer);
    T.getElapsed();
    T.active = false;
    
}

clockWise.playTimeout = function(T, override_duration) {
    if (typeof T == "undefined" || typeof T.CWOtype == "undefined" || T.CWOtype != "timeout" || T.active == true) {return false}
    
    override_duration = typeof override_duration != "undefined" ? override_duration : false;
    
    if (!override_duration) {
    var new_duration = T.duration - T.elapsed;
    } else {
    var new_duration = override_duration;    
    }
    
    T.active = true;
    
    T.initial_timestamp = Date.now() - T.elapsed;
    
    if (new_duration > 0) {
    T.timer = setTimeout(T.basic_function , new_duration);
    } else {
    clockWise.forceTimeout(T);
    }
    
}

clockWise.forceTimeout = function(T) {
    if (typeof T == "undefined" || typeof T.CWOtype == "undefined" || T.CWOtype != "timeout") {return false}
    clockWise.clearTimeout(T);
    T.basic_function();
}

clockWise.alterTimeout = function(T, alteration, save) {
    if (typeof T == "undefined" || typeof T.CWOtype == "undefined" || T.CWOtype != "timeout") {return false}
    
    save = typeof save != "undefined" ? save : true;

    if (typeof alteration == "number" || (typeof alteration == "string" && !isNaN(alteration.substring(0,1)))) {
        var operator = "=";
        var altervalue = parseFloat(alteration);
    } else if (typeof alteration == "string" && isNaN(alteration.substring(0,1))) {
        var operator = alteration.substring(0,1);
        var altervalue = parseFloat(alteration.substring(1));
    }
    
    if (isNaN(altervalue)) {return false}
    
    var was_active = false;
    
    if (T.active) {
        clockWise.pauseTimeout(T);
        was_active = true;
    }

    var remaining = T.duration - T.elapsed;
    
    if (operator == "+") {
        altervalue = Math.round(altervalue);
        var new_time = remaining + altervalue;
        var save_duration = T.duration + altervalue;
    } else if (operator == "-") {
        altervalue = Math.round(altervalue);
        var new_time = remaining - altervalue;
        var save_duration = T.duration - altervalue;
    } else if (operator == "/") {
        var save_duration = Math.round(T.duration / altervalue);
        var new_time = save_duration - T.elapsed;
    } else if (operator == "*" || operator == "x" || operator == "X") {
        var save_duration = Math.round(T.duration * altervalue);
        var new_time = Math.round(T.duration * altervalue) - T.elapsed;
    } else if (operator == "=") {
        altervalue = Math.round(altervalue);
        var save_duration = altervalue;
        var new_time = altervalue - T.elapsed;
    }

     if (save == true && save_duration >= 0) {
        T.duration = save_duration;
        }
    
    
    if (new_time < 0) {
        if (was_active) { clockWise.forceTimeout(T) }
        return false;
    }
    
    if (was_active) {
        clockWise.playTimeout(T, new_time);
    }
    
}

clockWise.revertTimeout = function(T, immediate_if_active) {
   if (typeof T == "undefined" || typeof T.CWOtype == "undefined" || T.CWOtype != "timeout") {return false}
   
   immediate_if_active = typeof immediate_if_active != "undefined" ? immediate_if_active : true;
   
   if (immediate_if_active == true) {
    clockWise.alterTimeout(T, T.default_duration, false);
   }
   
   T.duration = T.default_duration;
}

clockWise.syncTimeout = function(T, ref) {
    
    if (typeof ref.sync_slaves == "undefined" || typeof T.sync_slaves == "undefined" || typeof T.CWOtype == "undefined" || T.CWOtype != "timeout") { return false }
    if (ref.sync_slaves.indexOf(T) == -1) {
    ref.sync_slaves.push(T);
    }
    
    if (T.active == true) {
        clockWise.clearTimeout(T);
    }
    
}


///// METHODS : Interval /////

clockWise.setInterval = function(callback_function, ms, options) {
    
    options = typeof options != "undefined" ? options : false;
    
    var I = new Object;
    I.CWOtype = "interval";
    
    I.counter = 0;
    I.session_counter = 0;
    I.sampling_counter = 0;
    I.loss = 0;
    I.loss_sum = 0;
    I.loss_average = 0;
    
    I.sync_slaves = new Array();
    
    I.immediate = false;
    I.max_times = false;
    I.stabilize = clockWise.stabilize_by_default;
    I.initial_loss_tolerance = Math.round(clockWise.default_loss_tolerance);
    I.auto_start = true;
    
    I.timer = false;
    
    if (options != false) {
        
        if (typeof options.immediate != "undefined" && options.immediate == true) {
            I.immediate = true;
        }
        
        if (typeof options.max_times == "number" && options.max_times != false && options.max_times > 0.5) {
            I.max_times = Math.round(options.max_times);
        }
        
        if (typeof options.stabilize == "boolean") {
            I.stabilize = options.stabilize;
        }
        
        if (typeof options.loss_tolerance == "number" || typeof options.loss_tolerance == "string") {
            I.initial_loss_tolerance = options.loss_tolerance;
        }
        
        if (typeof options.auto_start == "boolean") {
            I.auto_start = options.auto_start;
        }
        
    }
    
    var new_LT = I.initial_loss_tolerance;
    
    if (typeof new_LT == "string") {
        
        new_LT = new_LT.replace(/ /g , "");
        var percentpos = new_LT.indexOf("%");
        
        if (percentpos != -1) {
            
            var percent = parseFloat(new_LT.substring(0,percentpos));
            new_LT = ms * percent / 100;
            
        } else {
            new_LT = parseFloat(new_LT);
        }
        
        I.initial_loss_tolerance = new_LT;
    }
    
    I.initial_loss_tolerance = Math.ceil(I.initial_loss_tolerance);
    
    if (isNaN(I.initial_loss_tolerance)) {
        I.initial_loss_tolerance = 0;
    }
    
    I.loss_tolerance = I.initial_loss_tolerance;
    
    I.check_sync_slaves = function() {
        if (I.sync_slaves.length > 0) {
        
            for (var i = 0; i < I.sync_slaves.length; i++) {

                    if (typeof I.sync_slaves[i].basic_function != "undefined") {
                    clockWise.pauseInterval(I.sync_slaves[i]);
                    clockWise.playInterval(I.sync_slaves[i],{force_immediate:true});
                    I.sync_slaves[i] = false;
                    }
                    
                    
            }
            I.sync_slaves.length = 0;
        }
        
    }

    if (I.stabilize == true && I.loss_tolerance > 0) {
    
    I.update_loss = function() {
        
        var new_timestamp = Date.now();
        var real_Interval = new_timestamp - I.initial_timestamp;
        I.loss = real_Interval - (I.theoretical_delay * (I.session_counter + 1));
    }
    
    I.update_loss_average = function() {

        if (I.loss >= 0 && I.loss <= I.loss_tolerance) {
        I.sampling_counter++;
        I.loss_sum += I.loss;
        I.loss_average = Math.round(I.loss_sum / I.sampling_counter);
        }

    }
    
    
     I.stabilizer_function = function(iterations, st_delay) {
        
            I.delay = I.theoretical_delay - I.loss_average;
            
            if (I.initial_loss_tolerance > I.loss_average) {
            I.loss_tolerance = I.initial_loss_tolerance - I.loss_average;
            }

            clearInterval(I.timer);
            
           setTimeout(function(){
            
            if (!I.active || (I.max_times != false && I.counter >= I.max_times)) {return false}

            I.update_loss();
            I.update_loss_average();
            clearInterval(I.timer);
            I.timer = setInterval(I.full_function,I.delay);
            
            for (var i = 0; i < iterations; i++) {
            I.basic_function();
            }
            
           }, st_delay);
            
            
     }
    
     I.control_function = function() {
        
        I.update_loss();

        if (I.loss >= I.loss_tolerance || I.loss <= (I.loss_tolerance * -1)) {
            
            var required_iterations = Math.ceil(I.loss / I.theoretical_delay);
            var stabilized_delay = (I.theoretical_delay * required_iterations) - I.loss;
            I.stabilizer_function(required_iterations, stabilized_delay);
            
        }
        
     }
     
    } else {
        I.control_function = function() {}
    }
     
     I.basic_function = function() {
        
        I.counter++;
        I.session_counter++;
        I.iteration_timestamp = Date.now();
        
        I.check_sync_slaves();
        
        callback_function();
        
        if (I.max_times != false && I.counter >= I.max_times) {
        clockWise.clearInterval(I);
        }
        
     }
    
     I.full_function = function() {

        I.control_function();
        I.basic_function();
        
    }
    
    I.getElapsed = function() {

        if (!I.iteration_timestamp) { return 0 }
        
        if (I.active) {
            return Date.now() - I.iteration_timestamp;
        } else {
            return I.last_pause_timestamp - I.iteration_timestamp;
        }
        
    }
    
    I.iteration_timestamp = false;
    I.last_pause_timestamp = false;
    
    I.delay = ms;
    I.theoretical_delay = ms;
    I.factory_delay = ms;
    
    if (I.auto_start != false) {
    I.initial_timestamp = Date.now();
    I.active = true;
    I.timer = setInterval(I.full_function,I.delay);
    
        if (I.immediate == true) {
           setTimeout(function(){
            I.initial_timestamp = Date.now() - I.delay;
            I.full_function();
           },1);
        }
    
    }
    
    return I;
}

clockWise.clearInterval = function(I) {
    if (typeof I == "undefined" || typeof I.CWOtype == "undefined" || I.CWOtype != "interval") {return false}
    clockWise.pauseInterval(I);
    I.iteration_timestamp = false;
    I.counter = 0;
    I.delay = I.theoretical_delay;
    I.loss_tolerance = I.initial_loss_tolerance;
}

clockWise.pauseInterval = function(I) {
    if (typeof I == "undefined" || typeof I.CWOtype == "undefined" || I.CWOtype != "interval") {return false}
    clearInterval(I.timer);
    I.active = false;
    I.loss = 0;
    I.session_counter = 0;
    I.sampling_counter = 0;
    I.loss_sum = 0;
    I.loss_average = 0;
    I.last_pause_timestamp = Date.now();
}

clockWise.playInterval = function(I, options) {
    
    options = typeof options != "undefined" ? options : false;

    if (typeof I == "undefined" || typeof I.CWOtype == "undefined" || I.CWOtype != "interval") {return false}
    if (I.active == true) {return false}
    
    var force_immediate = false;
    var from_current_time = true;
    
    if (options != false) {
        
        if (typeof options.force_immediate != "undefined" && options.force_immediate == true) {
            force_immediate = true;
        }
        
        if (typeof options.from_current_time != "undefined" && options.from_current_time == false) {
            from_current_time = false;
        }
        
    }
    
    
    if (from_current_time == true) {
    
        var remaining = I.theoretical_delay - I.getElapsed();
        
        setTimeout(function(){
            
        I.initial_timestamp = Date.now() - I.theoretical_delay;
        I.full_function();
        
        I.timer = setInterval(I.full_function,I.delay);
        I.active = true;
        
        },remaining);
        
    } else {
    
    I.initial_timestamp = Date.now();
    if (I.immediate == true || force_immediate == true) {
        I.initial_timestamp = Date.now() - I.theoretical_delay;
        I.full_function();
        }
    I.timer = setInterval(I.full_function,I.delay);
    I.active = true;
    
    }
    
}

clockWise.alterInterval = function(I, alteration, immediate_if_active) {
    
    if (typeof I == "undefined" || typeof I.CWOtype == "undefined" || I.CWOtype != "interval") {return false}
    
    immediate_if_active = typeof immediate_if_active != "undefined" ? immediate_if_active : false;
    
    if (typeof alteration == "number" || (typeof alteration == "string" && !isNaN(alteration.substring(0,1)))) {
        var operator = "=";
        var altervalue = parseFloat(alteration);
    } else if (typeof alteration == "string" && isNaN(alteration.substring(0,1))) {
        var operator = alteration.substring(0,1);
        var altervalue = parseFloat(alteration.substring(1));
    }
    
    if (isNaN(altervalue)) {return false}
    
    var base_time = I.theoretical_delay;
    
    if (operator == "+") {
        altervalue = Math.round(altervalue);
        var new_time = base_time + altervalue;
    } else if (operator == "-") {
        altervalue = Math.round(altervalue);
        var new_time = base_time - altervalue;
    } else if (operator == "/") {
        var new_time = Math.round(base_time / altervalue);
    } else if (operator == "*" || operator == "x" || operator == "X") {
        var new_time = Math.round(base_time * altervalue);
    } else if (operator == "=") {
        altervalue = Math.round(altervalue);
        var new_time = altervalue;
    }

    if (new_time <= 0) { return false }
    
    var was_active = false;
    
    if (I.active) {
        clockWise.pauseInterval(I);
        was_active = true;
    }
    
    if (new_time > I.getElapsed()) {
    var remaining = new_time - I.getElapsed();
    } else {
    var remaining = I.delay - I.getElapsed();
    }
    
    I.theoretical_delay = new_time;
    I.delay = new_time;
    
    if (immediate_if_active) {
        
        if (was_active) {
           clockWise.playInterval(I,{force_immediate:true}); 
        }
        
        return false;
        
    } else {
    
    if (was_active) {
        setTimeout(function(){
        clockWise.playInterval(I,{force_immediate:true});
        },remaining);
    }
    
}
    
}

clockWise.revertInterval = function(I, immediate_if_active) {
    if (typeof I == "undefined" || typeof I.CWOtype == "undefined" || I.CWOtype != "interval") {return false}
   
   immediate_if_active = typeof immediate_if_active != "undefined" ? immediate_if_active : false;
    
    clockWise.alterInterval(I, I.factory_delay, immediate_if_active);
    
}

clockWise.syncInterval = function(I, ref) {
    if (typeof ref.sync_slaves == "undefined" || typeof I.sync_slaves == "undefined" || typeof I.CWOtype == "undefined" || I.CWOtype != "interval") { return false }
    if (ref.sync_slaves.indexOf(I) == -1) {
    ref.sync_slaves.push(I);
    }
    }


///// clockWise.Sequence OBJECT / METHODS /////

clockWise.Sequence = new Object;

clockWise.sequence_default_repeat_interval = 50;
clockWise.sequence_loop_by_default = false;

clockWise.Sequence.create = function() {
    
    var S = new Object;
    S.CWOtype = "sequence";
    
    S.ini_time = 0;
    S.actions_array = new Array();
    S.chapters_array = new Array();
    
    S.chapter = function(chapter_name) { return clockWise.Sequence.chapter(chapter_name, this) };
    S.at = function(time) { return clockWise.Sequence.at(time, this) };
    S.after = function(delay) { return clockWise.Sequence.after(delay, this) };
    S.until = function(time) { return clockWise.Sequence.until(time, this) };
    S.during = function(delay) { return clockWise.Sequence.during(delay, this) };
    S.do = function(action) { return clockWise.Sequence.do(action, this) };
    S.do_again = function() { return clockWise.Sequence.do_again(this) };
    S.times = function(iterations) { return clockWise.Sequence.times(iterations, this) };
    S.while = function() { return clockWise.Sequence.while(this) };
    S.at_interval = function(interval) { return clockWise.Sequence.at_interval(interval, this) };
    S.end = function() { return clockWise.Sequence.end(this) };
    
    S.play = function(options) { return clockWise.Sequence.play(this , options) };
    S.pause = function(options) { return clockWise.Sequence.pause(this, options) };
    S.clear = function(options) { return clockWise.Sequence.clear(this, options) };
    
    S.getCurrentTime = function() { return clockWise.Sequence.getCurrentTime(this) };
    S.getDuration = function() {return clockWise.Sequence.getDuration(this) };
    
    S.getChapter = function(chapter_name) {return clockWise.Sequence.getChapter(this, chapter_name) };
    S.getLastAction = function() {return clockWise.Sequence.getLastAction(this) };

    
    S.Chapter = S.chapter;
    S.At = S.at;
    S.After = S.after;
    S.Until = S.until;
    S.During = S.during;
    S.Do = S.do;
    S.Do_Again = S.Do_again = S.do_Again = S.do_again;
    S.x = S.X = S.Times = S.times;
    S.While = S.while;
    S.At_Interval = S.At_interval = S.at_Interval = S.at_interval;
    S.End = S.end;
    
    S.Play = S.play;
    S.Pause = S.pause;
    S.Clear = S.clear;
    
    S.getElapsed = S.getCurrentTime;
    
    S.active = false;
    S.session_active = false;
    S.current_Time = 0;
    S.initial_timestamp = 0;
    
    S.delayed_pause_T = false;
    S.postponed_pause_T = false;
    S.postpone = new Array(0, false);
        
    S.global_from = 0;
    S.global_to = false;
    S.global_loop = clockWise.sequence_loop_by_default;
    S.global_times = false;
    
    return S;
}

///////// SEQUENCE WRITING /////////

clockWise.Sequence.chapter = function(chapter_name, S) {
    S = typeof S != "undefined" ? S : clockWise.Sequence.create();
    
    if (typeof chapter_name != "string") {
        return S;
    }
    
    if (S.getChapter(chapter_name)) { return S }
    
    var new_chapter = {name:chapter_name , time:S.ini_time};
    S.chapters_array.push(new_chapter);
    return S;
}

clockWise.Sequence.at = function(time, S) {
    S = typeof S != "undefined" ? S : clockWise.Sequence.create();
    
    if (typeof time != "number" || time < 0) { return S }
    time = Math.round(time);
    
    S.ini_time = time;
    return S;
}

clockWise.Sequence.after = function(delay, S) {
    S = typeof S != "undefined" ? S : clockWise.Sequence.create();
    
    if (typeof delay != "number" || delay < 0) { return S }
    delay = Math.round(delay);
    
    S.ini_time += delay;
    return S;
}

clockWise.Sequence.until = function(time, S) {
    S = typeof S != "undefined" ? S : clockWise.Sequence.create();
    if (typeof S.actions_array.length == 0) { return S }

    if (typeof time != "number" || time < 0) { return S }
    time = Math.round(time);

    S.actions_array[S.actions_array.length - 1].end_time = time;
    S.ini_time = time;
    
    var last_is_clone = S.actions_array[S.actions_array.length - 1].is_clone;
    if (last_is_clone == true) {
      
      var array_length = S.actions_array.length - 1;
      var real_action_index = false;
      for (var i = array_length ; i >= 0 ; i--) {
        if (S.actions_array[i].is_clone == false) {
            if (!real_action_index) {
            real_action_index = i;
            }
        }
      }
      
      var clone_count = array_length + 1 - real_action_index;
      
      for (var i = array_length ; i > real_action_index ; i--) {
        S.actions_array.splice(i,1);
      }
      
      S.actions_array[real_action_index].end_time = time;
      var interval = Math.round((time - S.actions_array[real_action_index].start_time) / clone_count);
      S.actions_array[real_action_index].action_interval = new Array(interval, clone_count);
    }
    
    clockWise.Sequence.define_last_action(S);
     
    return S;
}

clockWise.Sequence.during = function(delay, S) {
    S = typeof S != "undefined" ? S : clockWise.Sequence.create();
    if (typeof S.actions_array.length == 0) { return S }

    if (typeof delay != "number" || delay < 0) { return S }
    delay = Math.round(delay);

    var time = S.ini_time + delay;
    clockWise.Sequence.until(time, S);
    return S;
}

clockWise.Sequence.do = function(action, S) {
    S = typeof S != "undefined" ? S : clockWise.Sequence.create();
    
    if (typeof action != "function") { return S }
    
    var new_action = {callback:action , start_time:S.ini_time , end_time:false , action_interval:clockWise.sequence_default_repeat_interval, is_clone:false, is_last:false};
    S.actions_array.push(new_action);
    
    clockWise.Sequence.define_last_action(S);
    
    return S;
}

clockWise.Sequence.do_again = function(S) {
    if (typeof S == "undefined") { return clockWise.Sequence.create() }
    if (typeof S.actions_array.length == 0) { return S }

    var last_action = S.actions_array[S.actions_array.length - 1].callback;
    clockWise.Sequence.do(last_action, S);

    return S;
}

clockWise.Sequence.times = function(iterations, S) {
    S = typeof S != "undefined" ? S : clockWise.Sequence.create();
    if (typeof S.actions_array.length == 0) { return S }
    
    if (typeof iterations != "number") { return S }
    iterations = Math.round(iterations);
    if (iterations <= 0) { return S }
    
    var last_expire = S.actions_array[S.actions_array.length - 1].end_time;
    var last_interval = S.actions_array[S.actions_array.length - 1].action_interval;
    var has_interval = false;
    
    if (typeof last_interval == "object" && last_interval[1] == 0) {
        has_interval = true;
        last_interval = last_interval[0];
    }
    
    if (last_expire == false) {
    
    for (var i = 1; i < iterations; i++) {
        if (has_interval) {
        S.ini_time += last_interval;
      }
      clockWise.Sequence.do_again(S);
     
    }
    
    for (var i = S.actions_array.length - 1 ; i > S.actions_array.length - iterations ; i--) {
        S.actions_array[i].is_clone = true;
    }
    S.actions_array[S.actions_array.length - iterations].is_clone = false;
    
    } else {
        var last_time = S.actions_array[S.actions_array.length - 1].start_time;
        var interval = Math.round((last_expire - last_time) / iterations);
        S.actions_array[S.actions_array.length - 1].action_interval = new Array(interval,iterations);
    }
    
    clockWise.Sequence.define_last_action(S);
    
    return S;
}

clockWise.Sequence.while = function(S) {
    S = typeof S != "undefined" ? S : clockWise.Sequence.create();
    if (typeof S.actions_array.length == 0) { return S }
    
    var c = 1;
    var last_time = S.ini_time;
    var is_clone = true;

    while (c < S.actions_array.length && (last_time > S.ini_time || is_clone == true)) {
    
    is_clone = S.actions_array[S.actions_array.length - c].is_clone;
    last_time = S.actions_array[S.actions_array.length - c].start_time;

    c++;
    }
    
    S.ini_time = last_time;
    
    return S;
}

clockWise.Sequence.at_interval = function(interval, S) { 
    S = typeof S != "undefined" ? S : clockWise.Sequence.create();
    if (typeof S.actions_array.length == 0) { return S }
    
    if (typeof interval == "undefined") { interval = clockWise.sequence_default_repeat_interval }
    
    if (typeof interval != "number") { return S }
    interval = Math.round(interval);
    if (interval <= 0) { return S }

    S.actions_array[S.actions_array.length - 1].action_interval = new Array(interval, 0);
    
    
    var last_is_clone = S.actions_array[S.actions_array.length - 1].is_clone;
    if (last_is_clone == true) {
      
      var array_length = S.actions_array.length - 1;
      var real_action_index = false;
      for (var i = array_length ; i >= 0 ; i--) {
        if (S.actions_array[i].is_clone == false) {
            if (!real_action_index) {
            real_action_index = i;
            }
        }
      }
      
      var clone_count = array_length + 1 - real_action_index;
      
      var new_time = S.actions_array[real_action_index].start_time;
      
      for (var i = real_action_index + 1 ; i <= array_length ; i++) {
        new_time += interval;
        S.actions_array[i].start_time = new_time;
      }
      S.ini_time = new_time;

    }
    
    clockWise.Sequence.define_last_action(S);
    
    return S;
}

clockWise.Sequence.end = function(S) {
    S = typeof S != "undefined" ? S : clockWise.Sequence.create();
    
    S.do(function(){});
    
    return S;
}

///////// SEQUENCE CONTROLS /////////

clockWise.Sequence.play = function(S, options) {
    if (typeof S == "undefined") { return clockWise.Sequence.create() }

    if (typeof S.actions_array.length == 0) { return S }
    if (S.active == true) { return S }
    
    options = typeof options != "undefined" ? options : false;

    if (options != false) {
        
        if (S.session_active == false || (typeof options.reset != "undefined" && options.reset === true ) ) {
            
            if (typeof options.reset != "undefined" && options.reset === true) {
                S.clear();
            }
        
        
        if (typeof options.from == "string" || typeof options.from == "number") {
            
            var from = options.from;
            
            if (S.getChapter(from)) {
                from = S.getChapter(from).time;
            }
            
            from = Math.round(parseFloat(from));
            
            if (from >= 0 && from < S.getDuration()) {
                S.global_from = from;
            }
            
        }
        
        if (typeof options.to == "string" || typeof options.to == "number") {
            
            var to = options.to;
            
            if (S.getChapter(to)) {
                to = S.getChapter(to).time;
            }
            
            to = Math.round(parseFloat(to));
            
            if (to > 0 && to <= S.getDuration()) {
            S.global_to = to;
            }
            
        }
        
        if (typeof options.times == "number") {
            S.global_times = Math.round(options.times);
        }
        
        if (typeof options.loop != "undefined" && options.loop == true) {
            S.global_loop = options.loop;
        }
        
    }
    
    }
    
    S.options = options;
    
    if (S.global_to != false && S.global_to <= S.global_from) {
        S.global_to = false;
    }
    
    if (S.global_to != false) {
        S.ini_time = S.global_to;
        clockWise.Sequence.do(function() {clockWise.Sequence.natural_end(S , true)} , S);
        S.actions_array[S.actions_array.length - 1].is_global_to_marker = true;
    }
    
    if (S.initial_timestamp == 0) {
        for (var i = 0; i < S.actions_array.length; i++) {
            S.actions_array[i].iteration_count = 0;
        }
        S.current_Time = S.global_from;
    }
    
    
     if (S.postpone[0] != false) {
        S.postponed_pause_T = setTimeout(function(){S.postpone[1]() ; clockWise.Sequence.clear_postponed(S) } , S.postpone[0]);
     }
    
    
    S.active = true;
    S.session_active = true;
    
    for (var i = 0; i < S.actions_array.length; i++) {
        clockWise.Sequence.trigger_action(S.actions_array[i] , S);
    }
    
        S.initial_timestamp = Date.now() - S.current_Time;
    
    return S;
}

clockWise.Sequence.pause = function(S, options) {
    if (typeof S == "undefined") { return clockWise.Sequence.create() }

    options = typeof options != "undefined" ? options : false;
    
    clearTimeout(S.delayed_pause_T);
    
    var kill = true;
    
    if (options != false) {
        
        if (typeof options.at == "string" || typeof options.at == "number" || typeof options.after == "number") {
            
            if (typeof options.after != "number") {
            var at = options.at;
            } else {
                var at = options.after + S.getCurrentTime();
            }
            
            if (S.getChapter(at)) {
                at = S.getChapter(at).time;
            }
            
            at = Math.round(parseFloat(at));
            
            if (at > S.global_from) {
        
            clockWise.Sequence.set_delayed_pause(S, options, at, function(){S.pause()});
            
            return false;
            
            }
            
        }
        
        if (typeof options.kill == "boolean" && options.kill == false) {
            kill = false;
        }
    }
    
    S.getCurrentTime();
    
    if (kill == true) {
    for (var i = 0; i < S.actions_array.length; i++) {
        clockWise.Sequence.kill_action(S.actions_array[i]);
    }
    }
    
    if (typeof options == "undefined" || typeof options.kill == "undefined" || options.kill == false) {
   clockWise.Sequence.clear_postponed(S);
   }
    
    S.active = false;
    
    return S;
}

clockWise.Sequence.clear = function(S , options) {
    if (typeof S == "undefined") { return clockWise.Sequence.create() }
    
    options = typeof options != "undefined" ? options : false;
    
    clearTimeout(S.delayed_pause_T);
    
    if (options != false) {
        
        if (typeof options.at == "string" || typeof options.at == "number" || typeof options.after == "number") {
            
            if (typeof options.after != "number") {
            var at = options.at;
            } else {
                var at = options.after + S.getCurrentTime();
            }
            
            
            if (S.getChapter(at)) {
                at = S.getChapter(at).time;
            }
            
            at = Math.round(parseFloat(at));
            
            if (at > S.global_from) {
        
            clockWise.Sequence.set_delayed_pause(S, options, at, function(){S.clear()});
            
            return false;
            
            }
            
        }
        
        if (typeof options.kill == "boolean" && options.kill == false) {
            kill = false;
        }
    }
   
   clockWise.Sequence.pause(S, options);
   S.current_Time = 0;
   S.initial_timestamp = 0;
   
   if (typeof options == "undefined" || typeof options.kill == "undefined" || options.kill == false) {
   clockWise.Sequence.cancel_options(S);
   }
    
    return S;
}

clockWise.Sequence.Chapter = clockWise.Sequence.chapter;
clockWise.Sequence.At = clockWise.Sequence.at;
clockWise.Sequence.After = clockWise.Sequence.after;
clockWise.Sequence.Until = clockWise.Sequence.until;
clockWise.Sequence.During = clockWise.Sequence.during;
clockWise.Sequence.Do = clockWise.Sequence.do;
clockWise.Sequence.Do_Again = clockWise.Sequence.Do_again = clockWise.Sequence.do_Again = clockWise.Sequence.do_again;
clockWise.Sequence.x = clockWise.Sequence.X = clockWise.Sequence.Times = clockWise.Sequence.times;
clockWise.Sequence.While = clockWise.Sequence.while;
clockWise.Sequence.At_Interval = clockWise.Sequence.At_interval = clockWise.Sequence.at_Interval = clockWise.Sequence.at_interval;
clockWise.Sequence.End = clockWise.Sequence.end;

clockWise.Sequence.Play = clockWise.Sequence.play;
clockWise.Sequence.Pause = clockWise.Sequence.pause;
clockWise.Sequence.Clear = clockWise.Sequence.clear;


///////// SEQUENCE GET METHODS /////////


clockWise.Sequence.getCurrentTime = function(S) {
    
    if (typeof S == "undefined" || typeof S.current_Time == "undefined") { return 0 }
    
    if (S.initial_timestamp == 0) { S.current_Time = 0 ; return 0 }
    
        var now = Date.now();
        S.current_Time = now - S.initial_timestamp;
        return S.current_Time;
    }
    
clockWise.Sequence.getDuration = function(S) {
    
    if (typeof S == "undefined" || typeof S.actions_array == "undefined") { return 0 }
   
   var last = clockWise.Sequence.getLastAction(S);
   
    var D = last.start_time;
    if (last.end_time != false && last.end_time > D) {
        D = last.end_time;
    }
   
   return D;

}

clockWise.Sequence.getElapsed = clockWise.Sequence.getCurrentTime;

///////// SEQUENCE INTERNAL FUNCTIONS /////////
    
clockWise.Sequence.getChapter = function(S , chapter_name) {
    
    if (typeof S == "undefined" || typeof S.chapters_array == "undefined") { return false }
    
    for (var i = 0; i < S.chapters_array.length; i++) {
        if (S.chapters_array[i].name == chapter_name) { return S.chapters_array[i] }
    }
    
    return false;
    
}

clockWise.Sequence.getLastAction = function(S) {
   
   if (typeof S == "undefined" || typeof S.actions_array == "undefined") { return false }
   
   clockWise.Sequence.define_last_action(S);
   
   for (var i = 0; i < S.actions_array.length; i++) {
    if (S.actions_array[i].is_last == true) { return S.actions_array[i] }
   }

   return false;

}

clockWise.Sequence.trigger_action = function(array_elem, S) {
    
    var action = array_elem.callback;
        var time = array_elem.start_time;
        var expire = array_elem.end_time;
        var interval = array_elem.action_interval;
        var last_action = array_elem.is_last;
        
        var iterations = false;
        
        if (typeof interval == "object") {
            iterations = interval[1];
            interval = interval[0];
        }

        time -= S.current_Time;
        
        var expire_delay = false;
        
        if (expire == false) {
            
          var callback = function(e_d) { action() ; if (last_action) { clockWise.Sequence.natural_end(S, true) } };
          
        } else {
            
            expire -= S.current_Time;
            expire_delay = expire - time;
            
            var callback = function(e_d) {

                array_elem.expired = false;
                
                if (typeof array_elem.iteration_count == "undefined") {
                    array_elem.iteration_count = 0;
                }
                
                var options = false;
                var must_clear_Interval = true;
                
                if (iterations != false && iterations != 0) {
                    
                    if (array_elem.iteration_count >= iterations) {
                        array_elem.iteration_count = 0;
                    } else if (array_elem.iteration_count > 0) {
                        iterations -= array_elem.iteration_count;
                    }

                    options = {max_times:iterations} ;
                    must_clear_Interval = false;
                    
                }
                
                array_elem.expire_Timeout = setTimeout(function(){
                                                       array_elem.expired = true;
                                                       if (array_elem.repeat_Interval.active == false && last_action) {
                                                         clockWise.Sequence.natural_end(S, must_clear_Interval);
                                                       }
                                                       },e_d);
                
                array_elem.repeat_Interval = clockWise.setInterval(function(){
                    
                    if (array_elem.expired == true) {

                        if (iterations == false) {
                        clockWise.clearInterval(array_elem.repeat_Interval);
                        }
                        if (last_action) { clockWise.Sequence.natural_end(S, must_clear_Interval) }
                        }
                          action();
                          array_elem.iteration_count++;
                        
                        
                } , interval , options);
                
            }
        }
        
        if (time > 0) {
        array_elem.action_Timeout = setTimeout(function(){callback(expire_delay)}, time);
        } else if (time == 0) {
        callback(expire_delay);
        } else {

            if (expire != false && expire > 0) {
                
                var diff = time * -1;
                expire_delay -= diff;
                callback(expire_delay);
            }
            
        }
    
}

clockWise.Sequence.define_last_action = function(S) {
    
    var max_time = 0;
    var max_endtime = 0;
    
    var last_action = false;
    var last_action_regarding_endtime = false;
    
    for (var i = 0; i < S.actions_array.length; i++) {
        
        S.actions_array[i].is_last = false;
        
        var this_time = S.actions_array[i].start_time;
        var this_endtime = S.actions_array[i].end_time;
        if (this_time >= max_time) {
            max_time = this_time;
            last_action = S.actions_array[i];
        }
        if (this_endtime >= max_endtime) {
            max_endtime = this_endtime;
            last_action_regarding_endtime = S.actions_array[i];
        }
    }
    
    if (max_endtime > max_time) {
       last_action_regarding_endtime.is_last = true;
    } else {
       last_action.is_last = true; 
    }
    
}

clockWise.Sequence.kill_action = function(array_elem) {
        
        clearTimeout(array_elem.expire_Timeout);
        clearTimeout(array_elem.action_Timeout);
        clockWise.clearInterval(array_elem.repeat_Interval);
    
}

clockWise.Sequence.natural_end = function(S, must_kill) {
    S.clear({kill:must_kill});

    if (S.global_times != false && S.global_times > 1) {
        
        S.global_times--;
        S.options.times = S.global_times;
        S.play(S.options);
        return false;
    }

    if (S.global_loop == true) {
        S.play(S.options);
        return false;
    }
    
    clockWise.Sequence.cancel_options(S);
    
}

clockWise.Sequence.cancel_options = function(S) {
    
    var to_cancel = false;
    for (var i = 0; i < S.actions_array.length; i++) {
        if (typeof S.actions_array[i].is_global_to_marker && S.actions_array[i].is_global_to_marker == true) {
            to_cancel = i;
        }
    }
    
    S.actions_array.splice(i,1);
    
    clockWise.Sequence.clear_postponed(S);
    
    S.options = false;
    S.global_from = 0;
    S.global_to = false;
    S.global_loop = clockWise.sequence_loop_by_default;
    S.global_times = false;
    
    S.session_active = false;
    
}

clockWise.Sequence.set_delayed_pause = function(S , options , at , delayed_callback ) {
            var curr_T = S.getCurrentTime();
            
            if (S.global_to != false) {
                var max_T = S.global_to;
            } else {
                var max_T = S.getDuration();
            }
            
            if (at > curr_T && at <= max_T) {
                
                var diff = at - curr_T;
               S.delayed_pause_T = setTimeout( delayed_callback , diff);
                
            } else {
                
               if (typeof options.if_too_late == "undefined" || options.if_too_late == "postpone") {
                
                    if (S.global_loop == false && (S.global_times == false || S.global_times == 1)) {
                
                    } else {
                        if (at <= max_T) {
                         var pp_delay = at - S.global_from;
                        } else {
                        var pp_delay = (at - curr_T) - (max_T - curr_T);
                        }
                        S.postpone[0] = pp_delay;
                        S.postpone[1] = delayed_callback;
                    }
                    
                } else if (options.if_too_late == "immediate") {
                    delayed_callback();
                }
                
            }
}

clockWise.Sequence.clear_postponed = function(S) {
    clearTimeout(S.postponed_pause_T);
    S.postpone[0] = 0;
    S.postpone[1] = false;
}


clockWise.Seq = clockWise.seq = clockWise.sequence = clockWise.Sequence;

clockWise.setSequence = function() {return clockWise.Sequence.create()}

//////

clockWise.window = clockWise;