﻿	var g_ctx;
	var g_ctx_moon;
	var g_ctx_sun;
	var g_ctx_earth;
	var g_processEntire = false;
	var g_t = 0;
	var g_t_chg = 0;
	var g_t_lookup = 0;
	var g_time_apt_arrival = .5;
	var g_time_apt_departure = 1;
	var g_time_avg_speed = 750;
	var g_time_air;
	var g_time_apt;
	var g_jg_plane_srch;
	var g_jg_plane_label;
	var g_ie = navigator.appVersion.match(/MSIE (\d\.\d)/);
	var g_w;
	var g_h;
	var g_w_ems;
	var g_h_ems;
	var g_w_rnd;
	var g_h_rnd;
	var g_currentleg;
	var g_currentsegment;
	var g_plane_rotate_from = 0;
	var g_plane_rotate_to = 0;
	var g_plane_x = -1;
	var g_plane_y = -1;
	var g_total_kms = 0;
	var g_update_leg_info = true;
	var g_paused = false;
	var g_playing = false;
	var g_lastJourney = null;
	var g_airport_ranks = null;
	var g_planeiconurls = new Array();
	var g_planeicons = new Array();

	var g_ma_sprite_map = null;
	var g_ma_sprite_earth = null;
	var g_ma_sprite_sun = null;
	var g_ma_sprite_moon = null;

	var g_animstartedat = null;
	var g_framesrendered = 0;
	
	var g_lastAirportSearch = null;
	var g_selobj = null;
	var g_linkselobj = null;

	var g_map = null;


	// called when the body.onload event fires
	function onloaddone() {
	    //ieCanvasInit();
	    //setTimeout("continue_onload();", 10);
	    continue_onload();
	    // boilerplate google maps initialization
	    if (GBrowserIsCompatible()) {
	        g_map = new GMap2(document.getElementById("map"));
	        g_map.addControl(new GLargeMapControl());
	        g_map.addControl(new GMapTypeControl());
	        g_map.setMapType(G_SATELLITE_MAP);
	        g_map.setCenter(new GLatLng(42.487535, -71.115706), 13);
	    }
	};

	function resize_document() {
	    // resize the "around the earth" container so it fills the space between the input box and the table below
	    var el = document.getElementById("earth_container");
	    var h = el.parentNode.parentNode.offsetHeight + el.parentNode.parentNode.clientTop - el.offsetTop;
	    el.style.height = h + "px";
	    el.style.width = el.clientWidth + "px";

	    // resize the "output table" container so it does not change in size (problem with IE)
	    el = document.getElementById("outtable_container");
	    el.style.width = el.clientWidth + "px";

	    // the canvas element can't be sized with "100%", which
	    // is what we tried to do. simply rewrite the width/height
	    // properties using the parent node's width/height info.
	    el = document.getElementById("canvas");
	    g_w = el.width = el.parentNode.clientWidth;
	    g_h = el.height = el.parentNode.clientHeight;
	    el = document.getElementById("canvas_moon");
	    g_w_ems = el.width = el.parentNode.clientWidth;
	    g_h_ems = el.height = el.parentNode.clientHeight;
	    el = document.getElementById("canvas_sun");
	    el.width = el.parentNode.clientWidth;
	    el.height = el.parentNode.clientHeight;
	    el = document.getElementById("canvas_earth");
	    g_w_rnd = el.width = el.parentNode.clientWidth;
	    g_h_rnd = el.height = el.parentNode.clientHeight;

	    g_jg_plane_srch = new jsGraphics("mainimg");
	    g_jg_plane_label = new jsGraphics("mainimg");

	    if (g_ie) {
	        var c1 = new Canvas({id: 'moocanvas', width: g_w, height: g_h});
	        var c2 = new Canvas({id: 'moocanvas_moon', width: g_w_ems, height: g_w_ems});
	        var c3 = new Canvas({id: 'moocanvas_sun', width: g_w_ems, height: g_w_ems});
	        var c4 = new Canvas({ id: 'moocanvas_earth', width: g_w_rnd, height: g_w_rnd });
	        document.getElementById("mainimg").appendChild(c1);
	        document.getElementById("info_moon_gfx").appendChild(c2);
	        document.getElementById("info_sun_gfx").appendChild(c3);
	        document.getElementById("info_earth_gfx").appendChild(c4);
	        g_ctx = c1.getContext('2d');
	        g_ctx_moon = c2.getContext('2d');
	        g_ctx_sun = c3.getContext('2d');
	        g_ctx_earth = c4.getContext('2d');
	    } else {
	        g_ctx = document.getElementById('canvas').getContext('2d');
	        g_ctx_sun = document.getElementById('canvas_sun').getContext('2d');
	        g_ctx_moon = document.getElementById('canvas_moon').getContext('2d');
	        g_ctx_earth = document.getElementById('canvas_earth').getContext('2d');
    	}
    	g_ctx.strokeStyle = "rgba(255,255,0,.3)";
    	g_ctx.lineWidth = 2;
    	g_ctx_sun.strokeStyle = "rgba(255,0,0,.3)";
    	g_ctx_sun.lineWidth = 2;
    	g_ctx_moon.strokeStyle = "rgba(255,0,0,.3)";
    	g_ctx_moon.lineWidth = 2;
    	g_ctx_earth.strokeStyle = "rgba(255,0,0,.3)";
    	g_ctx_earth.lineWidth = 2;

	    g_ma_sprite_map = new ma_sprite("mainimg");
	    g_ma_sprite_earth = new ma_sprite("info_earth_gfx");
	    g_ma_sprite_sun = new ma_sprite("info_sun_gfx");
	    g_ma_sprite_moon = new ma_sprite("info_moon_gfx");
	    g_ma_sprite_earth_bg = new ma_sprite("info_earth_gfx");
	    g_ma_sprite_earth_bg.showimage(document.getElementById("earth-ico-l"), g_w_rnd / 2 - 32, g_h_rnd / 2 - 32);
	}
	
    function continue_onload() {

        var planecanvas = document.getElementById('planedraw');
        if (planecanvas.toDataURL) {
            var planectx = planecanvas.getContext('2d');
            var img = document.createElement("img");
            img.src = "plane/p-16-000.png";
            for (var i = 0; i < 360; i++) {
                planectx.save();
                planectx.fillStyle = 'rgba(0,0,0,0)';
                planectx.clearRect(0, 0, 16, 16);
                planectx.translate(8, 8);
                planectx.rotate(apt_gc_deg2rad(i));
                planectx.drawImage(img, -8, -8);
                planectx.restore();
                g_planeiconurls[i] = planecanvas.toDataURL();
                g_planeicons[i] = document.createElement("img");
                g_planeicons[i].src = g_planeiconurls[i];
                document.getElementById("plane-preload").appendChild(g_planeicons[i]);
            }
        } else {
            for (var i = 0; i < 360; i++) {
                var a = "000" + i;
                a = a.substr(a.length - 3);
                g_planeiconurls[i] = "plane/p-16-" + a + ".png";
                g_planeicons[i] = document.createElement("img");
                g_planeicons[i].src = g_planeiconurls[i];
                document.getElementById("plane-preload").appendChild(g_planeicons[i]);
            }
        }

        g_combo_maptype = new dhtmlXCombo("combo_maptype_zone", "combo_maptype", 200, 'image');
        var x = new Array();
        x[0] = { value: 0, text: "Shaded Water", img_src: "bg/bg0t.jpg" };
        x[1] = { value: 1, text: "Boring", img_src: "bg/bg1t.jpg" };
        x[2] = { value: 2, text: "Shallow Water", img_src: "bg/bg2t.jpg" };
        x[3] = { value: 3, text: "Night", img_src: "bg/bg3t.jpg" };
        g_combo_maptype.addOption(x);
        g_combo_maptype.readonly(true);
        g_combo_maptype.selectOption(document.getElementById("mapt").value);
        g_combo_maptype.attachEvent("onSelectionChange", onMapSize);

        g_combo_mapsize = new dhtmlXCombo("combo_mapsize_zone", "combo_mapsize", 100);
        var x = new Array();
        x[0] = { value: 0, text: "Small" };
        x[1] = { value: 1, text: "Medium" };
        x[2] = { value: 2, text: "Large" };
        x[3] = { value: 3, text: "Very Large" };
        x[4] = { value: 4, text: "Huge" };
        g_combo_mapsize.addOption(x);
        g_combo_mapsize.readonly(true);
        g_combo_mapsize.selectOption(document.getElementById("maps").value);
        g_combo_mapsize.attachEvent("onSelectionChange", onMapSize);

        show_minmax_button("output_table_minmax", false, "output_table_minmaxthis");
        show_minmax_button("airportrank_minmax", true, "airportrank_minmaxthis");
        show_close_button("apt_lookup_close", "apt_lookup_close();");
        show_close_button("map_close", "hideAirportMap();");
        show_close_button("helptext_close", "hideHelptext();");

        if (readCookie('justsaved')) {
			eraseCookie('justsaved');
			document.getElementById("cookie_note").style.display="block";
        }
		document.getElementById("loading_note").style.display="none";
		document.getElementById("content_widescreen").style.display="block";

        resize_document();
		
        document.getElementById("flights").focus();

        cleanSlate();
		onStop();
		processEntire();
		updateButtons();

		if (!readCookie('reminder_suppress')) {
		    setTimeout("flashreminder('reminder', '#728c40', 50)", 50);
		    createCookie('reminder_suppress', '1', 365);
		}
    }

    function flashreminder(elid, color, counter) {
        var el = document.getElementById(elid);
        if (!el) return;
        var c = flashreminder_gradient("#ffffff", color, 10, counter % 10);
        el.style.backgroundColor = c;
        if (--counter > 0) {
            setTimeout("flashreminder('" + elid + "', '" + color + "', " + counter +")", 50);
        } else {
            el.style.backgroundColor = "transparent";
        }
    }

    function flashreminder_gradient(sf, st, steps, idx) {
        if (sf.substr(0, 1) == "#")
            sf = sf.substr(1);
        if (st.substr(0, 1) == "#")
            st = st.substr(1);
        var rf = flashreminder_fromhex(sf.substr(0, 2));
        var gf = flashreminder_fromhex(sf.substr(2, 2));
        var bf = flashreminder_fromhex(sf.substr(4, 2));
        var rt = flashreminder_fromhex(st.substr(0, 2));
        var gt = flashreminder_fromhex(st.substr(2, 2));
        var bt = flashreminder_fromhex(st.substr(4, 2));
        var r = rf + Math.round((rt - rf) * idx / steps);
        var g = gf + Math.round((gt - gf) * idx / steps);
        var b = bf + Math.round((bt - bf) * idx / steps);
        var s = "#";
        s += flashreminder_tohex(r);
        s += flashreminder_tohex(g);
        s += flashreminder_tohex(b);
        return s;
    }

    function flashreminder_tohex(n) {
        var h = "0123456789abcdef";
        var hp = Math.floor(n/16);
        var lp = n - hp * 16;
        return h.substr(hp, 1) + h.substr(lp, 1);
    }

    function flashreminder_fromhex(s) {
        var h = "0123456789abcdef";
        return h.indexOf(s.substr(0, 1)) * 16 + h.indexOf(s.substr(1, 1));
    }

	function display_plane_progress(jg, ctx, totallen, unitlen, force) {
		var trips = Math.floor(totallen / unitlen);
		var pct = (totallen % unitlen) / unitlen;
		var dir = (trips % 2);
		var y = g_h_ems / 2;
		var x = g_w_ems * (dir ? 1-pct : pct);
		if (!g_processEntire || force) {
		    var angle = dir ? 270 : 90;
			jg.showimage(g_planeicons[angle], x - 8, y - 8);
        }
        if (g_processEntire) {
            if (ctx.apt_last) {
                if (ctx.apt_last_x != x || ctx.apt_last_y != y) {
                    ctx.lineTo(x, y);
                    ctx.apt_segments++;
                }
                if (force || ctx.apt_segments > 10) {
                    ctx.stroke();
                    ctx.beginPath();
                    ctx.moveTo(x, y);
                    ctx.apt_segments = 0;
                }
            } else {
                ctx.apt_last = true;
                ctx.apt_segments = 0;
                ctx.beginPath();
                ctx.moveTo(x, y);
            }
            ctx.apt_last_x = x;
            ctx.apt_last_y = y;
        } else {
            if (ctx.apt_last) {
                if (ctx.apt_last_x != x || ctx.apt_last_y != y) {
                    ctx.beginPath();
                    ctx.moveTo(ctx.apt_last_x, ctx.apt_last_y);
                    ctx.lineTo(x, y);
                    ctx.stroke();
                }
            } else {
                ctx.apt_last = true;
            }
            ctx.apt_last_x = x;
            ctx.apt_last_y = y;
        }
	}

	function display_plane_progress_round(jg, ctx, totallen, unitlen, force) {
		var pct = (totallen % unitlen) / unitlen;
		var r = 48;
		var x = g_w_rnd / 2;
		var y = g_h_rnd / 2;
		var x1 = Math.round(x+r*Math.cos(Math.PI*2*pct-Math.PI/2));
		var y1 = Math.round(y+r*Math.sin(Math.PI*2*pct-Math.PI/2));
		if (!g_processEntire || force) {
		    var angle = angleadd(Math.floor(pct * 360), 90);
			jg.showimage(g_planeicons[angle], x1 - 8, y1 - 8);        }

        if (g_processEntire) {
            if (ctx.apt_last) {
                if (ctx.apt_last_x != x1 || ctx.apt_last_y != y1) {
                    ctx.lineTo(x1, y1);
                    ctx.apt_segments++;
                }
                if (force || ctx.apt_segments > 10) {
                    ctx.stroke();
                    ctx.beginPath();
                    ctx.moveTo(x1, y1);
                    ctx.apt_segments = 0;
                }
            } else {
                ctx.apt_last = true;
                ctx.apt_segments = 0;
                ctx.beginPath();
                ctx.moveTo(x1, y1);
            }
            ctx.apt_last_x = x1;
            ctx.apt_last_y = y1;
        } else {
            if (ctx.apt_last) {
                if (ctx.apt_last_x != x1 || ctx.apt_last_y != y1) {
                    ctx.beginPath();
                    ctx.moveTo(ctx.apt_last_x, ctx.apt_last_y);
                    ctx.lineTo(x1, y1);
                    ctx.stroke();
                }
            } else {
                ctx.apt_last = true;
            }
            ctx.apt_last_x = x1;
            ctx.apt_last_y = y1;
        }
	}

	function show_plane(x, y, angle) {
		if (x == -1 || y == -1) {
			x = g_plane_x;
			y = g_plane_y;
		}
		angle = Math.floor(angle);
		g_ma_sprite_map.showimage(g_planeicons[angle], x - 8, y - 8);
		g_plane_x = x;
		g_plane_y = y;
	}

	function newleginfo(idx) {
        var leg = apt_leg_get(idx);
		if (!g_processEntire) {
			document.getElementById("output_table_stead").style.display = "none";
			document.getElementById("output_table").style.display = "block";
			textarea_setSelection(document.getElementById("flights"), leg.charidx_src, leg.charidx_dst - leg.charidx_src + apt_get_code(leg.idx_dst).length);
		}
		var tbody = document.getElementById("out_tbody");
		if (!tbody.hasChildNodes()) {
			append_out_row(tbody, "Segment", "From", "To", "Time", "Miles");
		}
        var kms = apt_gc_dist(apt_get_lonlat(leg.idx_src), apt_get_lonlat(leg.idx_dst));
        var tm = kms / g_time_avg_speed;
        var miles = Math.round(kms / 1.609344);
		append_out_row(tbody, 
		    htmlEncode((idx + 1) + "."),
		    "<a href=\"javascript:showAirportMap(" + leg.idx_src + ")\">" + htmlEncode(apt_get_code(leg.idx_src) + " (" + apt_get_desc(leg.idx_src) + ")") + "</a>",
		    "<a href=\"javascript:showAirportMap(" + leg.idx_dst + ")\">" + htmlEncode(apt_get_code(leg.idx_dst) + " (" + apt_get_desc(leg.idx_dst) + ")") + "</a>", 
		    htmlEncode(timespanformat(tm)),
		    htmlEncode(numberformat(miles)));
		update_leginfo_title();
		if (leg.legidx == 0)
		    airportrank_add(leg.idx_src);
		airportrank_add(leg.idx_dst);
		if (!g_processEntire)
		    airportrank_dump();
		if (!g_processEntire) {
		    g_jg_plane_label.clear();
		    showAirportLabel(leg.idx_src);
		    showAirportLabel(leg.idx_dst);
		    setMsg("<a href=\"javascript:showAirportMap(" + leg.idx_dst + ")\">" + htmlEncode(apt_get_code(leg.idx_dst) + " (" + apt_get_desc(leg.idx_dst) + ")") + "</a>");
		    g_jg_plane_label.paint();
		}
    }

    function showAirportLabel(idx) {
        var code = apt_get_code(idx);
        var coord = apt_get_lonlat(idx);
        var x = Math.round(coord.lon / 360 * g_w + g_w / 2);
        var y = Math.round(coord.lat / 180 * g_h + g_h / 2);
        x -= 16;
        y += 8;

        g_jg_plane_label.setColor("#000000");
        for (var i = -2; i <= 2; i++) {
            for (var j = -2; j <= 2; j++) {
                //hackhack
                g_jg_plane_label.drawString(code, x + i, y + j);
            }
        }
        g_jg_plane_label.setColor("#8080ff");
        g_jg_plane_label.drawString(code, x, y);
    }

    function showAirportMap(idx) {
        g_map.clearOverlays();
        var point = new GLatLng(-apt_get_lat(idx), apt_get_lon(idx));
        g_map.setCenter(point, 13);
        var el = document.getElementById("map_container");
        el.style.top = (document.documentElement.scrollTop + 100) + "px";
        el.style.visibility = "visible";
        document.getElementById("mapinfo_iata").innerHTML = htmlEncode(apt_get_iata(idx));
        document.getElementById("mapinfo_icao").innerHTML = htmlEncode(apt_get_icao(idx));
        document.getElementById("mapinfo_faa").innerHTML = htmlEncode(apt_get_faa(idx));
        document.getElementById("mapinfo_name").innerHTML = htmlEncode(apt_get_name(idx));
        document.getElementById("mapinfo_city").innerHTML = htmlEncode(apt_get_city(idx));
        document.getElementById("mapinfo_country").innerHTML = htmlEncode(apt_get_country(idx));
        document.getElementById("mapinfo_state").innerHTML = htmlEncode(apt_get_state(idx));
    }

    function hideAirportMap() {
        document.getElementById("map_container").style.visibility = "hidden";
    }

    function reset_leginfo() {
        var tbody = document.getElementById("out_tbody");
        if (tbody.hasChildNodes()) {
            while (tbody.childNodes.length > 0) {
                tbody.removeChild(tbody.firstChild);
            }
        }
        document.getElementById("output_table_stead").style.display = "block";
        document.getElementById("output_table").style.display = "none";
        update_leginfo_title();
    }
    
    function update_leginfo_title() {
        var el = document.getElementById("outtable_head");
        var cn = document.getElementById("out_tbody").childNodes.length - 1;
        if (cn > 0)
            el.innerHTML = htmlEncode("Entire journey (" + cn + " flight" + (cn > 1 ? "s" : "") + ")");
        else
            el.innerHTML = htmlEncode("Entire journey");
    }
    
	function airportrank_add(idx) {
        var code = apt_get_code(idx);
        if (g_airport_ranks[code]) {
            g_airport_ranks[code]++;        
        } else {
            g_airport_ranks[code] = 1;
	    }
	}

    function airportrank_sort(a, b) {
        return b.visits - a.visits;
    }

    function airportrank_getflat() {
        var flat = new Array();
        for (var i in g_airport_ranks) {
            if (typeof i == "string") {
                if (typeof g_airport_ranks[i] == "number") {
                    var o = new Object();
                    o.code = i;
                    o.visits = g_airport_ranks[i];
                    flat[flat.length] = o;
                }
	        }
	    }
        return flat;
    }

    function airportrank_reset() {
        g_airport_ranks = new Array();
        airportrank_dump();
    }
    
	function airportrank_dump() {
	    var arr = airportrank_getflat();
	    arr = arr.sort(airportrank_sort);

	    var tbody = document.getElementById("airportrank_tbody");
	    if (tbody.hasChildNodes()) {
	        while (tbody.childNodes.length > 0) {
	            tbody.removeChild(tbody.firstChild);
	        }
	    }

	    var n = Math.min(12, arr.length);
	    if (n) {
	        document.getElementById("airportrank_table").style.display = "block";
	        document.getElementById("airportrank_stead").style.display = "none";
	        var tr = document.createElement('tr');
	        var w = Math.round(100 / n) + "%";
	        for (var i = 0; i < n; i++) {
	            var h_total = 100;
	            var h_f = Math.round(arr[i].visits / arr[0].visits * h_total);
	            var h_e = h_total - h_f;
	            var t = "";
	            t += "<div class=\"airportrank_empty\" style=\"height:" + h_e + "px;\"></div>";
	            t += "<div class=\"airportrank_full\" style=\"height:" + h_f + "px;\"></div>";
	            t += "<a href=\"javascript:showAirportMap(";
	            t += apt_get_idx(arr[i].code);
	            t += ")\">";
	            t += arr[i].code + "<br/>" + arr[i].visits + "&nbsp;stops";
	            t += "</a>";
	            var td = document.createElement('td');
	            td.style.paddingRight = "2em";
	            td.style.width = w;
	            td.style.textAlign = "center";
	            td.innerHTML = t;
	            tr.appendChild(td);
	        }
	        tbody.appendChild(tr);
	    } else {
	        document.getElementById("airportrank_table").style.display = "none";
	        document.getElementById("airportrank_stead").style.display = "block";
    	}
	}

	function append_out_row(tbody, t0, t1, t2, t3, t4) {
		var tr = document.createElement('tr');
		var td0 = document.createElement('td');
		var td1 = document.createElement('td');
		var td2 = document.createElement('td');
		var td3 = document.createElement('td');
		var td4 = document.createElement('td');
		td0.style.textAlign = "right";
		td1.style.textAlign = "left";
		td2.style.textAlign = "left";
		td3.style.textAlign = "right";
		td4.style.textAlign = "right";
		td0.style.verticalAlign = "top";
		td1.style.verticalAlign = "top";
		td2.style.verticalAlign = "top";
		td3.style.verticalAlign = "top";
		td4.style.verticalAlign = "top";
		td0.style.paddingRight = "2em";
		td1.style.paddingRight = "2em";
		td2.style.paddingRight = "2em";
		td3.style.paddingRight = "2em";
		td4.style.paddingRight = "2em";
		tr.appendChild(td0);
		tr.appendChild(td1);
		tr.appendChild(td2);
		tr.appendChild(td3);
		tr.appendChild(td4);
		td0.innerHTML = t0;
		td1.innerHTML = t1;
		td2.innerHTML = t2;
		td3.innerHTML = t3;
		td4.innerHTML = t4;
		tbody.appendChild(tr);
	}
	
  function heartbeat() {
  	if (g_paused) {
  		return false;
  	}
  	if (!g_playing) {
  		return false;
  	}
  	if (g_update_leg_info) {
  	    if (apt_leg_get(g_currentleg).legidx == 0) {
  	        g_time_apt += g_time_apt_departure;
  	    }
  		g_update_leg_info = 0;
 		newleginfo(g_currentleg);
  	}
  	// rotate the plane if the we're departing from the same airport
  	if (g_plane_rotate_from != g_plane_rotate_to && !g_processEntire) {
  		var step = 30;
  		var diff = anglesub(g_plane_rotate_to, g_plane_rotate_from);
  		if (diff > step) {
	  		if (diff > 180) step *= -1;
	  		g_plane_rotate_from = angleadd(g_plane_rotate_from, step);
	  		show_plane(-1, -1, g_plane_rotate_from);
			g_t = setTimeout("heartbeat();", 50);
  			return true;
  		}
  	}
  	
  	// get current leg and segments
  	var leg = apt_leg_get(g_currentleg);
  	var seg1 = leg.segments[g_currentsegment  ];
  	var seg2 = leg.segments[g_currentsegment+1];
  	
  	var kms = apt_gc_dist(seg1, seg2);
  	var tm = kms / g_time_avg_speed;
  	g_time_air += tm;
  	g_total_kms += kms;

	update_stats();

	// convert gc lonlat pairs to screen coords  		
	var x1 = seg1.lon / 360 * g_w + g_w / 2;
	var y1 = seg1.lat / 180 * g_h + g_h / 2;
	var x2 = seg2.lon / 360 * g_w + g_w / 2;
	var y2 = seg2.lat / 180 * g_h + g_h / 2;

	// if a line is too long that means we're passing through the edge of the map so dont draw
	var asqrd = (x1 - x2)*(x1 - x2);
	var bsqrd = (y1 - y2)*(y1 - y2);
	var csqrd = (g_w/20)*(g_w/20);
	if ( asqrd + bsqrd < csqrd ) {
        if (g_processEntire) {
            if (x1 != x2 || y1 != y2) {
                if (g_ctx.apt_last) {
                    if ( (g_ctx.lastcoord_x == x1 && g_ctx.lastcoord_y == y1) && (g_ctx.apt_segments <= 10)) {
                        g_ctx.lineTo(x2, y2);
                        g_ctx.apt_segments++;
                    } else {
                        g_ctx.stroke();
                        g_ctx.beginPath();
                        g_ctx.moveTo(x1, y1);
                        g_ctx.lineTo(x2, y2);
                        g_ctx.apt_segments = 0;
                    }
                } else {
                    g_ctx.apt_last = true;
                    g_ctx.apt_segments = 0;
                    g_ctx.beginPath();
                    g_ctx.moveTo(x1, y1);
                    g_ctx.lineTo(x2, y2);
                }
                g_ctx.lastcoord_x = x2;
                g_ctx.lastcoord_y = y2;
            }
        } else {
            g_ctx.beginPath();
            g_ctx.moveTo(x1, y1);
            g_ctx.lineTo(x2, y2);
            g_ctx.stroke();
            show_plane(x2, y2, apt_gc_angle(seg1, seg2));
        }
	}

	// next gc segment in this leg
    g_currentsegment++;
    if (g_currentsegment >= leg.segments.length - 1) {
	    // ok leg over, next leg
	    g_currentsegment = 0;
	    g_currentleg++;
	    g_update_leg_info = 1;
	    g_time_apt += g_time_apt_arrival;
        //append_out_row(document.getElementById("out_tbody"), "", "arrival: added " + g_time_apt_arrival + " hours");
	    if (g_currentleg >= apt_leg_count()) {
		    // whole journey is over
		    if (g_processEntire) {
		        g_ctx.stroke();
		        update_stats(true);
			    show_plane(x2, y2, apt_gc_angle(seg1, seg2));
			    document.getElementById("output_table_stead").style.display = "none";
			    document.getElementById("output_table").style.display = "block";
			    g_jg_plane_label.clear();
			    showAirportLabel(leg.idx_src);
			    showAirportLabel(leg.idx_dst);
			    setMsg("<a href=\"javascript:showAirportMap(" + leg.idx_dst + ")\">" + htmlEncode(apt_get_code(leg.idx_dst) + " (" + apt_get_desc(leg.idx_dst) + ")") + "</a>");
			    g_jg_plane_label.paint();
			}
		    onStop();
		    airportrank_dump();
		    return false;
	    }
	    if (!g_processEntire) {
  		    // determine if the plane needs to be rotated
	  	    var nextleg = apt_leg_get(g_currentleg);
	  	    if (leg.idx_dst == nextleg.idx_src) {
	  		    // yes, figure out from/to angles
  			    g_plane_rotate_from = Math.floor(apt_gc_angle(seg1, seg2));
		  	    seg1 = nextleg.segments[g_currentsegment  ];
		  	    seg2 = nextleg.segments[g_currentsegment+1];
	  		    g_plane_rotate_to = Math.floor(apt_gc_angle(seg1, seg2));
	  	    }
	    }
    }
	if (!g_processEntire)
		g_t = setTimeout("heartbeat();", 50);
	return true;
  }

	function update_stats(force) {
		if (!g_processEntire || force) {
			var total_kms = "N/A";
			var total_mls = "N/A";
			var total_time_air = "N/A";
			var total_time_apt = "N/A";
			var total_time_tot = "N/A";
			var info_earth = "N/A";
			var info_sun = "N/A";
			var info_moon = "N/A";
			if (g_total_kms > 0) total_kms = numberformat(Math.round(g_total_kms));
			if (g_total_kms > 0) total_mls = numberformat(Math.round(g_total_kms / 1.609344));
			if (g_time_air > 0) total_time_air = timespanformat(Math.round(g_time_air));
			if (g_time_apt > 0) total_time_apt = timespanformat(Math.round(g_time_apt));
			if (g_time_air + g_time_apt > 0) total_time_tot = timespanformat(Math.round(g_time_apt + g_time_air));
			if (g_total_kms) info_earth = numberformat(Math.round(g_total_kms / 40075.16 * 1000) / 1000) + " times";
			if (g_total_kms) info_moon = numberformat(Math.round(g_total_kms / 384403.1 * 1000) / 1000) + " times";
			if (g_total_kms) info_sun = numberformat(Math.round(g_total_kms / 149669180 * 1000) / 1000) + " times";
			if (0) {
			    var fps = "N/A";
			    var playing_for = (new Date()).getTime() - g_animstartedat;
			    if ((++g_framesrendered) > 0 && playing_for > 0) {
			        fps = Math.round(g_framesrendered * 1000000 / playing_for);
			        fps = fps / 1000;
			        fps += " FPS (";
			        fps += g_framesrendered;
			        fps += ")";
			    }
			    document.getElementById("info_fps").innerHTML = fps;
			}
			document.getElementById("info_kms").innerHTML = total_kms;
	  	    document.getElementById("info_miles").innerHTML = total_mls;
	  	    document.getElementById("info_time_air").innerHTML = total_time_air;
	  	    document.getElementById("info_time_apt").innerHTML = total_time_apt;
	  	    document.getElementById("info_time_tot").innerHTML = total_time_tot;
	  	    document.getElementById("info_earth_txt").innerHTML = "Earth circumnavigation: " + info_earth;
	  	    document.getElementById("info_moon_txt").innerHTML = "Distance to the Moon: " + info_moon;
	  	    document.getElementById("info_sun_txt").innerHTML = "Distance to the Sun: " + info_sun;
		}
  	    display_plane_progress(g_ma_sprite_moon, g_ctx_moon, g_total_kms, 384403.1, force);
  	    display_plane_progress(g_ma_sprite_sun, g_ctx_sun, g_total_kms, 149669180, force);
  	    display_plane_progress_round(g_ma_sprite_earth, g_ctx_earth, g_total_kms, 40075.16, force);
  	}
	
	function preprocess(str) {
		return str.replace(/\r/g, '');
	}
	
	function onTextChange() {
		clearTimeout(g_t_chg);
		g_t_chg = setTimeout("processText();", 200);
	}
	
	function processText() {
		clearTimeout(g_t_chg);
		g_t_chg = 0;
		var v = preprocess(document.getElementById("flights").value).trim();
	    if (g_lastJourney == v) {
	        return;
	    }
	    g_lastJourney = v;
		processEntire();
	}
	
	function processEntire() {
		g_processEntire = true;
		onStart();
	}

	function onGo() {
	    if (document.getElementById("start_btn").getAttribute("status") != "enabled")
	        return;
	    document.getElementById("cookie_note").style.display = "none";
	    if (g_playing) {
	        processEntire();
	    } else {
	        g_processEntire = false;
	        onStart();
    	}
	}
	
	function setMsg(s, flash) {
		if (s.length == 0) {
			document.getElementById("infopop").style.visibility = "hidden";
		} else {
		    if (typeof flash == undefined)
		        flash = false;
		    if (flash)
		        s = '<span id="flashme">' + s + '</span>';
			document.getElementById("infopop").innerHTML = s;
			document.getElementById("infopop").style.visibility = "visible";
			setTimeout("flashreminder('flashme', '#728c40', 20)", 50);
		}
	}
	
	function getNextAirport(so) {
	    var i = so.idx;
	    // find beginning of next useful char
	    var commentMode = false;
	    while (i < so.text.length) {
	        var c = so.text.substr(i, 1);
	        if (c == '\n') {
	            commentMode = false;
	            so.legidx = 0;
            }
	        if (!commentMode) {
	            if (isalnum(c)) {
	                // found it
	                so.code = c;
	                so.code_idx = i;
	                i++;
	                while (i < so.text.length) {
	                    var c = so.text.substr(i, 1);
	                    i++;
	                    if (isalnum(c) || c == ':') {
	                        so.code += c;
	                    } else {
	                        so.idx = i - 1;
	                        return true;
	                    }
	                }
	                so.idx = i;
	                return true;
	            }
            }
	        if (c == '#')
	            commentMode = true;
	        i++;
	    }
	    return false;
	}
	
	function onStart() {
		cleanSlate();
		onStop();
		setMsg("");
		var searchobj = new Object();
		searchobj.text = preprocess(document.getElementById("flights").value);
		searchobj.idx = 0;
		searchobj.legidx = 0;
		searchobj.code = "";
		searchobj.code_idx = 0;
		var ret = 1;
		apt_reset();
		while (getNextAirport(searchobj)) {
		    if (searchobj.legidx == 0) {
		        ret = apt_journey_start(searchobj.code, searchobj.code_idx);
		    } else {
		        ret = apt_journey_next(searchobj.code, searchobj.code_idx);
		    }
		    if (!ret) {
		        var err = "Unknown airport \"";
		        err += "<a href=\"javascript: g_selobj = g_linkselobj; onLookup('" + escape(searchobj.code) + "');\">";
		        err += htmlEncode(searchobj.code);
		        err += "</a>";
		        err += "\". <a href=\"javascript: g_selobj = g_linkselobj; onLookup('" + escape(searchobj.code) + "');\">";
		        err += "Look it up.";
		        err += "</a>";
			    setMsg(err, true);
			    g_linkselobj = new Object();
			    g_linkselobj.text = searchobj.code;
			    g_linkselobj.idx = searchobj.code_idx;
		        if (!g_processEntire)
		            textarea_setSelection(document.getElementById("flights"), searchobj.code_idx, searchobj.code.length);
			    return;
		    }
		    searchobj.legidx++;
		}
		apt_journey_finish();
		if (apt_leg_count() > 0) {
			g_playing = true;
			updateButtons();
			if (g_processEntire) {
				while(heartbeat()) {};
			} else {
				g_t = setTimeout("heartbeat();", 50);
			}
		}
	}
	
	function onPause() {
	    if (document.getElementById("pause_btn").getAttribute("status") != "enabled")
	        return;
	    if (g_paused) {
			g_paused = false;
			g_t = setTimeout("heartbeat();", 50);
			updateButtons();
		} else {
			g_paused = true;
			updateButtons();
			clearTimeout(g_t);
			g_t = 0;
		}
	}
	
	function onStop() {
		g_paused = false;
		g_playing = false;
		updateButtons();
		clearTimeout(g_t);
		g_t = 0;
	}
	
	function cleanSlate() {
		g_currentleg = 0;
		g_currentsegment = 0;
		g_plane_rotate_from = 0;
		g_plane_rotate_to = 0;
		g_total_kms = 0;
		g_time_air = 0;
		g_time_apt = 0;
		g_update_leg_info = true;
		g_framesrendered = 0;
		g_animstartedat = (new Date()).getTime();
		update_stats(true);
		// zero again so the increment we've done within update_stats is gone
		g_framesrendered = 0;
		g_ctx.clearRect(0, 0, g_w, g_h);
		g_ctx.drawImage(document.getElementById("backgroundimage"), 0, 0);
		g_ctx.apt_last = false;
		g_ctx_moon.clearRect(0, 0, g_w_ems, g_h_ems);
		g_ctx_moon.apt_last = false;
		g_ctx_sun.clearRect(0, 0, g_w_ems, g_h_ems);
		g_ctx_sun.apt_last = false;
		g_ctx_earth.clearRect(0, 0, g_w_rnd, g_h_rnd);
		g_ctx_earth.apt_last = false;
		g_ma_sprite_map.clear();
  	    airportrank_reset();
  	    reset_leginfo();
  	    g_jg_plane_label.clear();
	}
	
	function updateButtons() {
        if (g_paused) {
            document.getElementById("pause_btn_img").src = "button/button-play-024.png";
            tooltip_ctl_pause_txt = "Resume playing";
        } else {
            document.getElementById("pause_btn_img").src = "button/button-pause-024.png";
            tooltip_ctl_pause_txt = "Pause playing";
        }
        if (g_playing) {
            document.getElementById("start_btn_img").src = "button/button-stop-024.png";
            tooltip_ctl_go_txt = "Stop playing";
        } else {
            document.getElementById("start_btn_img").src = "button/button-play-024.png";
            tooltip_ctl_go_txt = "Start playing";
        }

        tooltip_ctl_save_txt = "Save your entries so you can come back to them later";
        tooltip_ctl_lookup_txt = "Airport lookup tool";
        
        document.getElementById("pause_btn").setAttribute("status", g_playing ? "enabled" : "disabled");
        document.getElementById("save_btn").setAttribute("status", (g_playing && !g_paused) ? "disabled" : "enabled");
        document.getElementById("start_btn").setAttribute("status", "enabled");
        document.getElementById("lookup_btn").setAttribute("status", "enabled");
    }

	function onSave(displaynote) {
	    if (document.getElementById("save_btn").getAttribute("status") != "enabled")
	        return;
	    var box = preprocess(document.getElementById("flights").value);
		box = escape(box);
		var url = "airpaths.aspx?b=";
		url += box;
		url += "&s=" + escape(g_combo_mapsize.getSelectedValue());
		url += "&t=" + escape(g_combo_maptype.getSelectedValue());
		if (displaynote)
		    createCookie('justsaved', '1');
		window.location = url;
	}
	
	function onMapSize() {
        onSave(false);
	}

	function apt_lookup() {
		document.getElementById("cookie_note").style.display="none";
		clearTimeout(g_t_lookup);
		g_t_lookup = setTimeout("processAirportLookup(false);", 200);
	}

    function positionLookupBox() {
        // position the lookup box over "earth_container"
		var el1 = document.getElementById("earth_container");
        var el2 = document.getElementById("apt_lookup");
//        var x = 0;
//        var el3 = el;
//        while (el3 != el2.parentNode) {
//            if (el3.offsetLeft)
//                x += el3.offsetLeft;
//            el3 = el3.parentNode;
//        }
//        var y = 0;
//        el3 = el;
//        while (el3 != el2.parentNode) {
//            if (el3.offsetTop)
//                y += el3.offsetTop;
//            el3 = el3.parentNode;
//        }
//        x += el.offsetWidth - el2.offsetWidth;
        el2.style.left = el1.offsetLeft + "px";
        el2.style.top = el1.offsetTop + "px";
        el2.style.width = el1.offsetWidth + "px";

//        var el = document.getElementById("apt_lookup_out");
//        el.top = el.offsetTop + "px";
//        el.height = el.offsetHeight + "px";
//        el.width = el.offsetWidth + "px";

//        var el2 = document.getElementById("apt_lookup_tbody");
//        el2.top = el.offsetTop + "px";
//        el2.height = el.offsetHeight + "px";
//        el2.width = el.offsetWidth + "px";

//        var el3 = document.getElementById("apt_lookup_table");
//        el3.top = el.offsetTop + "px";
//        el3.height = el.offsetHeight + "px";
//        el3.width = el.offsetWidth + "px";
    }
    
    function processAirportLookup(fullSearch) {
        positionLookupBox();
		clearTimeout(g_t_lookup);
		g_t_lookup = 0;
		var v = document.getElementById("apt_lookup_input").value.trim();
		if (v.length > 32)
		    v = v.substr(0, 32);
		if (!fullSearch)
		    if (v == g_lastAirportSearch)
		        return;
		g_lastAirportSearch = v;
        var lookfor = v.toLowerCase();
		var tbody = document.getElementById("apt_lookup_tbody");
		if (tbody.hasChildNodes()) {
	        while (tbody.childNodes.length > 0) {
	            tbody.removeChild(tbody.firstChild);       
	        } 
		}
        g_jg_plane_srch.clear();
        g_jg_plane_label.clear();
		var rows_added = 0;
		var results_found = 0;
		if (lookfor.length > 0) {
		    for (var i = 0; i < apt_get_count(); i++) {
		        var desc = apt_get_desc(i);
                var code = apt_get_code(i);
                if (findSearchString(code, lookfor) >=0 ||
                    findSearchString(desc, lookfor) >=0)
                {
                    if (rows_added < 5 || fullSearch) {
                        var code2 = findAndMarkupSearchString(code, lookfor);
                        var desc2 = findAndMarkupSearchString(desc, lookfor);
                        code2 = "<a href=\"javascript:processLookupResult('" + escape(code) + "');\" onmouseover=\"javascript:doHover(" + i + "); Tip('" + jsEncode(desc2) + "' ,DELAY, 0, WIDTH, -300); \" onmouseout=\"javascript:unHover(); UnTip();\">" + code + "</a>";
                        append_lookup_row(tbody, code2, desc2);
                        rows_added++;
                        
                        var c = apt_get_lonlat(i);
		                var x = c.lon / 360 * g_w + g_w / 2;
		                var y = c.lat / 180 * g_h + g_h / 2;
		                g_jg_plane_srch.drawImage(g_planeiconurls[0], x - 8, y - 8);
                    }
                    results_found++;
                }
            }
		}
        var t = "";
        if (lookfor.length > 0) {
            if (results_found > 0) {
                if (results_found > rows_added)
                    t = rows_added + " of "; 
                t += results_found + " airports containing \"<b>" + htmlEncode(v) + "</b>\".";
                if (!fullSearch) {
                    if (results_found > rows_added && results_found < 300) {
                        t = "<a href=\"javascript:processAirportLookup(true);\">" + t + " Click to see all.</a>";
                    }
                }
            } else {
                t = "\"<b>" + htmlEncode(v) + "</b>\" not found";
            }
        }
        document.getElementById("apt_lookup_sum").innerHTML = t;
        g_jg_plane_srch.paint();
    }

    function findSearchString(where, what) {
        return where.toLowerCase().indexOf(what);
    }

    function findAndMarkupSearchString(where, what) {
        var idx = findSearchString(where, what);
        if (idx >= 0) {
            where = htmlEncode(where.substr(0, idx)) + "<b>" + htmlEncode(where.substr(idx, what.length)) + "</b>" + htmlEncode(where.substr(idx + what.length));
            return where;
        }
        return where;
    }

	function append_lookup_row(tbody, t0, t1) {
		var tr = document.createElement('tr');
		var td0 = document.createElement('td');
		var td1 = document.createElement('td');
		td0.className = "lookuptd0";
		td1.className = "lookuptd1";
		td0.innerHTML = (t0);
		td1.innerHTML = (t1);
		tr.appendChild(td0);
		tr.appendChild(td1);
		tbody.appendChild(tr);
	}

    function onLookup(s) {
        if (document.getElementById("lookup_btn").getAttribute("status") != "enabled")
            return;
        var l = document.getElementById("apt_lookup_input");
        l.value = "";
        if (s) {
            l.value = unescape(s);
        } else {
            g_selobj = textarea_getSelection(document.getElementById("flights"));
            l.value = g_selobj.text;
        }
        var l2 = document.getElementById("apt_lookup");
        l2.style.visibility = "visible";
        textarea_setSelection(l, 0, l.value.length);
        processAirportLookup(false);
        l.focus();
    }
    
    function apt_lookup_close() {
        var l = document.getElementById("apt_lookup");
        l.style.visibility = "hidden";
        g_jg_plane_srch.clear();
        g_jg_plane_label.clear();
    }

    function hideHelptext() {
        document.getElementById('helptext').style.display = 'none';
    }
    
    function doHover(idx) {
        g_jg_plane_label.clear();
        showAirportLabel(idx);
        g_jg_plane_label.paint();
    }
    
    function unHover() {
        g_jg_plane_label.clear();
    }
    
    function processLookupResult(code) {
        if (g_selobj) {
            apt_lookup_close();
            var textarea = document.getElementById("flights");
            textarea_setSelection(textarea, g_selobj.idx, g_selobj.text.length);
            textarea_replaceSelection(textarea, code);
            onTextChange();
        }
    }

    function show_minmax_button(divname, ismin, targetdivname) {
        var el = document.getElementById(divname);
        if (el) {
            var htm = "<a href=\"javascript:UnTip(); do_minmax('" + divname + "', '" + targetdivname + "');\" class=\"imagebutton16\" status=\"enabled\" onmouseover=\"Tip('" + (ismin ? "Hide" : "Show") + " this section')\" onmouseout=\"UnTip()\">";
            htm += "<img src=\"button/button-" + (ismin ? "minimize" : "maximize") + "-016.png\"/>";
            htm += "<img src=\"button/shine-016.png\" class=\"shine\"/>";
            htm += "<img src=\"button/grayout-016.png\" class=\"grayout\"/>";
            htm += "<img src=\"button/depress-016.png\" class=\"depress\"/>";
            htm += "</a>";
            el.innerHTML = htm;
        }
    }

    function show_close_button(divname, jsexec) {
        var el = document.getElementById(divname);
        if (el) {
            var htm = "<a href=\"javascript:UnTip(); " + jsexec +"\" class=\"imagebutton16\" status=\"enabled\" onmouseover=\"Tip('Close this section')\" onmouseout=\"UnTip()\">";
            htm += "<img src=\"button/button-close-016.png\"/>";
            htm += "<img src=\"button/shine-016.png\" class=\"shine\"/>";
            htm += "<img src=\"button/grayout-016.png\" class=\"grayout\"/>";
            htm += "<img src=\"button/depress-016.png\" class=\"depress\"/>";
            htm += "</a>";
            el.innerHTML = htm;
        }
    }

    function do_minmax(divname, targetdivname) {
        var el = document.getElementById(targetdivname);
        if (el) {
            if (el.style.display == "none") {
                el.style.display = "block";
                show_minmax_button(divname, true, targetdivname);
            } else {
                el.style.display = "none";
                show_minmax_button(divname, false, targetdivname);
            }
        }
    }

    function do_close(divname, targetdivname) {
        var el = document.getElementById(targetdivname);
        if (el) {
        	el.style.visibility = "hidden";
        }
    }

    function setPath(s) {
        document.getElementById("flights").value = s;
        onTextChange();
    }
    
    function createCookie(name, value, days) {
        if (days) {
            var date = new Date();
            date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
            var expires = "; expires=" + date.toGMTString();
        }
        else var expires = "";
        document.cookie = name + "=" + value + expires + "; path=/";
    }

    function readCookie(name) {
        var nameEQ = name + "=";
        var ca = document.cookie.split(';');
        for (var i = 0; i < ca.length; i++) {
            var c = ca[i];
            while (c.charAt(0) == ' ') c = c.substring(1, c.length);
            if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);
        }
        return null;
    }

    function eraseCookie(name) {
        createCookie(name, "", -1);
    }

    var tooltip_info_earth_txt = "Based on the Earth's equatorial circumference of 40,075.16 kilometers.";
    var tooltip_info_moon_txt = "Based on the average Earth-Moon center-to-center distance of 384,403 kilometers.";
    var tooltip_info_sun_txt = "Based on the average Earth-Sun distance of 149,669,180 kilometers.";
    var tooltip_info_kms = "Based on the shortest distance over the Earth's surface between the respective airports. (The corresponding Great Circle segment.) In reality the amount flown is always more, sometimes significantly. Even after accounting for direction correction after takeoff and before landing, the route taken by an aircraft can significantly diverge from the shortest path - for example, to avoid certain weather systems.";
    var tooltip_info_miles = "See above for explanation. This is simply the number of kilometers divided by 1.609,344.";
    var tooltip_info_time_air = "This is an approximation based on an average aircraft speed of 750 kilometers per hour. The cruising speed of large commercial aircraft can be somewhat more, but smaller airplanes are usually slower.";
    var tooltip_info_time_apt = "Based on spending one hour before every flight at the airport, and half an hour after arrival. Transfers count as arrivals. So if your flight is MUC-FRA-JFK, you will spend one hour at MUC, half an hour at FRA, and half an hour at JFK. If your flights are entered separately as MUC-FRA then FRA-JFK, then two departures and two arrivals are counted. Your own mileage may vary significantly - this is just to give you a rough idea about the amount of time, well, wasted.";
    var tooltip_info_time_tot = "The sum of the above two values.";

    function ma_sprite(divname) {
        this.clear = function() {
            if (this.img)
                this.div.removeChild(this.img);
            this.img = null;
            this.original_img = null;
        };

        this.showimage = function(img, x, y) {
            if (this.original_img != img) {
                this.clear();
                this.img = img.cloneNode(false);
                this.img.style.position = "absolute";
                this.original_img = img;
                this.div.appendChild(this.img);
            }
            this.img.style.left = x + "px";
            this.img.style.top = y + "px";
        };

        this.div = document.getElementById(divname);
        this.original_img = null;
        this.img = null;
    }