var iX, iY, oX = 0, oY = 0;
var Viewer = null;



var SurfaceEvents =
{
    move : function( event )
    {
        pt = System.Input.getMousePosition(event);
        
        var x = pt.x - iX + oX;
        var y = pt.y - iY + oY;
        
        Viewer.picture.style.pixelLeft = x;
        Viewer.picture.style.pixelTop  = y;
        
        Viewer.picture.style.left = x + "px";
        Viewer.picture.style.top  = y + "px";
        
        Viewer.curr.x = x;
        Viewer.curr.y = y;
        
        Viewer.picture.refresh();
       
        ev = System.Web.getEvent(event);
        if( ev && ev.preventDefault )
            ev.preventDefault();
       
        return false;
    },
    
    down : function( event )
    {
        System.Web.addEvent( Viewer.surface, "mousemove", SurfaceEvents.move );
        oX = Viewer.curr.x;//System.Math.getInt( Viewer.picture.style.pixelLeft );
        oY = Viewer.curr.y;//System.Math.getInt( Viewer.picture.style.pixelTop );
        iX = System.Input.getMousePosition(event).x;
        iY = System.Input.getMousePosition(event).y;
    },
    
    up : function( event )
    {
        System.Web.removeEvent( Viewer.surface, "mousemove", SurfaceEvents.move );
    },
    
    enter : function( event )
    {
        Viewer.description.style.display = (Viewer.description.visible ? "block" : "none");
        Viewer.comments.style.display = (Viewer.comments.visible ? "block" : "none");
        Viewer.controller.style.display = "block";
        
        System.Cache["event_enter"] = true;
        Viewer.events.resize();
        Viewer.picture.refresh();
        System.Cache["event_enter"] = false;
    },
    
    leave : function( event )
    {
        System.Web.removeEvent( Viewer.surface, "mousemove", SurfaceEvents.move );
    
        Viewer.events.resize();
        Viewer.picture.refresh();
    
        Viewer.controller.style.display  = "none";
        Viewer.description.style.display = "none";
        Viewer.comments.style.display    = "none";
        
        Viewer.description.move_up.style.display = "none";
        Viewer.description.move_dn.style.display = "none";
        
        Viewer.comments.move_up.style.display = "none";
        Viewer.comments.move_dn.style.display = "none";
    }
};



