/**
 * gyaase.js
 *
 * Copyright (C) 2006-2009 Guwashi <guwashi999 at gmail dot com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
 */


//
// Utility functions
//

function $(id) {
    return document.getElementById(id);
}

function debug(name, obj) {
    var message = '';
    message += 'name: ' + name + '\n';
    message += 'toString: ' + obj + '\n';
    message += 'properties:\n';
    if (obj) {
        for (var field in obj) {
            if (obj[field]) {
                message += 'obj.' + field + ' == ' + obj[field] + ', ';
            }
        }
    } else {
        message += 'no properties';
    }
    alert(message);
    return message;
}

function isParseError (xmlDoc) {
    return (null != xmlDoc && null != xmlDoc.parseError && 0 != xmlDoc.parseError.errorCode);
}

function hasProperty(obj) {
    for (var dummy in obj) {
        return true;
    }
    return false;
}

function objectLength(obj) {
    var count = 0;
    for (var dummy in obj) {
        count++;
    }
    return count;
}

function compiledRegExp(pattern, flags) {
    var re = new RegExp('');
    re.compile(pattern, flags);
    return re;
}

function stringTrim(str) {
    return str.replace(/^\s+|\s+$/g, '');
}

function showError(message, id) {
    $('result_head').innerHTML = '<b>Error: ' + message + '</b> (in "' + id + '")';
    //alert('Error: ' + message + ' (in "' + id + '")');
    return true;
}

function showMessage(message) {
    $('result_head').innerHTML = message;
    return true;
}

/*
function escapeHTML(str) { // from prototype.js
    var div = document.createElement('div');
    var text = document.createTextNode(str);
    div.appendChild(text);
    return div.innerHTML;
}
*/

var ESCAPE_HTML_TABLE = {
    // '&' : '&amp;',
    '<' : '&lt;',
    '>' : '&gt;',
    '"' : '&quot;',
    "'" : '&#39;'
}

function escapeHTML(str) {
    for (var key in ESCAPE_HTML_TABLE) {
        str = str.replace(new RegExp(key, 'g'), ESCAPE_HTML_TABLE[key])
    }
    return str;
}

function formatItem(item, maxLength) {
    if (!maxLength) maxLength = 10;
    var emptyString = ' ';
    var continueString = '...';
    if ('number' == typeof(item) && isNaN(item)) return '-';
    if ('number' == typeof(item) && 0 == item) return '0';
    if (!item) return emptyString;
    var str = item.toString();
    if (str.length <= maxLength) return str;
    var sub = str.substring(0, maxLength - continueString.length);
    return sub + continueString;
}

// http://wolfwiki.anime.net/index.php/Color_Codes
var ID_GAME_COLOR_TABLE = new Array(
    '#000000',
    '#ff0000',
    '#008000', // '#00ff00',
    '#d9d900', // '#ffff00',
    '#0000ff',
    '#00ffff',
    '#ff00ff',
    '#000000', // '#ffffff',
    '#ff7f00',
    '#7f7f7f',
    '#bfbfbf',
    '#bfbfbf',
    '#007f00',
    '#7f7f00',
    '#00007f',
    '#7f0000',
    '#7f3f00',
    '#ff9919',
    '#007f7f',
    '#7f007f',
    '#007fff',
    '#7f00ff',
    '#3399cc',
    '#ccffcc',
    '#006633',
    '#ff0033',
    '#b21919',
    '#993300',
    '#cc9933',
    '#999933',
    '#ffffbf',
    '#d9d960' // '#ffff7f'
);

var ID_GAME_TRANS_TABLE = false;

function generateIDGameTransTable() {
    var trans = new Array();

    trans.push(new Array(compiledRegExp('\\^$', ''), ''));
    trans.push(new Array(compiledRegExp('\\^\\.\\.\\.$', ''), '...'));

    for (var i = '!'.charCodeAt(0); i <= '~'.charCodeAt(0); i++) {
        var ch = String.fromCharCode(i);
        if (ch.match(/^[&<>]$/)) continue;
        if (ch.match(/^[^\d\w]$/)) ch = '\\' + ch;
        trans.push(new Array(compiledRegExp('\\^' + ch, 'g'), '</span><span style="color: ' + ID_GAME_COLOR_TABLE[(i + 16) % 32] + '">'));
    }
    trans.push(new Array(compiledRegExp('\\^&gt;', 'g'), '</span><span style="color: ' + ID_GAME_COLOR_TABLE[14] + '">'));
    trans.push(new Array(compiledRegExp('\\^&lt;', 'g'), '</span><span style="color: ' + ID_GAME_COLOR_TABLE[12] + '">'));
    trans.push(new Array(compiledRegExp('\\^&quot;', 'g'), '</span><span style="color: ' + ID_GAME_COLOR_TABLE[18] + '">'));
    trans.push(new Array(compiledRegExp('\\^&#39;', 'g'), '</span><span style="color: ' + ID_GAME_COLOR_TABLE[23] + '">'));

    trans.push(new Array(compiledRegExp('\\^[^<]', 'g'), ''));

    return trans;
}

function replaceColorCode(str) {
    if (window.opera) return stripColorCode(str); // TODO: why???

    if (!str.match(/\^./)) return str;

    if (!ID_GAME_TRANS_TABLE) ID_GAME_TRANS_TABLE = generateIDGameTransTable();
    // for (var i in ID_GAME_TRANS_TABLE) {
    for (var i = 0; i < ID_GAME_TRANS_TABLE.length; i++) {
        str = str.replace(ID_GAME_TRANS_TABLE[i][0], ID_GAME_TRANS_TABLE[i][1]);
    }

    // return '<span style="color: white">' + str + '</span>';
    return '<span style="color: black">' + str + '</span>'; // for white background
}

