function gpxPhotoScroller() {

    /*  
        this function send ajax request to retrieve photos
        server should respond xml format with following format
    
        <xml...>
        <gallery>
            # backward-link contains the url for the back arrow
            # when it is empty 
            <backward-link>...</backward-link>
            <forward-link>...</forward-link>
            <photo>
                <name>...</name>
                <url>...</url>
                <link>...</link>
                
            </photo>
            ...
        </gallery>

        when server response has <hide>, this scroller will hidden
        when server response has <error>, this scroller will display the error message
    */

    var scroller = new Object
    scroller.left_arrow_url = ''
    scroller.right_arrow_url = ''
    scroller.left_arrow_img = "https://www.marathon-photos.com/images/arrows/backward-arrow.png"
    scroller.right_arrow_img = "https://www.marathon-photos.com/images/arrows/forward-arrow.png"
    scroller.breaker = '</tr><tr>'
    scroller.emptymsg = "no photos to view"
    scroller.nav_text_pos = 'bottom'
    scroller.loading_text = ''
    scroller.loading_img = "https://www.marathon-photos.com/images/loader.gif"
    scroller.cols = 5
    scroller.rows = 1
    scroller.row_height = 120
    scroller.response = ''
    scroller.previous_photos_text = ''
    scroller.next_photos_text = ''
    scroller.url = ''

    // text like " display 2 to 5 of 10 photos "
    scroller.nav_text = ''

    // html code for each image
    scroller.unit_layout = '<td><a href="_LINK_"><img src="_URL_"></a></td>'

    this.enable_click2pop = function () {
        scroller.unit_layout = '<td><a href="_LINK_" target="_blank"><img src="_URL_"></a></td>'
    }

    // default exchanger
    scroller.exchanger = new gpxdictionary
    scroller.exchanger.set('_START_', 'start')
    scroller.exchanger.set('_END_', 'end')
    scroller.exchanger.set('_TOTAL_', 'total')
    scroller.exchanger.set('_LINK_', 'link')
    scroller.exchanger.set('_URL_', 'url')
    scroller.exchanger.set('_EVENT_', 'event')
    scroller.exchanger.set('_PHOTO_', 'name')
    
    this.set_arrow_text = function(previous_text, next_text){
        scroller.previous_photos_text = previous_text
        scroller.next_photos_text = next_text
    }

    this.set_row_height = function(height) {
        scroller.row_height = height
    }

    this.set_error_msg = function(msg) {
        scroller.error_msg = msg
    }

    this.set_loading_text = function(text) {
        scroller.loading_text = text
    }

    this.set_cols = function(cols) {
        scroller.cols = cols
    }

    this.set_rows = function(rows) {
        try {
            rows = parseInt(rows)
        } catch(e) {
            rows = 1
        }
        scroller.rows = rows
    }

    this.set_emptymsg = function(text) {
        scroller.emptymsg = text
    }

    this.set_breaker = function(text) {
        scroller.breaker = text
    }

    this.set_unit_layout = function(layout) {
        scroller.unit_layout = layout
    }

    this.set_exchanger = function(exchanger) {
        scroller.exchanger = exchanger
    }

    scroller.exchange = function(data, response) {
        keys = scroller.exchanger.keys()
        try {
            for( var i = 0; i < keys.length; i++ ) {
                tag = scroller.exchanger.get(keys[i])
                try {
                    value = response.getElementsByTagName(tag)[0].childNodes[0].nodeValue
                } catch(e) {
                    value = ''
                }
                while ( data.indexOf(keys[i]) >= 0 )
                    data = data.replace(keys[i], value)
            }
        } catch(e) {
            return data
        }
        return data
    }

    scroller.refresh = function(){
        scroller.display_from_url(scroller.url)
    }

    scroller.get_response = function() {
        // process the response from server

        if ( scroller.ajax.readyState != 4 )
            return

        // returned error code -1
        if ( scroller.ajax.responseText == '-1') {
            if ( scroller.error_msg ) {
                document.getElementById(scroller.id+"-middle-td").innerHTML = "<div class='err'>"+scroller.error_msg+"</div>"
                return 
            }
        }
        if ( scroller.ajax.responseText.indexOf('ERR:') == 0 ) {
            msg = scroller.ajax.responseText.replace('ERR:', '')
            document.getElementById(scroller.id+"-middle-td").innerHTML = msg
            return
        } else if ( scroller.ajax.responseText.indexOf('Refresh:') == 0 ) {
            var secs = scroller.ajax.responseText.substr(8)
            secs = parseInt(secs) * 1000
            setTimeout(scroller.refresh, secs)
            return
        }
        // actual process code
        response_text = scroller.ajax.responseText
        response = parse_xml_text(response_text)

        try {
            // if error found, display the error message only and stop
            scroller.error_msg = response.getElementsByTagName('error')[0].childNodes[0].nodeValue
            if ( scroller.error_msg ) {
                document.getElementById(scroller.id+"-middle-td").innerHTML = "<div class='err'>"+scroller.error_msg+"</div>" 
                return;
            }
        } catch(e) {
            // do nothing
        }

        try {
            // if error found, display the error message only and stop
            hide = response.getElementsByTagName('hide')[0].childNodes[0].nodeValue
            if ( hide ) {
                document.getElementById(scroller.id).style.display = 'none'
                return;
            }
        } catch(e) {
            // do nothing
        }


        // left/right arrow url
        try {
            scroller.left_arrow_url = response.getElementsByTagName('backward-link')[0].childNodes[0].nodeValue
        } catch(e) {
            scroller.left_arrow_url = ''
        }
        try {
            scroller.right_arrow_url = response.getElementsByTagName('forward-link')[0].childNodes[0].nodeValue
        } catch(e) {
            scroller.right_arrow_url = ''
        }

        // cols and rows
        cols = scroller.cols
        rows = scroller.rows

        // nav text
        try {
            var nav_text
            var no_nav_text = response.getElementsByTagName('no_nav_text')
            if ( no_nav_text[0] ) {
                nav_text = ''
            } else {
                nav_text = scroller.exchange(scroller.nav_text, response)
            }
            document.getElementById(scroller.id+"-nav-text").innerHTML = nav_text
        } catch(e) {
            // do nothing
        }

        var photos = response.getElementsByTagName('photo')
        if ( !scroller.left_arrow_url ) {
            $('#'+scroller.id+"-left-td div.arrow").css('display', 'none')
        } else {
            $('#'+scroller.id+"-left-td div.arrow").css('display', 'block')
            document.getElementById(scroller.id+"-left-img").onclick = scroller.l_retrieve
        }
        if ( !scroller.right_arrow_url ) {
            $('#'+scroller.id+"-right-td div.arrow").css('display', 'none')
        } else {
            $('#'+scroller.id+"-right-td div.arrow").css('display', 'block')
            document.getElementById(scroller.id+"-right-img").onclick = scroller.r_retrieve
        }

        if ( photos.length == 0 ) {
            document.getElementById(scroller.id+"-middle-td").innerHTML = "<div class='empty'>"+scroller.emptymsg+"</div>"
            return;
        }

        maincontent = "<table cellspacing='0'><tbody><tr>"
        ccol = 1
        crow = 1
        for ( var i = 0; i < photos.length; i++ ) {
            photo = photos[i]
            layout = scroller.exchange(scroller.unit_layout, photo)
            maincontent += layout

            if ( ccol == cols ) {
                maincontent += scroller.breaker
                ccol = 1
                crow += 1
            } else {
                ccol += 1
            }

            if ( crow > rows ) {
                break
            }
        }
        crow = photos.length / cols
        maincontent += "</tr></tbody></table>"
        document.getElementById(scroller.id+"-middle-td").innerHTML = maincontent
    }

    this.set_left_arrow_url = function(url) {
        scroller.left_arrow_url = url
    }

    this.set_right_arrow_url = function(url) {
        scroller.right_arrow_url = url
    }

    scroller._remake_url = function(url){
        if(url.indexOf('?')<0){
            url = url + "?rand=" + Math.random()
        } else {
            url = url + "&rand=" + Math.random()
        }
        return url
    }

    scroller.display_from_url = function(url){
        scroller.url = url
        document.getElementById(scroller.id+'-middle-td').innerHTML = "<img class='loader' src='"+scroller.loading_img+"'>"
        try {
            document.getElementById(scroller.id+'-nav-text').innerHTML = scroller.loading_text
        } catch(e) {
            // do nothing
        }
        scroller.ajax = get_ajax_object()
        scroller.ajax.onreadystatechange = scroller.get_response
        url = scroller._remake_url(url)
        scroller.ajax.open('GET', url, true)
        scroller.ajax.send(null)
    }
    this.display_from_url = function(url) {
        scroller.display_from_url(url)
    }

    scroller.get_new_id = function() {
        id = Math.round(Math.random() * 1000 / 1)
        while ( document.getElementById("photo-scroller-"+id) )
            id = Math.round(Math.random() * 1000 / 1)

        scroller.id = "scroller" + id
    }

    scroller.get_new_id()

    scroller.l_retrieve = function() {
        document.getElementById(scroller.id+'-middle-td').innerHTML = "<img class='loader' src='"+scroller.loading_img+"'>"
        try {
            document.getElementById(scroller.id+'-nav-text').innerHTML = scroller.loading_text
        } catch(e) {
            // do nothing
        }
        scroller.ajax = get_ajax_object()
        scroller.ajax.onreadystatechange = scroller.get_response

        scroller.ajax.open('GET', scroller.left_arrow_url, true)
        scroller.ajax.send(null)
    }

    scroller.r_retrieve = function() {
        document.getElementById(scroller.id+'-middle-td').innerHTML = "<img class='loader' src='"+scroller.loading_img+"'>"
        try {
            document.getElementById(scroller.id+'-nav-text').innerHTML = scroller.loading_text
        } catch(e) {
            // do nothing
        }
        scroller.ajax = get_ajax_object()
        scroller.ajax.onreadystatechange = scroller.get_response

        scroller.ajax.open('GET', scroller.right_arrow_url, true)
        scroller.ajax.send(null)
    }

    this.set_left_arrow_img = function(url) {
        scroller.left_arrow_img = url
    }

    this.set_right_arrow_img = function(url) {
        scroller.right_arrow_img = url
    }

    this.set_loading_img = function(url) {
        scroller.loading_img = url
    }

    this.set_nav_text_pos = function(pos) {
        scroller.nav_text_pos = pos
    }

    this.set_nav_text = function(text) {
        scroller.nav_text = text
    }

    this.generate = function() {
        id = scroller.id

        document.write("<DIV class='photo-scroller' id='"+id+"'>")
        if ( scroller.nav_text_pos == "top" )
            document.write("<DIV class='nav-text' id='"+id+"-nav-text'></DIV>")
        document.write("<TABLE class='thumbnails' align='center'>")
        document.write("<TR>")
        var previous_text = ''
        if ( scroller.previous_photos_text ) {
            previous_text = "<br>"+scroller.previous_photos_text
        }
        document.write("<TD class='left-cell' id='"+id+"-left-td'><div class='arrow'>" + 
            "<img src='"+scroller.left_arrow_img+"' id='"+id+"-left-img' class='arrow'>" + 
            previous_text+"</div></TD>")
        document.write("<TD class='middle-cell' id='"+id+"-middle-td'></TD>")
        var next_text = ''
        if ( scroller.next_photos_text ) {
            next_text = "<br>"+scroller.next_photos_text
        }
        document.write("<TD class='right-cell' id='"+id+"-right-td'><div class='arrow'>"+
            "<img src='"+scroller.right_arrow_img+"' id='"+id+"-right-img' class='arrow'>"+
            next_text+"</div></TD>")
        document.write("</TR></TABLE>")
        if ( scroller.nav_text_pos == "bottom" )
            document.write("<DIV class='nav-text' id='"+id+"-nav-text'></DIV>")
        document.write("</DIV>")
    }

}