var ViewerEvents =
{
    resize : function( event )
    {
        // Resize the wall.
        
        Viewer.wall.style.width  = System.Web.width() + "px";
        Viewer.wall.style.height = System.Web.height() + "px";
    
        // Resize and reposition the viewer.
        
        y = Math.max( System.Web.height() / 20, 10 );
        h = Math.ceil(y * 18) + "px";
        
        Viewer.surface.style.height = h;
        Viewer.canvas.style.height  = h;
        Viewer.style.height         = h;
        
        Viewer.style.top = Math.ceil(y) + "px";
        
        x = System.Web.width() / 10;
        w = Math.ceil(x * 8) + "px";

        Viewer.style.width         = w;
        Viewer.surface.style.width = w;
        Viewer.canvas.style.width  = w;
        
        Viewer.style.left = Math.ceil(x) + "px";
        
        // Resize the description block.
        
        dx = Math.ceil(x * 5) + "px";
        
        Viewer.description.contents.style.width = dx;
        dw = Viewer.description.contents.clientWidth + "px";
        
        Viewer.description.back.style.width  = dw;
        Viewer.description.style.width       = dw;
        Viewer.description.front.style.width = dw;
        
        dh = Viewer.description.contents.clientHeight;
        Viewer.description.back.style.height  = dh + "px";
        Viewer.description.front.style.height = dh + "px";
        
        th = Math.ceil(y * 7);
        
        if( dh < th )
        {
            Viewer.description.style.height   = dh + "px";
            Viewer.description.style.overflow = "visible";
            th = dh;

            Viewer.description.move_up.style.display = "none";
            Viewer.description.move_dn.style.display = "none";
            
            Viewer.description.move_pos = 0;
            Viewer.description.contents.style.top = Viewer.description.move_pos + "px";
        }
        else
        {
            Viewer.description.style.height   = th + "px";
            Viewer.description.style.overflow = "hidden";

            // Accommodate the scroll arrows.
            Viewer.description.contents.style.width = (Viewer.description.contents.clientWidth - 24) + "px";

            if( Viewer.description.visible && Viewer.description.style.display == "block" )
            {
                Viewer.description.move_up.style.display = "block";
                Viewer.description.move_dn.style.display = "block";
            }
            
            Viewer.description.move_up.style.top  = (Viewer.clientHeight - th - 6) + "px";
            Viewer.description.move_up.style.left = (Viewer.description.contents.clientWidth + 2) + "px";
            
            Viewer.description.move_dn.style.top  = (Viewer.clientHeight - 26) + "px";
            Viewer.description.move_dn.style.left = (Viewer.description.contents.clientWidth + 2) + "px";
        }
        
        Viewer.description.style.top = (Viewer.clientHeight - th - 10) + "px";
        
        // Resize the comments block.
        
        Viewer.comments.contents.style.width = dx;
        dw = Viewer.comments.contents.clientWidth + "px";

        var ecn = document.getElementById("comments_form_name");
        var ect = document.getElementById("comments_form_text");
        
        if( ect && ecn )
            ect.style.width = Math.max(120, (Viewer.comments.contents.clientWidth - ecn.clientWidth - 30)) + "px";
        
        Viewer.comments.back.style.width = dw;
        Viewer.comments.style.width      = dw;
        
        dh = Viewer.comments.contents.clientHeight;
        Viewer.comments.back.style.height = dh + "px";
        
        th = Viewer.clientHeight - Viewer.description.clientHeight - 30;
        
        if( dh < th )
        {
            Viewer.comments.style.height   = dh + "px";
            Viewer.comments.style.overflow = "visible";
            th = dh;
            
            Viewer.comments.move_up.style.display = "none";
            Viewer.comments.move_dn.style.display = "none";
            
            Viewer.comments.contents.style.top = Viewer.comments.move_pos + "px";
        }
        else
        {
            Viewer.comments.style.height   = th + "px";
            Viewer.comments.style.overflow = "hidden";
            
            // Accommodate the scroll arrows.
            Viewer.comments.contents.style.width = (Viewer.comments.contents.clientWidth - 24) + "px";

            if( Viewer.comments.visible && Viewer.comments.style.display == "block" )
            {
                Viewer.comments.move_up.style.display = "block";
                Viewer.comments.move_dn.style.display = "block";
            }
            
            Viewer.comments.move_up.style.top  = "14px";
            Viewer.comments.move_up.style.left = (Viewer.comments.contents.clientWidth + 2) + "px";
            
            Viewer.comments.move_dn.style.top  = (Viewer.comments.clientHeight - 6) + "px";
            Viewer.comments.move_dn.style.left = (Viewer.comments.contents.clientWidth + 2) + "px";
            
            if( ect && ecn )
                ect.style.width = Math.max(120, (Viewer.comments.contents.clientWidth - ecn.clientWidth - 30)) + "px";
        }
        
        // Reveal most recent comments/comments form.
        if( !System.Cache["event_enter"] )
            Viewer.comments.move_pos = Viewer.comments.clientHeight - Viewer.comments.contents.clientHeight;

        Viewer.comments.contents.style.top = Viewer.comments.move_pos + "px";
        
        // Reposition the control block.
        
        Viewer.controller.style.left = (Viewer.clientWidth - Viewer.controller.clientWidth - 10) + "px";
        
        // Create the image mesh.
        
        var s = ( Viewer.curr && Viewer.curr.tile ? Viewer.curr.tile.size : 256 );
        var w = Math.ceil(Viewer.clientWidth / s) + 1;
        var h = Math.ceil(Viewer.clientHeight / s) + 1;

        Viewer.picture.maxTiles = w * h;

        if( w * h > System.Cache["tiles"].length )
        {
            Viewer.canvas.innerHTML = " ";
            Viewer.canvas.appendChild( Viewer.picture );
        
            while( w * h > System.Cache["tiles"].length )
                PictureData.createNewTile();
        }
        
        // Re-insert tiles if the canvas is empty.
        
        if( Viewer.canvas.childNodes && Viewer.canvas.childNodes.length < System.Cache["tiles"].length )
        {
            Viewer.canvas.innerHTML = " ";
            Viewer.canvas.appendChild( Viewer.picture );
            
            for( var i = 0; i < System.Cache["tiles"].length; ++i )
            {
                System.Cache["tiles"][i].style.display = "none";
                Viewer.canvas.insertBefore( System.Cache["tiles"][i], Viewer.picture );
            }
               
            Viewer.picture.refresh();
        }
    },
    
    close : function( event )
    {
        System.Cache["session_id"] = -1;
        Viewer.style.display = "none";
        Viewer.wall.style.display = "none";
        Viewer.visible = false;

	Viewer.picture.resetTiles();
        System.Cache["tiles"] = [];
        
        // Clear any stray connection reattempts.
        System.Web.clearTimer("map");
        System.Web.clearTimer("com");
        
        // Clear any stray requests.
        System.Xml.abortXmlRequest("map");
        System.Xml.abortXmlRequest("com");
    },
    
    talk : function( event )
    {
        Viewer.description.visible = !Viewer.description.visible;
        Viewer.description.style.display = (Viewer.description.visible ? "block" : "none");
        Viewer.events.resize(event);
    },
    
    guide_show : function( event )
    {
        Viewer.controller.style.height = "36px";
        Viewer.controller.guide.style.display = "block";
    },
    
    guide_hide : function( event )
    {
        Viewer.controller.style.height = "18px";
        Viewer.controller.guide.style.display = "none";
    },
    
    comments : function( event )
    {
        Viewer.comments.visible = !Viewer.comments.visible;
        Viewer.comments.style.display = (Viewer.comments.visible ? "block" : "none");
        
        if( Viewer.comments.visible )
        {
            Viewer.comments.contents.innerHTML = ViewerEvents._loading_i + ViewerEvents._loading_t;
            System.Xml.createXmlRequest( "com", ViewerEvents.response_com );
            System.Xml.sendXmlRequest( "com", "engine.php?q=" + Viewer.picture.path, ViewerEvents.failed_com, 15000 );
        }
        
        Viewer.events.resize(event);
    },
    
    zoom_in : function( event )
    {
        if( !Viewer.picture.ready )
            return;
        
        if( Viewer.curr.z > 0 )
        {
            Viewer.curr.z -= 1;
            
            var prev_tile    = Viewer.curr.tile;
            Viewer.curr.tile = Viewer.picture.data.tiles[Viewer.curr.z];
            
            Viewer.curr.x = Viewer.curr.x - Math.ceil((Viewer.curr.tile.cols - prev_tile.cols) * prev_tile.size / 2);
            Viewer.curr.y = Viewer.curr.y - Math.ceil((Viewer.curr.tile.rows - prev_tile.rows) * prev_tile.size / 2);
            
            Viewer.picture.style.pixelLeft = Viewer.curr.x;
            Viewer.picture.style.pixelTop  = Viewer.curr.y;
            Viewer.picture.style.left      = Viewer.curr.x + "px";
            Viewer.picture.style.top       = Viewer.curr.y + "px";
            
            oX = Viewer.curr.x;
            oY = Viewer.curr.y;
            
            Viewer.picture.style.width  = (Viewer.curr.tile.size * Viewer.curr.tile.cols) + "px";
            Viewer.picture.style.height = (Viewer.curr.tile.size * Viewer.curr.tile.rows) + "px";
            
            Viewer.picture.resetTiles();
            Viewer.picture.refresh();
        }
        else
        {
            Viewer.controller.guide.innerHTML = "Cannot enlarge.";
        }
    },
    
    zoom_out : function( event )
    {
        if( !Viewer.picture.ready )
            return;
        
        if( Viewer.curr.z < Viewer.picture.data.length - 1 )
        {
            Viewer.curr.z += 1;
            
            var prev_tile    = Viewer.curr.tile;
            Viewer.curr.tile = Viewer.picture.data.tiles[Viewer.curr.z];
            
            Viewer.curr.x = Viewer.curr.x + Math.ceil((prev_tile.cols - Viewer.curr.tile.cols) * prev_tile.size / 2);
            Viewer.curr.y = Viewer.curr.y + Math.ceil((prev_tile.rows - Viewer.curr.tile.rows) * prev_tile.size / 2);
            
            Viewer.picture.style.pixelLeft = Viewer.curr.x;
            Viewer.picture.style.pixelTop  = Viewer.curr.y;
            Viewer.picture.style.left      = Viewer.curr.x + "px";
            Viewer.picture.style.top       = Viewer.curr.y + "px";
            
            oX = Viewer.curr.x;
            oY = Viewer.curr.y;
            
            Viewer.picture.style.width  = (Viewer.curr.tile.size * Viewer.curr.tile.cols) + "px";
            Viewer.picture.style.height = (Viewer.curr.tile.size * Viewer.curr.tile.rows) + "px";
            
            Viewer.picture.resetTiles();
            Viewer.picture.refresh();
        }
        else
        {
            Viewer.controller.guide.innerHTML = "Cannot reduce.";
        }
    },
    
    zoom_fit : function( event )
    {
        if( !Viewer.picture.ready )
            return;
        
        Viewer.picture.reload();
        Viewer.picture.refresh();
    },
    
    prev : function( event )
    {
        for( var i = System.Cache["files"].length - 1; i > 0; --i )
        {
            if( System.Cache["files"][i] == Viewer.picture.path )
            {
                Viewer.load( System.Cache["files"][i-1] );
                Viewer.controller.style.height = "36px";
                return;
            }
        }
        
        Viewer.controller.guide.innerHTML = "This is the earliest.";
    },
    
    next : function( event )
    {
        for( var i = 0; i < System.Cache["files"].length - 1; ++i )
        {
            if( System.Cache["files"][i] == Viewer.picture.path )
            {
                Viewer.load( System.Cache["files"][i+1] );
                Viewer.controller.style.height = "36px";
                return;
            }
        }
        
        Viewer.controller.guide.innerHTML = "This is the latest.";
    },
    
    _loading_i : "<img class='loading' src='engine/loading.gif'/>",
    _loading_t : "&nbsp; <span style='position: relative; top: -3px'>Initialising Engine...</span>",
    _loading_c : "&nbsp; <span style='position: relative; top: -3px'>Connecting...</span>",
    _loading_s : "&nbsp; <span style='position: relative; top: -3px'>Posting Comment...</span>",
    _loading_g : "&nbsp; <span style='position: relative; top: -3px'>Thank you! Refreshing comments...</span>",
    
    load : function( strPath )
    {
        System.Cache["session_id"] = Math.ceil( Math.random() * 1000000 );
        
        Viewer.picture.resetTiles();
        System.Cache["tiles"] = [];
    
        Viewer.description.contents.innerHTML = ViewerEvents._loading_i + ViewerEvents._loading_c;
        Viewer.comments.contents.innerHTML = ViewerEvents._loading_i + ViewerEvents._loading_t;
        Viewer.canvas.innerHTML = ViewerEvents._loading_i;
        
        Viewer.picture.path = strPath;
        
        System.Xml.createXmlRequest( "map", ViewerEvents.response_map );
        System.Xml.createXmlRequest( "com", ViewerEvents.response_com );
        
        System.Xml.sendXmlRequest( "map", Viewer.picture.path + "/index.html", ViewerEvents.failed_map, 10000 );
        System.Xml.sendXmlRequest( "com", "engine.php?q=" + Viewer.picture.path, ViewerEvents.failed_com, 15000 );
        
        Viewer.description.move_pos = 0;
        Viewer.comments.move_pos = 0;
        
        Viewer.controller.style.width  = "144px";
        Viewer.controller.style.height = "18px";
        
        Viewer.wall.style.display = "block";
        Viewer.style.display = "block";
        Viewer.visible = true;
        
        SurfaceEvents.enter();
        Viewer.events.resize();
        Viewer.picture.refresh();
        
        ViewerEvents.zoom_fit();
    },
    
    response_map : function( xmlResponse )
    {
        if( xmlResponse )
        {
            var strHtml = xmlResponse.responseText;
        
            Viewer.description.contents.innerHTML = strHtml;
            Viewer.description.insertBefore( Viewer.description.contents, Viewer.description.front );
            Viewer.events.resize();
            
            Viewer.picture.load(strHtml);
        }
    },
    
    response_com : function( xmlResponse )
    {
        if( xmlResponse )
        {
            Viewer.comments.contents.innerHTML = xmlResponse.responseText;
            Viewer.events.resize(null);
        }
    },
    
    failed_map : function( xmlResponse )
    {
        if( !Viewer.visible )
            return;
    
        System.Web.setTimer( "map", function() {
            Viewer.canvas.innerHTML = ViewerEvents._loading_i;
            Viewer.description.contents.innerHTML = ViewerEvents._loading_i + ViewerEvents._loading_c;
            Viewer.events.resize(null);

            System.Xml.createXmlRequest( "map", ViewerEvents.response_map );
            System.Xml.sendXmlRequest( "map", Viewer.picture.path + "/index.html", ViewerEvents.failed_map, 10000 );
        }, 5000 );
    
        Viewer.canvas.innerHTML = "Failed to load picture. Reconnecting in five seconds.";
        Viewer.description.contents.innerHTML = "[" + xmlResponse.status + "] The server appears to be experiencing a high volume of activity. Reconnecting in five seconds.";
        Viewer.events.resize(null);
    },
    
    failed_com : function( xmlResponse )
    {
        if( !Viewer.visible )
            return;
    
        System.Web.setTimer( "com", function() {
                Viewer.comments.contents.innerHTML = ViewerEvents._loading_i + ViewerEvents._loading_t;
                Viewer.events.resize(null);
                System.Xml.createXmlRequest( "com", ViewerEvents.response_com );
                System.Xml.sendXmlRequest( "com", "engine.php?q=" + Viewer.picture.path, ViewerEvents.failed_com, 15000 );
            }, 5000 );
    
        Viewer.comments.contents.innerHTML = "The server request failed from error " + xmlResponse.status + ". Reconnecting in five seconds.";
        Viewer.events.resize(null);
    }
};