function stripColorCode(str) {
    if (!str.match(/\^./)) return str;
    str = str.replace(/\^$/, '');
    str = str.replace(/\^\.\.\.$/, '...'); // for formatItem
    str = str.replace(/\^&gt;/g, '');
    str = str.replace(/\^&lt;/g, '');
    str = str.replace(/\^&apos;/g, '');
    str = str.replace(/\^&#39;/g, '');
    return str.replace(/\^./g, '');
}

function getTarget(e) {
    var target = false;
    if (e.target) {
        target = e.target;
    } else if (e.srcElement) {
        target = e.srcElement;
    } else {
        return false;
    }
    if (3 == target.nodeType) { // for Safari
        target = target.parentNode;
    }
    return target;
}

function getMousePosition(e) {
    var result = new Object();
    result['x'] = e.clientX;
    result['y'] = e.clientY;
    if (document.all) { // for IE
        var docElem = 'CSS1Compat' == document.compatMode ? document.documentElement : document.body;
        result['x'] = docElem.scrollLeft + event.clientX;
        result['y'] = docElem.scrollTop + event.clientY;
    } else if (document.getElementById) { // for Mozilla
        result['x'] = e.pageX;
        result['y'] = e.pageY;
    }
    return result;
}

function getMouseOffset(e, elem) {
    var result = new Object();
    if (document.all) { // for IE
        result['x'] = event.offsetX + 2;
        result['y'] = event.offsetY + 2;
    } else if (elem.getElementsByTagName) { // for Mozilla
        result['x'] = e.pageX - parseInt(elem.style.left);
        result['y'] = e.pageY - parseInt(elem.style.top);
    }
    return result;
}

function removeChildNodes(elem) {
    var count = 0;
    while (elem.hasChildNodes()) {
        elem.removeChild(elem.firstChild);
        count++;
    }
    return count;
}

function getFirstValue(element, tag) {
    try {
        if (null != element.getElementsByTagName(tag)[0].firstChild) {
            return element.getElementsByTagName(tag)[0].firstChild.nodeValue;
        } else {
            return false;
        }
    } catch (e) {
        return false;
    }
}

function makeXMLHttpRequest() {
    try {
        if (window.XMLHttpRequest) {
            return new XMLHttpRequest();
        } else if (window.ActiveXObject) {
            return new ActiveXObject('Msxml2.XMLHTTP');
        } else {
            return false;
        }
    } catch (e) {
        return false;
    }
}

function sendHttpRequest(httpObj, targetURL, callBackFunc){
    httpObj.open('GET', targetURL, true);
    httpObj.onreadystatechange = function() {
        if (4 == httpObj.readyState) {
            if (200 == httpObj.status) {
                callBackFunc(httpObj);
            } else {
                showError('Cannot connect HTTP server', 'sendHttpRequest');
                return false;
            }
        }
    };
    httpObj.send('');
    return httpObj;
}

function addListener(elem, eventType, func, cap) {
    if (elem.addEventListener) {
        elem.addEventListener(eventType, func, cap);
        return true;
    } else if (elem.attachEvent) {
        elem.attachEvent('on' + eventType, func);
        return true;
    } else {
        alert('Sorry, please use latest IE or Firefox.');
        showError('Sorry, please use latest IE or Firefox.', 'addListener');
        return false;
    }
}


///
/// global variables
///

var serverList = false;
var totalAvailable = 0;
var totalReturned = 0;
var firstPosition = 1;
var lastModified = false;
var cookieExpireInterval = 1000 * 60 * 60 * 24 * 30; // 30 days
var isDragging = false; // for drag and drop
var childWindowOffsetX = 0; // for drag and drop
var childWindowOffsetY = 0; // for drag and drop

///
/// event handlers
///

function setListeners(e) {
    addListener($('refresh_button'), 'click', getServerList, false);
    addListener($('game_select'), 'change', changeGame, false);
    addListener($('country_select'), 'change', changeCountry, false);
    addListener($('region_select'), 'change', changeRegion, false);
    addListener($('max_select'), 'change', changeMaxResult, false);
    addListener($('prev_button'), 'click', prevServerList, false);
    addListener($('next_button'), 'click', nextServerList, false);
    addListener($('search_text'), 'keyup', searchServerList, false);
    addListener($('sort_select'), 'change', searchServerList, false);
    addListener($('up_only_checkbox'), 'click', searchServerList, false);
    addListener($('not_empty_checkbox'), 'click', searchServerList, false);
    addListener($('not_full_checkbox'), 'click', searchServerList, false);
    addListener($('no_password_checkbox'), 'click', searchServerList, false);
    addListener($('child_window_close_button'), 'mousedown', closeChildWindow, false);

    addListener($('child_window_bar'), 'mousedown', dragDownChildWindow, false);
    addListener(document, 'mousemove', dragMoveChildWindow, false);
    addListener(document, 'mouseup', dragUpChildWindow, false);

    addListener($('gyachat_submit_button'), 'click', submitGyaChat, false);
    addListener($('gyachat_filter'), 'click', function(e) { $('gyachat_message').value = ''; submitGyaChat(e); }, false);

    addListener(window, 'unload', saveToCookie, false); // does NOT work for Opera, see getServerList
    addListener(window, 'unload', GUnload, false);

    //removeChildNodes($('country_select'));
    //appendOptions($('country_select'), getCountryHash());
    loadFromCookie(e);
    loadFromQuery(e);
    getServerList(e);
    // submitGyaChat(e);
}

function getServerList(e) {
    // $('search_text').focus();
    showMessage('Now loading server list... <b>Please wait for 5 seconds.<b>');
    setButtonsDisabled();
    var game = $('game_select').value;
    var countryCode = $('country_select').value;
    var region = $('region_select').value;
    var maxServers = $('max_select').value;
    var countryQuery = '&country_code=' + ('unknown' == countryCode ? '' : countryCode);
    if ('all' == countryCode) countryQuery = '';
    var regionQuery= '&region=' + ('unknown' == region ? '' : region);
    if ('all' == region) regionQuery = '';
    var dummy = Math.floor((new Date()).getTime() / (1000 * 60 * 5));
    sendHttpRequest(makeXMLHttpRequest(), 'gyastat_low_load.php?dummy=' + dummy + '&format=xml&game=' + game + '&start=' + firstPosition + '&max=' + maxServers + countryQuery + regionQuery, setServerList);
    $('gyachat_message').value = '';
    submitGyaChat(e);
    if (window.opera) saveToCookie(e); // Opera does not work with 'unload' event at window.
}

function changeMaxResult(e) {
    firstPosition = 1; // !!! global variable
    getServerList(e);
}

function changeGame(e) {
    firstPosition = 1; // !!! global variable
    getServerList(e);
}

function changeCountry(e) {
    removeChildNodes($('region_select'));
    appendOptions($('region_select'), getRegionHash($('country_select').value));
    firstPosition = 1; // !!! global variable
    getServerList(e);
}

function changeRegion(e) {
    firstPosition = 1; // !!! global variable
    getServerList(e);
}

function nextServerList(e) {
    firstPosition += parseInt($('max_select').value); // !!! global variable
    if (firstPosition > totalAvailable) firstPosition = totalAvailable;
    getServerList(e);
}

function prevServerList(e) {
    firstPosition -= parseInt($('max_select').value); // !!! global variable
    if (1 > firstPosition) firstPosition = 1;
    getServerList(e);
}

function setServerList(httpObj) {
    if (isParseError(httpObj.responseXML)) {
        serverList = false; // !!! set global variable
        totalAvailable = 0; // !!! set global variable
        totalReturned = 0; // !!! set global variable
        firstPosition = 1; // !!! set global variable
        lastModified = false; // !!! set global variable
        showError('Cannot parse XML document: ' + httpObj.responseXML.parseError.reason + ' &quot;' + httpObj.responseXML.parseError.srcText + '&quot;', 'setServerList');
        setButtonsEnabled();
        return false;
    }
    var hash = convertDOMtoHash(httpObj.responseXML);
    if (!hash) {
        serverList = false; // !!! set global variable
        totalAvailable = 0; // !!! set global variable
        totalReturned = 0; // !!! set global variable
        firstPosition = 1; // !!! set global variable
        lastModified = false; // !!! set global variable
        showError('Cannot load server list. Please refresh server list again.', 'setServerList');
        setButtonsEnabled();
        return false;
    }

    serverList = hash['serverList']; // !!! set global variable
    totalAvailable = hash['totalAvailable']; // !!! set global variable
    totalReturned = hash['totalReturned']; // !!! set global variable
    firstPosition = hash['firstPosition']; // !!! set global variable
    if (httpObj.getResponseHeader('Last-Modified')) {
        lastModified = (new Date(httpObj.getResponseHeader('Last-Modified'))).toString(); // !!! set global variable
    } else {
        lastModified = (new Date()).toString(); // !!! set global variable
    }
    showMessage((firstPosition && totalReturned && totalAvailable ? 'Results <b>' + firstPosition + ' - ' + (firstPosition + totalReturned - 1) + '</b> of <b>' + totalAvailable + '</b>, ' : '') + lastModified);
    searchServerList(false);
    setButtonsEnabled();
}

function searchServerList(e) {
    // $('search_text').focus();
    if (!serverList) {
        showError('Server list is empty. Please refresh server list.', 'searchServerList');
        setButtonsEnabled();
        return false;
    }
    showServerList($('result_body'), sortServerList(filterServerList(serverList, $('search_text').value, $('up_only_checkbox').checked, $('not_empty_checkbox').checked, $('not_full_checkbox').checked, $('no_password_checkbox').checked), $('sort_select').value));
}

function closeChildWindow(e) {
    var childWindow = $('child_window');
    childWindow.style.visibility = 'hidden';
}

function openChildWindow(e) {
    var childWindow = $('child_window');

    var mousePos = getMousePosition(e);
    childWindow.style.left = mousePos['x'] + 'px'; // TODO: adjust position
    childWindow.style.top  = mousePos['y'] + 'px';
    childWindow.style.visibility = 'visible';

    var target = getTarget(e);
    var childWindowBody = $('child_window_body');
    if (0 == target.id.indexOf('result_table_tbody_td_players_a_clone_')) {
        var i = target.id.substring('result_table_tbody_td_players_a_clone_'.length);
        showPlayerList(childWindowBody, serverList[i]['players'], serverList[i]['type'], serverList[i]['countrycode']);
        return false;
    } else if (0 == target.id.indexOf('result_table_tbody_td_location_a_clone_')) {
        var i = target.id.substring('result_table_tbody_td_location_a_clone_'.length);
        // TODO
        var googlemapId = 'google_map_' + encodeURIComponent(i);
        childWindowBody.innerHTML = '<div id="' + googlemapId + '" style="width: 400px; height: 400px"></div>';
        googleMapLoad($(googlemapId), serverList[i]['latitude'], serverList[i]['longitude'], serverList[i]['googlemapzoom']);
        // var template = $('child_window_google_template');
        // var clone = cloneNode(template, '_clone');
        // childWindowBody.appendChild(clone);
        // clone.style.display = 'block';
        // googleMapLoad(clone, serverList[i]['latitude'], serverList[i]['longitude'], serverList[i]['googlemapzoom']);
        return false;
    } else if (0 == target.id.indexOf('result_table_tbody_td_map_a_clone_')) {
        var i = target.id.substring('result_table_tbody_td_map_a_clone_'.length);
        // TODO
        // childWindowBody.innerHTML = '<img src="map/' + $('game_select').value + '/' + serverList[i]['map'] + '" />';
        var gameMapId = 'game_map_img_' + encodeURIComponent(i);
        var basename = serverList[i]['map'];
        if (basename) {
            basename = basename.toLowerCase();
            basename = basename.replace(' ', '_');
        }
        // childWindowBody.innerHTML = '<img id="' + gameMapId + '" src="img.php/map/' + $('game_select').value + '/' + basename + '.jpg" style="width: 100px; height: 100px;"></img>';
        childWindowBody.innerHTML = '<img id="' + gameMapId + '" src="img.php/map/' + $('game_select').value + '/' + basename + '.jpg" style="width: 120px; height: 90px;"></img>';
        return false;
    } else {
        return false;
    }
    return false;
}

function dragDownChildWindow(e) {
    isDragging = true; // !!! set global variable
    var childWindow = $('child_window');
    var mouseOffset = getMouseOffset(e, childWindow);
    childWindowOffsetX = mouseOffset['x']; // !!! set global variable
    childWindowOffsetY = mouseOffset['y']; // !!! set global variable
    return false;
}

function dragMoveChildWindow(e) {
    if (!isDragging) { // !!! global variable
        return true;
    }
    var childWindow = $('child_window');
    var mousePos = getMousePosition(e);
    childWindow.style.left = (mousePos['x'] - childWindowOffsetX) + 'px'; // !!! global variable
    childWindow.style.top  = (mousePos['y'] - childWindowOffsetY) + 'px'; // !!! global variable
    return false;
}

function dragUpChildWindow(e) {
    isDragging = false; // !!! set global variable
    childWindowOffsetX = 0; // !!! set global variable
    childWindowOffsetY = 0; // !!! set global variable
    return false;
}

function saveToCookie(e) {
    if (!navigator.cookieEnabled) return false;
    var expiresDate = new Date();
    expiresDate.setTime(expiresDate.getTime() + cookieExpireInterval);
    var expires = expiresDate.toGMTString();
    document.cookie = 'game_select=' + $('game_select').value + ';expires=' + expires;
    document.cookie = 'country_select=' + $('country_select').value + ';expires=' + expires;
    document.cookie = 'region_select=' + $('region_select').value + ';expires=' + expires;
    document.cookie = 'max_select=' + $('max_select').value + ';expires=' + expires;
    document.cookie = 'search_text=' + encodeURIComponent($('search_text').value) + ';expires=' + expires;
    document.cookie = 'sort_select=' + $('sort_select').value + ';expires=' + expires;
    document.cookie = 'up_only_checkbox=' + $('up_only_checkbox').checked + ';expires=' + expires;
    document.cookie = 'not_empty_checkbox=' + $('not_empty_checkbox').checked + ';expires=' + expires;
    document.cookie = 'not_full_checkbox=' + $('not_full_checkbox').checked + ';expires=' + expires;
    document.cookie = 'no_password_checkbox=' + $('no_password_checkbox').checked + ';expires=' + expires;
    document.cookie = 'gyachat_name=' + encodeURIComponent($('gyachat_name').value) + ';expires=' + expires;
    document.cookie = 'gyachat_filter=' + $('gyachat_filter').checked + ';expires=' + expires;
    // alert('saveToCookie: ' + document.cookie);
    return false;
}

function loadFromCookie(e) {
    if (!navigator.cookieEnabled) return false;
    if (!document.cookie) return false;
    var line = document.cookie;
    var attributes = line.split(';');
    var countryCode = false;
    var region = false;
    for (var i in attributes) {
        var keyValue = stringTrim(attributes[i]).split('=');
        if (2 == keyValue.length) {
            var key = stringTrim(keyValue[0]);
            var value = stringTrim(keyValue[1]);
            switch (key) {
            case 'game_select':
                $('game_select').value = value;
                break;
            case 'country_select':
                $('country_select').value = value;
                countryCode = value;
                break;
            case 'region_select':
                $('region_select').value = value;
                region = value;
                break;
            case 'max_select':
                $('max_select').value = value;
                break;
            case 'search_text':
                $('search_text').value = decodeURIComponent(value);
                break;
            case 'sort_select':
                $('sort_select').value = value;
                break;
            case 'up_only_checkbox':
                $('up_only_checkbox').checked = 'true' == value;
                break;
            case 'not_empty_checkbox':
                $('not_empty_checkbox').checked = 'true' == value;
                break;
            case 'not_full_checkbox':
                $('not_full_checkbox').checked = 'true' == value;
                break;
            case 'no_password_checkbox':
                $('no_password_checkbox').checked = 'true' == value;
                break;
            case 'gyachat_name':
                $('gyachat_name').value = decodeURIComponent(value);
                break;
            case 'gyachat_filter':
                $('gyachat_filter').checked = 'true' == value;
                break;
            default:
                break;
            }
        }
    }
    if (countryCode) {
        removeChildNodes($('region_select'));
        appendOptions($('region_select'), getRegionHash($('country_select').value));
    }
    if (region) {
        $('region_select').value = region;
    }

    //alert('loadFromCookie: ' + document.cookie);
    return false;
}

function loadFromQuery(e) {
    if (!location.search) return false;
    var hash = convertQueryToHash(location.search);
    if (null != hash['max']) $('max_select').value = hash['max'];
    if (null != hash['start']) firstPosition = parseInt(hash['start']); // !!! global variable
    if (null != hash['game']) $('game_select').value = hash['game'];
    if (null != hash['country']) $('country_select').value = hash['country'];
    if (null != hash['country_code']) $('country_select').value = hash['country_code'];
    if (null != hash['region']) $('region_select').value = hash['region'];
    if (null != hash['region_code']) $('region_select').value = hash['region_code'];
    if (null != hash['search']) $('search_text').value = hash['search'];
    if (null != hash['sort']) $('sort_select').value = hash['sort'];
    if (null != hash['sort_by']) $('sort_select').value = hash['sort_by'];
    if (null != hash['sortby']) $('sort_select').value = hash['sortby'];
    if (null != hash['not_empty']) $('not_empty_checkbox').checked = 'true' == hash['not_empty'];
    if (null != hash['notempty']) $('not_empty_checkbox').checked = 'true' == hash['notempty'];
    if (null != hash['not_full']) $('not_full_checkbox').checked = 'true' == hash['not_full'];
    if (null != hash['notfull']) $('not_full_checkbox').checked = 'true' == hash['notfull'];
    if (null != hash['no_password']) $('no_password_checkbox').checked = 'true' == hash['no_password'];
    if (null != hash['nopassword']) $('no_password_checkbox').checked = 'true' == hash['nopassword'];
}

function submitGyaChat(e) {
    $('gyachat_submit_button').disabled = true;
    $('gyachat_name').disabled = true;
    $('gyachat_message').disabled = true;
    $('gyachat_filter').disabled = true;
    var name = encodeURIComponent($('gyachat_name').value);
    var game = $('game_select').value;
    var message = encodeURIComponent($('gyachat_message').value);
    var filter = $('gyachat_filter').checked;
    if (location.pathname.match(/index-ja.html$/)) filter = 'true';
    sendHttpRequest(makeXMLHttpRequest(), 'gyachat.php?dummy=' + Math.random() + '&xml=0&name=' + name + '&game=' + game + '&message=' + message + '&filter=' + filter, setGyaChat);
}

function setGyaChat(httpObj) {
    $('gyachat_body').innerHTML = httpObj.responseText;
    $('gyachat_submit_button').disabled = false;
    $('gyachat_name').disabled = false;
    $('gyachat_message').disabled = false;
    $('gyachat_message').value = '';
    $('gyachat_filter').disabled = false;
    return false;
}



///
/// functions
///
function setButtonsEnabled() {
    $('refresh_button').disabled = false;
    $('game_select').disabled = false;
    $('country_select').disabled = false;
    $('region_select').disabled = false;
    $('max_select').disabled = false;
    $('prev_button').disabled = false;
    $('next_button').disabled = false;
    if (1 >= firstPosition) {
        $('prev_button').disabled = true;
    }
    if (firstPosition + totalReturned > totalAvailable) {
        $('next_button').disabled = true;
    }
}

function setButtonsDisabled() {
    $('refresh_button').disabled = true;
    $('game_select').disabled = true;
    $('country_select').disabled = true;
    $('region_select').disabled = true;
    $('max_select').disabled = true;
    $('prev_button').disabled = true;
    $('next_button').disabled = true;
}

function createOption(value, body) {
    var option = document.createElement('option');
    option.value = value;
    option.innerHTML = body;
    return option;
}

function appendOptions(elem, hash) {
    elem.appendChild(createOption('all', 'All'));
    elem.appendChild(createOption('unknown', 'Unknown'));
    for (key in hash) {
        var text = key + ', ' + hash[key];
        elem.appendChild(createOption(key, formatItem(text, 20)));
    }
}

function filterServerList(serverList, pattern, isUpOnly, isNotEmpty, isNotFull, isNoPassword) {
    serverList = isUpOnly ? filterServerListWithUp(serverList) : serverList;
    serverList = isNotEmpty ? filterServerListWithEmpty(serverList) : serverList;
    serverList = isNotFull ? filterServerListWithFull(serverList) : serverList;
    serverList = isNoPassword ? filterServerListWithPassword(serverList) : serverList;
    serverList = filterServerListWithPattern(serverList, pattern);
    return serverList;
}

function filterServerListWithUp(serverList) {
    if (!serverList) return serverList;
    var result = new Object();
    for (var i in serverList) {
        if ('UP' == serverList[i]['status']) result[i] = serverList[i];
    }
    return result;
}

function filterServerListWithEmpty(serverList) {
    if (!serverList) return serverList;
    var result = new Object();
    for (var i in serverList) {
        // if (0 < serverList[i]['numplayers']) result[i] = serverList[i];
        if (0 < serverList[i]['numplayers'] - serverList[i]['numbots']) result[i] = serverList[i];
    }
    return result;
}

function filterServerListWithFull(serverList) {
    if (!serverList) return serverList;
    var result = new Object();
    for (var i in serverList) {
        if (0 < serverList[i]['maxplayers'] && serverList[i]['maxplayers'] > serverList[i]['numplayers']) {
            result[i] = serverList[i];
        }
    }
    return result;
}

function filterServerListWithPassword(serverList) {
    if (!serverList) return serverList;
    var result = new Object();
    for (var i in serverList) {
        if (!isPassword(serverList[i]['rules'])) {
            result[i] = serverList[i];
        }
    }
    return result;
}

function filterServerListWithPattern(serverList, pattern) {
    if (!pattern || '' == pattern) return serverList;
    pattern = pattern.toLowerCase();
    var result = new Object();
    if (0 == pattern.indexOf('/')) { // regexp
        pattern = pattern.substring(1);
        for (var i in serverList) {
            if (serverList[i]['searchfield'] &&
                serverList[i]['searchfield'].match(pattern)) {
                result[i] = serverList[i];
            }
        }
    } else if (0 == pattern.indexOf('-')) { // NOT match pattern
        pattern = pattern.substring(1);
        for (var i in serverList) {
            if (serverList[i]['searchfield'] &&
                -1 == serverList[i]['searchfield'].indexOf(pattern)) { // ==
                result[i] = serverList[i];
            }
        }
    } else { // match pattern
        for (var i in serverList) {
            if (serverList[i]['searchfield'] &&
                -1 != serverList[i]['searchfield'].indexOf(pattern)) { // !=
                result[i] = serverList[i];
            }
        }
    }
    return result;
}

function sortServerList(serverList, field) {
    if (!serverList || !field || '' == field) return serverList;
    // convert Object to Array
    var array = new Array();
    for (var i in serverList) {
        array.push(serverList[i]);
    }

    if ('name' == field) field = 'stripedname';

    // sort Array by field
    switch (field) {
    case 'ping':
        array.sort(function (a, b) { return a[field] - b[field]; });
        break;
    case 'numplayers':
        array.sort(function (a, b) {
                       var humanplayersA = a['numplayers'] - a['numbots'];
                       var humanplayersB = b['numplayers'] - b['numbots'];
                       if (humanplayersB != humanplayersA) return humanplayersB - humanplayersA;
                       if (b['numplayers'] != a['numplayers']) return b['numplayers'] - a['numplayers'];
                       return b['maxplayers'] - a['maxplayers'];
                   });
        break;
    default:
        array.sort(function (a, b) {
                       var strA = !a[field] ? '' : String(a[field]).toLowerCase();
                       var strB = !b[field] ? '' : String(b[field]).toLowerCase();
                       return strA == strB ? 0 : (strA > strB ? 1 : -1);
                   })
        break;
    }
    // convert Array to Object
    var result = new Object();
    for (var i in array) {
        result[array[i]['address']] = array[i];
    }
    return result;
}

function convertDOMtoHash(xmlDoc) {
    var result = new Object();
    var qstatElement = xmlDoc.getElementsByTagName('qstat')[0];
    if (!qstatElement) return false;
    var totalAvailable = parseInt(qstatElement.getAttribute('totalAvailable'));
    var totalReturned = parseInt(qstatElement.getAttribute('totalReturned'));
    var firstPosition = parseInt(qstatElement.getAttribute('firstPosition'));
    var serverElements = qstatElement.getElementsByTagName('server');
    if (!serverElements) return false;
    for (var i = 0; i < serverElements.length; i++) {
        try {
            var server = serverElements[i];
            if (!server) continue;
            var type = server.getAttribute('type');
            var address = server.getAttribute('address');
            var status = server.getAttribute('status');
            var servers = server.getAttribute('servers');
            var hostname = getFirstValue(server, 'hostname');
            var countrycode = getFirstValue(server, 'countrycode');
            var countrycode3 = getFirstValue(server, 'countrycode3');
            var countryname = getFirstValue(server, 'countryname');
            var region = getFirstValue(server, 'region');
            var regionname = getFirstValue(server, 'regionname');
            var city = getFirstValue(server, 'city');
            var postalcode = getFirstValue(server, 'postalcode');
            var latitude = parseFloat(getFirstValue(server, 'latitude'));
            var longitude = parseFloat(getFirstValue(server, 'longitude'));
            var areacode = parseInt(getFirstValue(server, 'areacode'));
            var dmacode = parseInt(getFirstValue(server, 'dmacode'));
            // var name = decodeURIComponent(getFirstValue(server, 'name'));
            var name = getFirstValue(server, 'name');
            var coloredname = replaceColorCode(escapeHTML(formatItem(name, 35)));
            var stripedname = stripColorCode(formatItem(name, 100));
            var gametype = getFirstValue(server, 'gametype');
            var map = getFirstValue(server, 'map');
            var numplayers = parseInt(getFirstValue(server, 'numplayers'));
            var maxplayers = parseInt(getFirstValue(server, 'maxplayers'));
            var ping = parseInt(getFirstValue(server, 'ping'));
            var retries = parseInt(getFirstValue(server, 'retries'));
            var playersElement = server.getElementsByTagName('players')[0];
            var players = false;
            var numbots = 0;
            if (playersElement) {
                var playerElements = playersElement.getElementsByTagName('player');
                if (playerElements) {
                    players = new Object();
                    for (var j = 0; j < playerElements.length; j++) {
                        var player = playerElements[j];
                        if (player) {
                            var number = parseInt(getFirstValue(player, 'number'));
                            // var playername = decodeURIComponent(getFirstValue(player, 'name')); // var name is already used
                            var playername = getFirstValue(player, 'name'); // var name is already used
                            var coloredplayername = replaceColorCode(escapeHTML(formatItem(playername, 35)));
                            var stripedplayername = stripColorCode(formatItem(playername, 100));
                            var score = parseInt(getFirstValue(player, 'score'));
                            var playerping = parseInt(getFirstValue(player, 'ping')); // var ping is already used
                            var rate = parseInt(getFirstValue(player, 'rate'));
                            var clan = getFirstValue(player, 'clan'); // q4, ET:QW
                            var coloredclan = replaceColorCode(escapeHTML(formatItem(clan, 20)));
                            var stripedclan = stripColorCode(formatItem(clan, 100));
                            var team = getFirstValue(player, 'team'); // bf1942, bf2
                            var time = getFirstValue(player, 'time'); // steam games
                            players[j] = { 'number': number, 'name': playername, 'coloredname': coloredplayername, 'stripedname': stripedplayername, 'score': score, 'ping': playerping, 'rate': rate, 'clan': clan, 'coloredclan': coloredclan, 'stripedclan': stripedclan, 'team': team, 'time': time };
                            // }
                            if (('crysis' != $('game_select').value && 'arma2' != $('game_select').value && 0 == playerping) || (-9999 == score && -1 != stripedplayername.indexOf('TV')) || '-1s' == time) numbots++;
                        }
                    }
                }
            }
            var rulesElement = server.getElementsByTagName('rules')[0];
            var rules = false;
            if (rulesElement) {
                var ruleElements = rulesElement.getElementsByTagName('rule');
                if (ruleElements) {
                    rules = new Object();
                    for (var j = 0; j < ruleElements.length; j++) {
                        var rule = ruleElements[j];
                        if (rule) {
                            var rulename = rule.getAttribute('name');
                            var rulevalue = false;
                            if (rule.firstChild) rulevalue = rule.firstChild.nodeValue;
                            rules[rulename] = rulevalue;
                        }
                    }
                }
            }
            var location = countryname ? countryname : '';
            if (regionname && !(countryname && countryname == regionname)) {
                location = regionname + ',' + location;
            }
            if (city && !(regionname && regionname == city) && !(countryname && countryname == city)) {
                location = city + ',' + location;
            }
            if ('' == location) location = false;
            var googlemapzoom = 3;
            if (regionname) googlemapzoom = 6;
            if (city) googlemapzoom = 9;
            var searchfield = (countrycode + ' ' + countrycode3 + ' ' + stripedname + ' ' + location + ' ' + gametype + ' ' + map + ' ' + hostname).toLowerCase();
            result[address] = { 'type': type, 'address': address, 'status': status, 'servers': servers, 'hostname': hostname, 'countrycode': countrycode, 'countrycode3': countrycode3, 'countryname': countryname, 'region': region, 'regionname': regionname, 'city': city, 'postalcode': postalcode, 'latitude': latitude, 'longitude': longitude, 'areacode': areacode, 'dmacode': dmacode, 'name': name, 'coloredname': coloredname, 'stripedname': stripedname, 'gametype': gametype, 'map': map, 'numplayers': numplayers, 'maxplayers': maxplayers, 'numbots': numbots, 'ping': ping, 'retries': retries, 'players': players, 'rules': rules, 'searchfield': searchfield, 'location': location, 'googlemapzoom': googlemapzoom };
        } catch (e) {
            alert(e);
        }
    }
    return { 'serverList': result, 'totalAvailable': totalAvailable, 'totalReturned': totalReturned, 'firstPosition': firstPosition };
}

function isPassword(rules) {
    for (var rule in rules) {
        if (-1 != rule.toLowerCase().indexOf('pass') && '1' == rules[rule]) return true;
        if (-1 != rule.toLowerCase().indexOf('pswrd') && '1' == rules[rule]) return true; // CoD4
    }
    return false;
}

function convertQueryToHash(query) {
    if (!query) return null;
    var pos = query.indexOf('?');
    if (-1 != pos) query = query.substring('?'.length + pos);
    var pairs = query.split('&')
    var result = new Object();
    for (var i in pairs) {
        var p = pairs[i].split('=');
        if (2 == p.length) {
            result[p[0]] = decodeURIComponent(p[1]);
        }
    }
    return result;
}

function googleMapLoad(element, latitude, longitude, zoom) {
    if (GBrowserIsCompatible()) {
        var map = new GMap2(element);
        map.addControl(new GLargeMapControl());
        map.addControl(new GMapTypeControl());
        map.addControl(new GScaleControl());
        map.setCenter(new GLatLng(latitude, longitude), zoom);
        // map.setMapType(G_SATELLITE_MAP);
    }
}

function nodeGetElementById(node, id) {
    if (!node) return false;
    if (node.getElementById) return node.getElementById(id);
    if (node.id && id == node.id) return node;
    if (node.hasChildNodes()) {
        var children = node.childNodes;
        for (var i = 0; i < children.length; i++) {
            var child = children[i];
            var result = nodeGetElementById(child, id);
            if (result && result.id && id == result.id) return result;
        }
    }
    return false;
}

function addSuffixToId(node, suffix, isChildNode) {
    if (!node) return node;
    if (!node.removeAttribute) return node;
    var oldId = node.id;
    node.removeAttribute('id');
    node.id = oldId + suffix;
    if (isChildNode && node.hasChildNodes()) {
        var children = node.childNodes;
        for (var i = 0; i < children.length; i++) {
            var child = children[i];
            addSuffixToId(child, suffix, isChildNode);
        }
    }
    return node;
}

function cloneNode(node, suffix) {
    if (!node) return false;
    var clone = node.cloneNode(true);
    addSuffixToId(clone, suffix, true);
    return clone;
}

function setTdNode(parent, prefix, fieldName, index, html, tooltip) {
    var td = nodeGetElementById(parent, prefix + '_table_tbody_td_' + fieldName + '_clone_' + index);
    if (!td) return td;
    td.innerHTML = html;
    td.title = tooltip;
    return td;
}

function setTdPasswordNode(parent, prefix, fieldName, index, isPassword) {
    var td = nodeGetElementById(parent, prefix + '_table_tbody_td_' + fieldName + '_clone_' + index);
    if (!td) return td;

    var img = nodeGetElementById(td, prefix + '_table_tbody_td_' + fieldName + '_img' + '_clone_' + index);
    if (!img) {
        return td;
    }
    if (!img.src) {
        return td;
    }

    if (isPassword) {
        img.src = img.src.replace('default', 'key');
        img.alt = 'pw';
    } else {
        // img.parentNode.removeChild(img);
    }
    return td;
}

function setTdCountryCodeNode(parent, prefix, fieldName, index, html) {
    var td = nodeGetElementById(parent, prefix + '_table_tbody_td_' + fieldName + '_clone_' + index);
    if (!td) return td;

    var img = nodeGetElementById(td, prefix + '_table_tbody_td_' + fieldName + '_img' + '_clone_' + index);
    if (!img) {
        td.innerHTML = html;
        return td;
    }
    if (!img.src) {
        td.innerHTML = html;
        return td;
    }

    img.src = img.src.replace('countrycode', html.toLowerCase());
    img.alt = img.alt.replace('countrycode', html);
    return td;
}

function setTdNameNode(parent, prefix, fieldName, index, html, tooltip, hostname, rules) {
    var td = nodeGetElementById(parent, prefix + '_table_tbody_td_' + fieldName + '_clone_' + index);
    if (!td) return td;

    if (!hostname) {
        td.innerHTML = html;
        td.title = tooltip;
        return td;
    }

    var game = $('game_select').value;
    var template = $('launch_game_' + game);
    if (!template) {
        td.innerHTML = html;
        td.title = tooltip;
        return td;
    }

    var a = cloneNode(template, '_' + index);
    if (!a) {
        td.innerHTML = html;
        td.title = tooltip;
        return td;
    }
    if (!a.href) {
        a.innerHTML = html;
        a.title = tooltip;
        return td;
    }

    a.href = a.href.replace('about:blank', ''); // for IE6
    a.href = a.href.replace('about:', ''); // for IE7
    a.href = a.href.replace('hostname', hostname);
    if (rules && rules['sv_gtid']) a.href = a.href.replace('gtid', rules['sv_gtid']);
    var href = a.getAttribute('href');
    a.removeAttribute('href');
    a.setAttribute('href', href);
    a.innerHTML = html;
    a.title = tooltip;
    td.appendChild(a);
    return td;
}

function setTdPlayersNode(parent, prefix, fieldName, index, html, tooltip, isLink) {
    var td = nodeGetElementById(parent, prefix + '_table_tbody_td_' + fieldName + '_clone_' + index);
    if (!td) return td;
    var a = nodeGetElementById(td, prefix + '_table_tbody_td_' + fieldName + '_a' + '_clone_' + index);
    if (!a) {
        td.innerHTML = html;
        td.title = tooltip;
        return td;
    }
    if (!isLink) {
        a.parentNode.removeChild(a);
        td.innerHTML = html;
        td.title = tooltip;
        return td;
    }
    a.innerHTML = html;
    a.title = tooltip;
    addListener(a, 'click', openChildWindow, false);
    // addListener(a, 'mouseover', openChildWindow, false);
    return td;
}

function setTdLocationNode(parent, prefix, fieldName, index, html, tooltip) {
    var td = nodeGetElementById(parent, prefix + '_table_tbody_td_' + fieldName + '_clone_' + index);
    if (!td) return td;
    var a = nodeGetElementById(td, prefix + '_table_tbody_td_' + fieldName + '_a' + '_clone_' + index);
    if (!a) {
        td.innerHTML = html;
        td.title = tooltip;
        return td;
    }
    a.innerHTML = html;
    a.title = tooltip;
    addListener(a, 'click', openChildWindow, false);
    return td;
}

function setTdMapNode(parent, prefix, fieldName, index, html, tooltip) {
    var td = nodeGetElementById(parent, prefix + '_table_tbody_td_' + fieldName + '_clone_' + index);
    if (!td) return td;
    var a = nodeGetElementById(td, prefix + '_table_tbody_td_' + fieldName + '_a' + '_clone_' + index);
    if (!a) {
        td.innerHTML = html;
        td.title = tooltip;
        return td;
    }
    a.innerHTML = html;
    a.title = tooltip;
    addListener(a, 'click', openChildWindow, false);
    return td;
}

function setLinkNode(parent, id, queryList, anchor) {
    var a = nodeGetElementById(parent, id);
    // a.href = location.protocol + '//' + location.host + location.pathname + '?' + queryList.join('&') + anchor;
    a.href = a.href + '?' + queryList.join('&') + anchor;
    a.title = a.href;
    var href = a.getAttribute('href');
    a.removeAttribute('href');
    a.setAttribute('href', href);
}

function showServerList(element, serverList) { 
    // TODO: refine
    element.innerHTML = '';
    var oldClone = $('result_table_template_clone');
    if (oldClone) oldClone.parentNode.removeChild(oldClone);
    if (!serverList || !hasProperty(serverList)) {
        element.innerHTML = '<b>Did not match any servers.</b>';
        return false;
    }

    var template = $('result_table_template');
    var clone = cloneNode(template, '_clone');

    var thead = nodeGetElementById(clone, 'result_table_thead_clone');
    addListener(nodeGetElementById(thead, 'result_table_thead_td_countrycode_a_clone'), 'click', function (e) { $('sort_select').value = 'countrycode'; return searchServerList(e); }, false);
    addListener(nodeGetElementById(thead, 'result_table_thead_td_location_a_clone'), 'click', function (e) { $('sort_select').value = 'location'; return searchServerList(e); }, false);
    addListener(nodeGetElementById(thead, 'result_table_thead_td_name_a_clone'), 'click', function (e) { $('sort_select').value = 'name'; return searchServerList(e); }, false);
    addListener(nodeGetElementById(thead, 'result_table_thead_td_ping_a_clone'), 'click', function (e) { $('sort_select').value = 'ping'; return searchServerList(e); }, false);
    addListener(nodeGetElementById(thead, 'result_table_thead_td_players_a_clone'), 'click', function (e) { $('sort_select').value = 'numplayers'; return searchServerList(e); }, false);
    addListener(nodeGetElementById(thead, 'result_table_thead_td_gametype_a_clone'), 'click', function (e) { $('sort_select').value = 'gametype'; return searchServerList(e); }, false);
    addListener(nodeGetElementById(thead, 'result_table_thead_td_map_a_clone'), 'click', function (e) { $('sort_select').value = 'map'; return searchServerList(e); }, false);
    addListener(nodeGetElementById(thead, 'result_table_thead_td_hostname_a_clone'), 'click', function (e) { $('sort_select').value = 'hostname'; return searchServerList(e); }, false);

    var tbody = nodeGetElementById(clone, 'result_table_tbody_clone');
    var tr = nodeGetElementById(clone,'result_table_tbody_tr_clone');
    for (var i in serverList) {
        var tr_i = cloneNode(tr, '_' + i);
        setTdPasswordNode(tr_i, 'result', 'password', i, isPassword(serverList[i]['rules']));
        setTdCountryCodeNode(tr_i, 'result', 'countrycode', i, formatItem(serverList[i]['countrycode']));
        setTdLocationNode(tr_i, 'result', 'location', i, formatItem(serverList[i]['location'], 12), formatItem(serverList[i]['location'], 100));
        setTdNameNode(tr_i, 'result', 'name', i, serverList[i]['coloredname'], serverList[i]['stripedname'], serverList[i]['hostname'], serverList[i]['rules']);
        setTdNode(tr_i, 'result', 'ping', i, formatItem(serverList[i]['ping']) + 'ms', formatItem(serverList[i]['ping'], 100) + 'ms');
        var humanplayers = serverList[i]['numplayers'] - serverList[i]['numbots'];
        if (0 > humanplayers) humanplayers = 0;
        var playersString = formatItem(serverList[i]['numplayers'], 100) + (humanplayers == serverList[i]['numplayers'] ? '' : '(' + formatItem(humanplayers, 100) + ')') + '/' + formatItem(serverList[i]['maxplayers'], 100);
        setTdPlayersNode(tr_i, 'result', 'players', i, playersString, playersString, serverList[i]['players'] && hasProperty(serverList[i]['players']));
        setTdNode(tr_i, 'result', 'gametype', i, formatItem(serverList[i]['gametype'], 15), formatItem(serverList[i]['gametype'], 100));
        setTdMapNode(tr_i, 'result', 'map', i, formatItem(serverList[i]['map'], 20), formatItem(serverList[i]['map'], 100));
        setTdNode(tr_i, 'result', 'hostname', i, formatItem(serverList[i]['hostname'], 'nnn.nnn.nnn.nnn:nnnnnn'.length), formatItem(serverList[i]['hostname'], 100));
        setTdNode(tr_i, 'result', 'status', i, formatItem(serverList[i]['status']), formatItem(serverList[i]['status'], 100));
        tbody.appendChild(tr_i);
    }
    tr.parentNode.removeChild(tr); // remove template tr

    setLinkNode(clone, 'result_table_tfoot_td_a_simple_clone', new Array('game=' + $('game_select').value, 'country=' + $('country_select').value), '');
    setLinkNode(clone, 'result_table_tfoot_td_a_detail_clone', new Array('game=' + $('game_select').value, 'max=' + $('max_select').value, 'start=' + firstPosition, 'country=' + $('country_select').value, 'region=' + $('region_select').value, 'search=' + encodeURIComponent($('search_text').value), 'sort=' + $('sort_select').value, 'not_empty=' + $('not_empty_checkbox').checked, 'not_full=' + $('not_full_checkbox').checked, 'no_password=' + $('no_password_checkbox').checked), '#browser');
    setLinkNode(clone, 'result_table_tfoot_td_a_text_clone', new Array('format=text', 'max=' + $('max_select').value, 'start=' + firstPosition, 'game=' + $('game_select').value, 'country_code=' + $('country_select').value), '');
    setLinkNode(clone, 'result_table_tfoot_td_a_xml_clone', new Array('format=xml', 'max=' + $('max_select').value, 'start=' + firstPosition, 'game=' + $('game_select').value, 'country_code=' + $('country_select').value), '');
    setLinkNode(clone, 'result_table_tfoot_td_a_html_clone', new Array('format=html', 'max=' + $('max_select').value, 'start=' + firstPosition, 'game=' + $('game_select').value, 'country_code=' + $('country_select').value), '');
    setLinkNode(clone, 'result_table_tfoot_td_a_rss1.0_clone', new Array('format=rss1.0', 'max=' + $('max_select').value, 'start=' + firstPosition, 'game=' + $('game_select').value, 'country_code=' + $('country_select').value), '');
    setLinkNode(clone, 'result_table_tfoot_td_a_sslf_clone', new Array('format=sslf', 'max=' + $('max_select').value, 'start=' + firstPosition, 'game=' + $('game_select').value, 'country_code=' + $('country_select').value), '');

    element.appendChild(clone);
    clone.style.display = 'block';
    return true;
}

function showPlayerList(element, playerList, type, countrycode) {
    // TODO: refine
    element.innerHTML = '';
    var oldClone = $('players_table_template_clone');
    if (oldClone) oldClone.parentNode.removeChild(oldClone);
    if (!playerList || !hasProperty(playerList)) {
        element.innerHTML = '<b>No players found.</b>';
        return false;
    }
    var template = $('players_table_template');
    var clone = cloneNode(template, '_clone');
    var tbody = nodeGetElementById(clone, 'players_table_tbody_clone');
    var tr = nodeGetElementById(clone,'players_table_tbody_tr_clone');
    for (var i in playerList) {
        var tr_i = cloneNode(tr, '_' + i);
        var name = playerList[i]['coloredname'];

        // add stats link to playername (thx ayunyan :D)
        if ('ETQWS' == type) {
            if ('JP' == countrycode) {
                name = '<a href="http://stats.etqw.jp/search/player?q=' + encodeURIComponent(playerList[i]['stripedname']) + '" target="_blank">' + name + '</a>';
            } else {
                name = '<a href="http://stats.enemyterritory.com/search?username=' + encodeURIComponent(playerList[i]['stripedname']) + '" target="_blank">' + name + '</a>';
            }
        }

        setTdNode(tr_i, 'players', 'name', i, name, playerList[i]['stripedname']);
        setTdNode(tr_i, 'players', 'score', i, formatItem(playerList[i]['score']), formatItem(playerList[i]['score'], 100));
        setTdNode(tr_i, 'players', 'ping', i, formatItem(playerList[i]['ping']), formatItem(playerList[i]['ping'], 100));
        setTdNode(tr_i, 'players', 'rate', i, formatItem(playerList[i]['rate']), formatItem(playerList[i]['rate'], 100));
        setTdNode(tr_i, 'players', 'clan', i, playerList[i]['coloredclan'], playerList[i]['stripedclan']);
        setTdNode(tr_i, 'players', 'team', i, formatItem(playerList[i]['team']), formatItem(playerList[i]['team'], 100));
        setTdNode(tr_i, 'players', 'time', i, formatItem(playerList[i]['time']), formatItem(playerList[i]['time'], 100));
        tbody.appendChild(tr_i);
    }
    tr.parentNode.removeChild(tr); // remove template tr
    element.appendChild(clone);
    clone.style.display = 'block';
    return true;
}

///
/// entry point
///

addListener(window, 'load', setListeners, false);
