// ==UserScript==
// @name            AwfulVision
// @namespace       http://sae.tweek.us/AwfulVision/
// @description     See the internet through the eyes of a hateful monster.
// @include         http://*
// @exclude         http://sae.tweek.us/
// @exclude         http://forums.somethingawful.com/misc.php?s=&action=showsmilies
// @run-at document-start
// ==/UserScript==

if (!unsafeWindow)
{
    unsafeWindow = window;
}

// Create dummy GM_log, GM_setValue, and GM_getValue if they aren't implemented.
if (!GM_log)
{
    function GM_log(message)
    {
        console.log('<AwfulVision> ' + message);
    }
}

if (!GM_setValue)
{
    function GM_setValue(name, value)
    {
        GM_log('GM_setValue is not implemented.');
        return;
    }
}

if (!GM_getValue)
{
    function GM_getValue(name, defaultValue)
    {
        GM_log('GM_getValue is not implemented.');
        return defaultValue;
    }
}

if (!unsafeWindow.tweek)
{
    unsafeWindow.tweek = {};
}

unsafeWindow.tweek.awfulVision = new (function()
{
    var body;
    var bodyHeight = 0;
    var currentTime = (new Date()).getTime() / 1000;
    var lastCheck = parseInt(GM_getValue('lastCheck', '0'), 10);
    var updateInterval = 60 * 60;  // 1 Hour
    var emoticons;
    var reTest;
    var reExec;
    var emoticonCodes;
    var emoticonImageMap;
    var excludeTags = [
        'html',
        'head',
        'title',
        'meta',
        'link',
        'script',
        'style',
        'iframe',
        'code',
        'pre',
        'textarea',
        'input'
    ];
    var isGTalk = (/talkgadget\.google\.com|.*\.meebo\.com/).test(window.location.host);

    var replaceEmoticons = function(node)
    {
        var m, img, newNodes = [], last = 0, value = node.nodeValue;

        if (!reTest.test(value))
        {
            return;
        }

        while(m = reExec.exec(value))
        {
            GM_log('Found ' + m[1]);
            newNodes.push(document.createTextNode(value.substring(last, m.index)));
            last = m.index + m[1].length;
            img = document.createElement('img');
            img.setAttribute('src', emoticonImageMap[m[1]]);
            img.setAttribute('alt', m[1]);
            newNodes.push(img);
        }

        newNodes.push(document.createTextNode(value.substr(last)));
        for(var j = 0; j < newNodes.length; j++)
        {
            node.parentNode.insertBefore(newNodes[j], node);
        }
        node.parentNode.removeChild(node);
    };

    var parseNode = function(node)
    {
        var i = 0;
        var l = node.childNodes.length;
        var currentNode;

        for(; i < l; i++)
        {
            currentNode = node.childNodes[i];

            switch(currentNode.nodeType)
            {
                case 1:
                    if (currentNode.getAttribute('contenteditable') == 'true')
                    {
                        GM_log('Ignoring editable node.');
                        continue;
                    }
                    if (excludeTags.indexOf(currentNode.nodeName.toLowerCase()) == -1)
                    {
                        parseNode(currentNode);
                        continue;
                    }
                    break;

                case 3:
                    replaceEmoticons(currentNode);
                    break;
            }
        }
    };

    var checkPageHeight = function()
    {
        if (body.offsetHeight != bodyHeight || isGTalk)
        {
            if (isGTalk)
            {
                GM_log('Rescanning chat frame.');
            }
            else
            {
                GM_log('Page height changed.  Rescanning page.');
            }
            parseNode(body);
            bodyHeight = body.offsetHeight;
        }
        setTimeout(checkPageHeight, 2000);
    };

    var parsePage = function()
    {
        body = document.getElementsByTagName('body').item(0);
        reTest = new RegExp(emoticonCodes);
        reExec = new RegExp('(' + emoticonCodes + ')', 'g');
        GM_log('Emoticon codes: ' + emoticonCodes);
        checkPageHeight();
    };

    var parseEmoticons = function(response)
    {
        GM_log('Emoticons loaded.');
        var i, j, k, l;
        emoticonCodes = [];
        emoticonImageMap = {};
        if (response.status == 200)
        {
            emoticons = JSON.parse(response.responseText);
            for(i = 0, j = emoticons.length; i < j; i++)
            {
                for(j = 0, k = emoticons[i].emoticons.length; j < k; j++)
                {
                    emoticonCodes.push(emoticons[i].emoticons[j].code.replace(/(\(|\)|\*|\/|\?|\.|\^|\,|\+)/g, '\\\$1'));
                    emoticonImageMap[emoticons[i].emoticons[j].code] = emoticons[i].emoticons[j].image;
                }
            }

            // Sorting based on string length.
            emoticonCodes.sort(function(a, b)
            {
                if (a.length < b.length)
                {
                    return 1;
                }
                else if (a.length > b.length)
                {
                    return -1;
                }
                return 0;
            });

            emoticonCodes = emoticonCodes.join('|');

            GM_setValue('emoticonCodes', emoticonCodes);
            GM_setValue('emoticonImageMap', unsafeWindow.JSON.stringify(emoticonImageMap));

            parsePage();
        }
        else
        {
            GM_log('There was a problem loading the emoticon list.');
        }
    };

    var loadEmoticons = function()
    {
        GM_log('Loading external emoticons.');
        GM_xmlhttpRequest({
            'method': 'GET',
            'url': 'http://sae.tweek.us/api/json?' + currentTime,
            'onload': parseEmoticons
        });
    };

    var updateCheck = function(response)
    {
        var lastUpdate = GM_getValue('lastUpdate', 0);
        var emoticonUpdateTime;
        if (response.status == 200)
        {
            emoticonUpdateTime = parseInt(response.responseText, 10);
            if (emoticonUpdateTime > lastUpdate)
            {
                GM_log('There was an update to the emoticon list.');
                GM_setValue('lastUpdate', String(emoticonUpdateTime));
                loadEmoticons();
            }
        }
    };

    var init = function()
    {
        emoticonCodes = GM_getValue('emoticonCodes');
        emoticonImageMap = GM_getValue('emoticonImageMap');

        // currentTime - lastUpdate < updateInterval

        if (emoticonCodes)
        {
            GM_log('Using cache.');
            emoticonImageMap = unsafeWindow.JSON.parse(emoticonImageMap);
            parsePage();
            if (currentTime - lastCheck > updateInterval)
            {
                GM_log('Checking for emoticon updates.');
                GM_setValue('lastCheck', String(currentTime));
                GM_xmlhttpRequest({
                    'method': 'GET',
                    'url': 'http://sae.tweek.us/api/lastupdate?' + currentTime,
                    'onload': updateCheck
                });
            }
        }
        else
        {
            loadEmoticons();
        }
    };
        
    if (unsafeWindow.top == unsafeWindow.self || isGTalk)
    {
        init();
    }
});