var PictureData =
{
    load : function( strHtml )
    {
        // Tokenise the HTML into an arguments list.
        var args = strHtml.split("/");
        
        // Get the number of magnification levels of the picture.
        Viewer.picture.data        = new Object();
        Viewer.picture.data.length = parseInt(args[1]);
        Viewer.picture.data.tiles  = [];
        
        // Construct a map of indices to file names.
        System.Cache["image_files"] = null;
        System.Cache["image_files"] = [];
        var image_index = 0;
        
        // Parse and store picture information.
        for( var i = 2; i < Viewer.picture.data.length + 2; ++i )
        {
            arrTiles = args[i].split(",");
        
            var mx = parseInt(arrTiles[3]);
            var my = parseInt(arrTiles[4]);
            var mz = parseInt(arrTiles[2]);
            var xt = arrTiles[0];
        
            Viewer.picture.data.tiles.push( {
                "ext"  : xt,
                "size" : parseInt(arrTiles[1]),
                "zoom" : mz,
                "cols" : mx,
                "rows" : my,
                "file" : []
            } );
            
            // Generate and cache file names for the tiles.
            for( var x = 0; x < mx; ++x )
            {
                Viewer.picture.data.tiles[i-2].file.push( new Array() );
            
                for( var y = 0; y < my; ++y )
                {
                    Viewer.picture.data.tiles[i-2].file[x].push( image_index );
                    System.Cache["image_files"].push( Viewer.picture.path + "/" + mz + "," + x + "," + y + "." + xt );
                    ++image_index;
                }
            }
        }

        PictureData.reload();
    },

    reload : function( event )
    {
        // Determine the best zoom level for fitting by width.
        var width  = Viewer.clientWidth;
        var height = Viewer.clientHeight;
        
        Viewer.curr.z = Viewer.picture.data.length - 1;
        
        for( var z = 0; z < Viewer.picture.data.length; ++z )
        {
            if( Viewer.picture.data.tiles[z].size * Viewer.picture.data.tiles[z].cols <= width )
            {
                Viewer.curr.z = z;
                break;
            }
        }
        
        Viewer.curr.tile = Viewer.picture.data.tiles[Viewer.curr.z];
        
        // Determine the best coordinates to position the picture.
        var s = Viewer.curr.tile.size;
        var h = Viewer.curr.tile.rows * s;
            w = Viewer.curr.tile.cols * s;
        
        Viewer.curr.x = Math.ceil((width - w) / 2);
        Viewer.curr.y = Math.ceil((height - h) / 2);
        
        // Save the original picture metrics.
        Viewer.curr.ox = Viewer.curr.x;
        Viewer.curr.oy = Viewer.curr.y;
        Viewer.curr.oz = Viewer.curr.z;
        
        // Resize the guide to match the complete picture.
        Viewer.picture.style.width  = (s * Viewer.curr.tile.cols) + "px";
        Viewer.picture.style.height = (s * Viewer.curr.tile.rows) + "px";
        
        Viewer.picture.style.pixelLeft = Viewer.curr.x;
        Viewer.picture.style.pixelTop  = Viewer.curr.y;
        Viewer.picture.style.left      = Viewer.curr.x + "px";
        Viewer.picture.style.top       = Viewer.curr.y + "px";
        
        oX = Viewer.curr.x;//System.Math.getInt( Viewer.picture.style.pixelLeft );
        oY = Viewer.curr.y;//System.Math.getInt( Viewer.picture.style.pixelTop );
        
        // Present the image on the canvas.
        Viewer.picture.ready = true;
        Viewer.picture.resetTiles();
        Viewer.events.resize();
        Viewer.picture.refresh();
    },

    refresh : function( event )
    {
        if( !Viewer.picture.ready )
            return;
        
        var s = Viewer.curr.tile.size;
        var w = Viewer.clientWidth;
        var h = Viewer.clientHeight;
        var c = 0;
        
        var ty = Viewer.curr.tile.rows;
        var tx = Viewer.curr.tile.cols;
        
        var index = 0;
        
        // Prepare tile manipulation maps.
        var map_used = new Object();
        var vec_free = [];
        var vec_left = [];
        
        for( var i = 0; i < Viewer.picture.maxTiles; ++i )
        {
            var tile = System.Cache["tiles"][i];
        
            if( tile.pid > -1 )
                map_used[tile.pid] = tile;
            else
                vec_free.push(tile);
        }
        
        // Scan the coordinates of the image.
        for( var x = System.Math.getInt(Viewer.curr.x); x < w && c < tx; x += s )
        {
            var r = 0;
        
            for( var y = System.Math.getInt(Viewer.curr.y); y < h && r < ty; y += s )
            {
                var pid  = Viewer.curr.tile.file[c][r];
                var tile = map_used[pid];
            
                // If the coordinates are visible, handle tile assignment.
                if( x + s > 0 && y + s > 0 )
                {
                    // If the coordinates are already in use, update the position of the associated tile.
                    if( tile )
                    {
                        //tile.src           = System.Cache["image_files"][pid];
                        tile.style.display = "block";
                        tile.style.left    = x + "px";
                        tile.style.top     = y + "px";
                        map_used[pid]      = null;
                    }
                    // Otherwise, store for later assignment.
                    else
                    {
                        vec_left.push( {"pid": pid, "x": x, "y": y} );
                    }
                }
                // Otherwise, if a tile was previously assigned to these coordinates, free it.
                else if( tile )
                {
                    tile.style.display = "none";
                    map_used[pid] = null;
                    vec_free.push(tile);
                }
                ++r;
            }
            ++c;
        }
        
        // Decommissioned tiles need to be called back to the reserves.
        for( var key in map_used )
        {
            var tile = map_used[key];
        
            if( tile != null )
            {
                tile.style.display = "none";
                vec_free.push(tile);
            }
        }
        
        // Assign pending coordinate assignments to free tiles.
        for( var i = 0; i < vec_left.length; ++i )
        {
            vec_free[i].src           = "engine/pixie.gif";     // Damn you, Opera 9!
            vec_free[i].style.display = "block";
            vec_free[i].pid           = vec_left[i].pid;
            vec_free[i].style.left    = vec_left[i].x + "px";
            vec_free[i].style.top     = vec_left[i].y + "px";
            vec_free[i].src           = System.Cache["image_files"][vec_left[i].pid];
        }
    },
    
    createNewTile : function()
    {
        var tile = document.createElement("img");
                
        tile.src = "";
        tile.alt = "";
        tile.pid = -1;
        tile.className    = "tile";
        tile.style.width  = "256px";
        tile.style.height = "256px";

        System.Cache["tiles"].push( tile );
        Viewer.canvas.insertBefore( tile, Viewer.picture );
        
        return tile;
    },
    
    resetTiles : function()
    {
        for( var i = 0; i < System.Cache["tiles"].length; ++i )
        {
            System.Cache["tiles"][i].src           = "engine/pixie.gif";
            System.Cache["tiles"][i].pid           = -1;
            System.Cache["tiles"][i].style.display = "none";
        }
    },
    
    submit : function( strPathCheck )
    {
        var e = document.getElementById("comments_form_area");
        if( e )
        {
            if( strPathCheck != Viewer.picture.path )
            {
                e.innerHTML = "Asynchronous fault detected.";
                return;
            }
            
            // Validate.
            var k = System.Cache["comments_form_code"].replace( /"/g, "''" ).replace( /[=&<>]+/gi, "+" ).replace(/^\s+|\s+$/g, "" );
            var n = System.Cache["comments_form_name"].replace( /"/g, "''" ).replace( /[=&<>]+/gi, "+" ).replace(/^\s+|\s+$/g, "" );
            var m = System.Cache["comments_form_text"].replace( /"/g, "''" ).replace( /[=&<>]+/gi, "+" ).replace(/^\s+|\s+$/g, "" );
            
            if( k == "" || n == "" || m == "" || k.toLowerCase() == "code" || n.toLowerCase() == "name" || n.toLowerCase() == "comment on this work" )
                return "One or more fields are empty.";
            
            e.innerHTML = ViewerEvents._loading_i + ViewerEvents._loading_s;
            
            // Cache the submitted data in case of failure.
            System.Cache["cpc_data"] = "engine.php?k=" + k + "&n=" + n + "&m=" + m + "&p=" + Viewer.picture.path;
            
            System.Cache["comments_form_code"] = "";
            System.Cache["comments_form_name"] = "";
            System.Cache["comments_form_text"] = "";
            
            System.Xml.createXmlRequest( "cpc", PictureData.post_comments_resp );
            System.Xml.sendXmlRequest( "cpc", System.Cache["cpc_data"], PictureData.post_comments_fail, 10000 );
            Viewer.events.resize();
        }
        
        return null;
    },
    
    post_comments_resp : function( xmlResponse )
    {
        if( xmlResponse )
        {
            // Check for failure message.
            if( xmlResponse.responseText.substring(0,12) == "<!--FAILURE/" )
            {
                var ce = document.getElementById("comments_form_area");
                if( ce )
                    ce.innerHTML = xmlResponse.responseText;
                else
                    Viewer.comments.contents.innerHTML = "<span style='color: #FFCC88'>Browser DOM error encountered. Please close the viewer and try again.</span>";
            }
            else
            {
                Viewer.comments.contents.innerHTML = ViewerEvents._loading_i + ViewerEvents._loading_g;
                System.Xml.createXmlRequest( "com", ViewerEvents.response_com );
                System.Xml.sendXmlRequest( "com", "engine.php?q=" + Viewer.picture.path, ViewerEvents.failed_com, 15000 );
            }
            
            System.Cache["cpc_data"] = null;
            Viewer.events.resize();
        }
    },
    
    post_comments_fail : function( xmlResponse )
    {
        if( !Viewer.visible )
            return;
    
        var e = document.getElementById("comments_form_area");
        if( !e )
            return;

        System.Web.setTimer( "cpc", function() {
                var ce = document.getElementById("comments_form_area");
                if( !ce || !System.Cache["cpc_data"] )
                    return;
                ce.innerHTML = ViewerEvents._loading_i + ViewerEvents._loading_s;
                Viewer.events.resize();
                System.Xml.createXmlRequest( "cpc", PictureData.post_comments_resp );
                System.Xml.sendXmlRequest( "cpc", System.Cache["cpc_data"], PictureData.post_comments_fail, 15000 );
            }, 5000 );
    
        e.innerHTML = "<span style='color: #FFCC88'>The server request failed from error " + xmlResponse.status + ". Reconnecting in five seconds.</span>";
        Viewer.events.resize();
    },
    
    load_comments_form : function( strPathCheck, strName, strMsg )
    {
        var e = document.getElementById("comments_form_area");
        if( e )
        {
            if( strPathCheck != Viewer.picture.path )
            {
                e.innerHTML = "Asynchronous fault detected.";
                return;
            }
        
            e.innerHTML = ViewerEvents._loading_i + ViewerEvents._loading_t;
            System.Xml.createXmlRequest( "cfm", PictureData.load_comments_resp );
            System.Xml.sendXmlRequest( "cfm", "engine.php?c=" + Viewer.picture.path, PictureData.load_comments_fail, 15000 );
        }
    },
    
    load_comments_resp : function( xmlResponse )
    {
        if( !xmlResponse )
            return;

        var e = document.getElementById("comments_form_area");
        if( !e )
            return;
            
        e.innerHTML = xmlResponse.responseText;
        Viewer.events.resize();
    },
    
    load_comments_fail : function( xmlResponse )
    {
        if( !Viewer.visible )
            return;
    
        var e = document.getElementById("comments_form_area");
        if( !e )
            return;
    
        System.Web.setTimer( "cfm", function() {
                var ce = document.getElementById("comments_form_area");
                if( !ce )
                    return;
                ce.innerHTML = ViewerEvents._loading_i + ViewerEvents._loading_t;
                Viewer.events.resize();
                System.Xml.createXmlRequest( "cfm", PictureData.load_comments_resp );
                System.Xml.sendXmlRequest( "cfm", "engine.php?c=" + Viewer.picture.path, PictureData.load_comments_fail, 15000 );
            }, 5000 );
    
        e.innerHTML = "<span style='color: #FFCC88'>The server request failed from error " + xmlResponse.status + ". Reconnecting in five seconds.</span>";
        Viewer.events.resize();
    }
};



function viewer_main()
{
    // Initialise global variables.
    System.Cache["tiles"] = [];
    System.Cache["files"] = [];
    System.Cache["image_files"] = [];
    System.Cache["session_id"]  = -1;
    System.Cache["event_enter"] = false;
    
    System.Cache["comments_form_code"] = "";
    System.Cache["comments_form_name"] = "";
    System.Cache["comments_form_text"] = "";
    
    Viewer = document.getElementById("viewer");
    
    Viewer.curr    = new Object();
    Viewer.surface = document.getElementById("surface");
    Viewer.picture = document.getElementById("picture");
    Viewer.canvas  = document.getElementById("canvas");
    Viewer.wall    = document.getElementById("viewer_wall");
    
    Viewer.controller = document.getElementById("controls");
    
    Viewer.controller.buttons = [];
    Viewer.controller.buttons[0] = document.getElementById("controls_zoom_out");
    Viewer.controller.buttons[1] = document.getElementById("controls_zoom_in");
    Viewer.controller.buttons[2] = document.getElementById("controls_resize");
    Viewer.controller.buttons[3] = document.getElementById("controls_talk");
    Viewer.controller.buttons[4] = document.getElementById("controls_comments");
    Viewer.controller.buttons[5] = document.getElementById("controls_prev");
    Viewer.controller.buttons[6] = document.getElementById("controls_next");
    Viewer.controller.buttons[7] = document.getElementById("controls_close");
    
    Viewer.controller.guide = document.getElementById("controls_status");
    
    Viewer.description          = document.getElementById("description_group");
    Viewer.description.visible  = true;
    Viewer.description.contents = document.getElementById("description_text");
    Viewer.description.back     = document.getElementById("description_back");
    Viewer.description.front    = document.getElementById("description_front");
    Viewer.description.move_up  = document.getElementById("description_scroll_up");
    Viewer.description.move_dn  = document.getElementById("description_scroll_down");
    Viewer.description.move_pos = 0;

    Viewer.comments          = document.getElementById("comments_group");
    Viewer.comments.visible  = false;
    Viewer.comments.contents = document.getElementById("comments_text");
    Viewer.comments.back     = document.getElementById("comments_back");
    Viewer.comments.move_up  = document.getElementById("comments_scroll_up");
    Viewer.comments.move_dn  = document.getElementById("comments_scroll_down");
    Viewer.comments.move_pos = 0;

    // Attach functions and events to the comments container.
    Viewer.comments.submit    = PictureData.submit;
    Viewer.comments.load_form = PictureData.load_comments_form;

    // Attach functions and events to the viewer picture container.
    Viewer.picture.refresh    = PictureData.refresh;
    Viewer.picture.load       = PictureData.load;
    Viewer.picture.reload     = PictureData.reload;
    Viewer.picture.path       = "";
    Viewer.picture.ready      = false;
    Viewer.picture.maxTiles   = 0;
    Viewer.picture.resetTiles = PictureData.resetTiles;
    
    // Attach functions and events to the viewer master object.
    Viewer.events  = ViewerEvents;
    Viewer.load    = ViewerEvents.load;
    Viewer.visible = false;

    // Attach events to the control console.
    System.Web.addEvent( Viewer.controller.buttons[0], "mouseover", function(){ Viewer.controller.guide.innerHTML = "Zoom Out"; } );
    System.Web.addEvent( Viewer.controller.buttons[0], "mouseup", ViewerEvents.zoom_out );
    System.Web.addEvent( Viewer.controller.buttons[1], "mouseover", function(){ Viewer.controller.guide.innerHTML = "Zoom In"; } );
    System.Web.addEvent( Viewer.controller.buttons[1], "mouseup", ViewerEvents.zoom_in );
    System.Web.addEvent( Viewer.controller.buttons[2], "mouseover", function(){ Viewer.controller.guide.innerHTML = "Fit to Canvas"; } );
    System.Web.addEvent( Viewer.controller.buttons[2], "mouseup", ViewerEvents.zoom_fit );
    System.Web.addEvent( Viewer.controller.buttons[3], "mouseover", function(){ Viewer.controller.guide.innerHTML = "Description"; } );
    System.Web.addEvent( Viewer.controller.buttons[3], "mouseup", ViewerEvents.talk );
    System.Web.addEvent( Viewer.controller.buttons[4], "mouseover", function(){ Viewer.controller.guide.innerHTML = "Comments"; } );
    System.Web.addEvent( Viewer.controller.buttons[4], "mouseup", ViewerEvents.comments );
    System.Web.addEvent( Viewer.controller.buttons[5], "mouseover", function(){ Viewer.controller.guide.innerHTML = "Earlier Work"; } );
    System.Web.addEvent( Viewer.controller.buttons[5], "mouseup", ViewerEvents.prev );
    System.Web.addEvent( Viewer.controller.buttons[6], "mouseover", function(){ Viewer.controller.guide.innerHTML = "Newer Work"; } );
    System.Web.addEvent( Viewer.controller.buttons[6], "mouseup", ViewerEvents.next );
    System.Web.addEvent( Viewer.controller.buttons[7], "mouseover", function(){ Viewer.controller.guide.innerHTML = "Close"; } );
    System.Web.addEvent( Viewer.controller.buttons[7], "mouseup", ViewerEvents.close );
    System.Web.addEvent( Viewer.controller, "mouseover", ViewerEvents.guide_show );
    System.Web.addEvent( Viewer.controller, "mouseout", ViewerEvents.guide_hide );

    // Attach functions and events to the viewer control surface.
    Viewer.surface.events = SurfaceEvents;
    
    System.Web.addEvent( Viewer.surface, "mouseover", Viewer.surface.events.enter );
    System.Web.addEvent( Viewer.surface, "mousedown", Viewer.surface.events.down );
    System.Web.addEvent( Viewer.surface, "mouseup",   Viewer.surface.events.up );
    
    System.Web.addEvent( Viewer.wall, "mouseover", Viewer.surface.events.leave );

    // Attach events to the scroll bars.
    System.Web.addEvent( Viewer.description.move_dn, "mouseup", function() {
            Viewer.description.move_pos -= 24;
            Viewer.description.contents.style.top = Viewer.description.move_pos + "px";
        } );

    System.Web.addEvent( Viewer.description.move_up, "mouseup", function() {
            Viewer.description.move_pos += 24;
            Viewer.description.contents.style.top = Viewer.description.move_pos + "px";
        } );
        
    System.Web.addEvent( Viewer.comments.move_dn, "mouseup", function() {
            Viewer.comments.move_pos -= 24;
            Viewer.comments.contents.style.top = Viewer.comments.move_pos + "px";
        } );

    System.Web.addEvent( Viewer.comments.move_up, "mouseup", function() {
            Viewer.comments.move_pos += 24;
            Viewer.comments.contents.style.top = Viewer.comments.move_pos + "px";
        } );
    
    // Attach to the system message queue.
    System.Web.addEvent( window, "resize", Viewer.events.resize );
    
    // Prepare viewer metrics.
    Viewer.events.resize( null );
    SurfaceEvents.enter( null );
    
    Viewer.controller.guide.style.display = "none";
    Viewer.controller.style.height   = "18px";
    Viewer.controller.style.display  = "none";
    
    Viewer.description.style.display = "none";
    Viewer.comments.style.display    = "none";
    Viewer.style.display             = "none";
}

