class @Timer

constructor: (@func) ->
    @running = no; @id = null
    @_handler = =>
        @running = no
        @id = null
        @func()

start: (timeout) ->
    clearTimeout @id if @running
    @id = setTimeout @_handler, timeout
    @running = yes

stop: ->
    if @running
        clearTimeout @id
        @running = no; @id = null

Timer.start = (timeout, func) ->

setTimeout func, timeout

generateCacheBustUrl = (url) ->

URI(url).setQuery('now', 1 * new Date).toString()

unless URI.prototype.updateEmptyParts

URI.prototype.updateEmptyParts = (location) ->
    this.hostname(location.hostname) if this.hostname() == ''
    this.port(location.port) if this.port() == ''
    this.protocol(location.protocol) if this.protocol() == ''
    return this

class @ReloaderContext

'use strict';

STYLE_ID = "EpuberAutoRefreshTransitionRule"
TRANSITION =
    className: 'epuber_loading'
    duration: 0.3

@ReloadType = ReloadType =
    style: 1
    reload: 2
    compilation_begin: 3
    compilation_end: 4

constructor: (@window, @console) ->
    @document = @window.document
    $(@window).load =>
        @_restoreScrollPosition()

_head: ->
    @__head ||= @document.getElementsByTagName('head')[0]

_startAnimatedStylesheetReload: ->
    return if @document.getElementById(STYLE_ID)?

    style = @document.createElement("style")
    @_head().appendChild(style)

    style.setAttribute("type", "text/css")
    style.setAttribute("id", STYLE_ID)

    style_code = """
        .#{TRANSITION.className} * {
            -webkit-transition: all #{TRANSITION.duration}s ease-out;
            -moz-transition: all #{TRANSITION.duration}s ease-out;
            -o-transition: all #{TRANSITION.duration}s ease-out;
            transition: all #{TRANSITION.duration}s ease-out;
        }
    """

    if style.styleSheet
        style.styleSheet.cssText = style_code
    else
        style.appendChild(@document.createTextNode(style_code))

    @_addTransitionClassToHtmlNode()

_stopAnimatedStylesheetReload: ->
    element = @document.getElementById(STYLE_ID)
    @_head().removeChild(element) if element?

    @_removeTransitionClassFromHtmlNode()

_addTransitionClassToHtmlNode: ->
    htmlNode = document.body.parentNode
    htmlNode.className = htmlNode.className.replace(TRANSITION.className, "") + " #{TRANSITION.className}"

_removeTransitionClassFromHtmlNode: ->
    htmlNode = document.body.parentNode
    htmlNode.className = htmlNode.className.replace(TRANSITION.className, "")

_reattachAllStylesheetLinks: (changed_files_hrefs = null) ->
    links = (link for link in @document.getElementsByTagName('link') when link.rel.match(/^stylesheet$/i) and not link.__pendingRemoval)

    if changed_files_hrefs?
        changed_uris = (URI(href).updateEmptyParts(@window.location) for href in changed_files_hrefs)

        filtered_list = []
        for link in links
            uri = URI(link.href).removeQuery('now')

            for changed_uri in changed_uris
                if uri.equals(changed_uri)
                    filtered_list.push(link)
                    break

        links = filtered_list

    for link in links
        @_reattachStylesheetLink(link)

_reattachStylesheetLink: (link) ->
    # ignore LINKs that will be removed by LR soon
    return if link.__pendingRemoval
    link.__pendingRemoval = yes

    clone = link.cloneNode(false)
    clone.href = generateCacheBustUrl(link.href)

    # insert the new LINK before the old one
    parent = link.parentNode
    if parent.lastChild is link
        parent.appendChild(clone)
    else
        parent.insertBefore clone, link.nextSibling

    @_waitUntilCssLoads clone, =>
        if /AppleWebKit/.test(navigator.userAgent)
            additionalWaitingTime = 5
        else
            additionalWaitingTime = 200

        Timer.start additionalWaitingTime, =>
            return if !link.parentNode
            link.parentNode.removeChild(link)
            clone.onreadystatechange = null

            @window.StyleFix?.link(clone)

_waitUntilCssLoads: (clone, func) ->
    callbackExecuted = no

    executeCallback = =>
        return if callbackExecuted
        callbackExecuted = yes
        func()

    # supported by Chrome 19+, Safari 5.2+, Firefox 9+, Opera 9+, IE6+
    # http://www.zachleat.com/web/load-css-dynamically/
    # http://pieisgood.org/test/script-link-events/
    clone.onload = =>
        @console.log "AutoRefresh: the new stylesheet has finished loading... for file #{clone.href}"
        @knownToSupportCssOnLoad = yes
        executeCallback()

    unless @knownToSupportCssOnLoad
        # polling
        do poll = =>
            if clone.sheet
                @console.log "AutoRefresh: is polling until the new CSS finishes loading... for file #{clone.href}"
                executeCallback()
            else
                Timer.start 50, poll

    # fail safe
    Timer.start (10 * 1000), executeCallback

_createCompilingOverlayIfNeeded: ->
    if @spinner_container?
        return

    @spinner_container = document.createElement('div');
    @spinner_container.id = 'epuber_spinner_container';
    $(@spinner_container).css('opacity', '0');

    opts =
        lines: 12 # The number of lines to draw
        length: 7 # The length of each line
        width: 4 # The line thickness
        radius: 9 # The radius of the inner circle
        corners: 1 # Corner roundness (0..1)
        rotate: 0 # The rotation offset
        direction: 1 # 1: clockwise, -1: counterclockwise
        color: '#fff' # #rgb or #rrggbb or array of colors
        speed: 1 # Rounds per second
        trail: 42 # Afterglow percentage
        shadow: false # Whether to render a shadow
        hwaccel: false # Whether to use hardware acceleration
        className: 'epuber_spinner' # The CSS class to assign to the spinner
        zIndex: 2e9 # The z-index (defaults to 2000000000)
        top: '50%' # Top position relative to parent
        left: '50%' # Left position relative to parent

    @spinner = new Spinner(opts)

_displayCompilingOverlay: ->
    @_createCompilingOverlayIfNeeded()
    @spinner.spin(@spinner_container)
    $(@spinner_container).appendTo('body').animate({opacity: '1'}, TRANSITION.duration * 1000)

_hideCompilingOverlay: ->
    @spinner.stop()
    $(@spinner_container).remove().animate({opacity: '0'}, TRANSITION.duration * 1000)

_saveScrollPosition: ->
    expires = new Date;
    expires.setTime(expires.getTime() + 864e5)

    o = window.pageXOffset + "_" + window.pageYOffset;
    Cookies.set('epuber_scroll_offset', o, expires: expires)

_restoreScrollPosition: ->
    offset = Cookies.get('epuber_scroll_offset')
    if offset?
        t = offset.split("_")

        if t.length == 2
            @window.scrollTo(parseInt(t[0]), parseInt(t[1]))

    Cookies.expire('epuber_scroll_offset')

perform: (type, changed_files_hrefs) ->
    switch type
        when ReloadType.style
            @console.log('ReloadType.style')
            @_hideCompilingOverlay()

            @_startAnimatedStylesheetReload()
            @_reattachAllStylesheetLinks(changed_files_hrefs)
            after(TRANSITION.duration * 1000, => @_stopAnimatedStylesheetReload())

        when ReloadType.reload
            @console.log('AutoRefresh: reloading page')
            @_saveScrollPosition()

            window.location.reload(true)

        when ReloadType.compilation_begin
            @_displayCompilingOverlay()

        when ReloadType.compilation_end
            @_hideCompilingOverlay()

        else
            @console.error "Unsupported reload type #{type}"