//--- Function Wrappers -------------------------------------------------------
// Returns an AJAX object or FALSE if browser doesn't support them.
function GetXmlHttpObject()
{
    if (window.XMLHttpRequest)
        return new XMLHttpRequest();

    // Old IE versions
    else if (window.ActiveXObject)
        return new ActiveXObject("Microsoft.XMLHTTP");

    return false;
}


// Add an event handler to an object.
// NOTE: Using W3C convention (i.e: "load" instead of "onload")
function AddEventListener(obj, eventName, func, capture)
{
    if (capture == null)
        capture = false

    // W3C
    if (obj.addEventListener)
        return obj.addEventListener(eventName, func, capture)

    // Microsoft
    else if (obj.attachEvent)
        return obj.attachEvent("on"+eventName, func)

    // Unable to comply
    return false
}


// Sterilize string by replacing <, > and & characters to what they should be.
function sterilizeString(s)
{
    var newString = ''
    for (var i = 0; i < s.length; i++)
    {
        switch (s[i])
        {
        case '<':
            newString += '&#60;'
            break
        case '>':
            newString += '&#62;'
            break
        case '&':
            newString += '&amp;'
            break
        default:
            newString += s[i]
            break
        }
    }
    
    return newString
}

//--- AJAX Functions ----------------------------------------------------------
// Send a request for the instruction data we should parse.
function beginGetInstrData()
{
    // Send a request for the data XML
    xmlhttp.open("GET", "data.php", true);
    xmlhttp.send(null);
}

// Handle status change for the AJAX request.
function handleReadyStateChange()
{
    if (xmlhttp.readyState == 4)
    {
        // Make sure we've got the file and not an error
        if (xmlhttp.status != 200)
            alert("Unable to retrieve data file - this page might not work!")
        else        
            handleData(xmlhttp.responseXML)
           
        xmlhttp = null // Unassign all the crap in here
    }
}


//--- Data Handling & Display Functions
function createSpanObject(classAttr)
{
    var obj = document.createElement('span')
    obj.className = classAttr
    return obj
}

function createSeparatorSpanObject(sepStr)
{
    var obj = createSpanObject('type_static')
    obj.title = 'Separator'
    obj.innerHTML = sepStr.replace(' ', '<i>sp</i>')
    return obj
}

function repeatArguments(argList, body)
{
    for (var i = 0; i < argList.length; i++)
    {
        var element = transformArgument(argList[i])
        element.title = "REPEATABLE GROUP[" + argList.length + "]: " + element.title
        element.style.opacity = ".7"
        element.style.filter = "alpha(opacity=70)"
        element.innerHTML += " <sup>RG</sup>"
        body.appendChild(element)
    }
    
    return body
}

function transformArgument(arg)
{
    var obj = document.createElement('span')
    var returnObj = false
    switch (arg.type)
    {
        case "null":
            break
        case "static":
            obj.className = 'type_static'
            obj.title = 'Static Data[' + arg.value.length + ']'
            obj.innerHTML = arg.value.replace(' ', '<i>sp</i>')
            returnObj = true
            break
        case "binary":
        case "string":
        case "int":
        case "base95":
        case "base220":
        case "base220str":
        case "colorstr_part":
        case "colorstr":
            obj.className = 'type_' + arg.type
            obj.title = "(" + arg.type + ") " + arg.summary
            obj.innerHTML = arg.name + '[' + arg.sizeStr + ']'
            returnObj = true
            break
        default:
            break
    }
    
    if (arg.optional)
    {
        obj.innerHTML += " <sup>OPT</sup>"
        obj.title = "OPTIONAL: " + obj.title
    }
    
    return (returnObj) ? obj : null
}

// Generate an HTML-based syntax from the instruction object.
function genSyntax(instrObj)
{
    var body = document.createElement('span')
    
    var instrSpanObj = createSpanObject('instr')
    instrSpanObj.innerHTML = sterilizeString(instrObj.prefix)
    body.appendChild(instrSpanObj)

    var arguments = instrObj.arguments
    var separator = instrObj.argSeparator

    // If this instruction has no arguments, release it.
    if (arguments.length == 0)
        return body.innerHTML

    // Go through all the arguments
    var arg
    var firstIteration = true
    var shouldRepeat = false
    var repeatGroup = new Array()
    for (var i = 0; i < arguments.length; i++)
    {
        arg = arguments[i]

        // See if this is a repeatable argument
        if (arg.repeatable)
            repeatGroup.push(arg)
        else if (repeatGroup.length > 0)
        {
            repeatArguments(repeatGroup, body)
            repeatGroup = []
        }
        
        // Add a separator if set
        if (!firstIteration)
        {
            if (separator != "")
                body.appendChild(createSeparatorSpanObject(separator))
        }
        else
            firstIteration = false

        // Set the size string to display
        if (arg.minsize != arg.maxsize)
            var sizeStr = ((arg.minsize > 0) ? arg.minsize : "?") +" - "+ ((arg.maxsize > 0) ? arg.maxsize : "?")
        else
            var sizeStr = (arg.size == 0) ? "*" : arg.size

        // Set the right HTML class according to type
        arg.sizeStr = sizeStr // Trans. func needs this
        var obj = transformArgument(arg)
        if (obj != null)
            body.appendChild(obj)
    }
    
    // Code repeating - I know...
    if (arg.repeatable && repeatGroup.length > 0)
        repeatArguments(repeatGroup, body)
    
    return body.innerHTML
}

// Tries to find an instruction with a specific prefix (not base64-encoded!)
function findInstructionByPrefix(prefix)
{
    for (var i = 0; i < instrSet.length; i++)
        if (instrSet[i].prefix == prefix)
            return instrSet[i]
    return null
}

// Tries to find an instruction with a specific base64-encoded prefix. Uses the
// findInstructionByPrefix() function to do the lookup itself.
function findInstructionByBase64Prefix(b64prefix)
{
    return findInstructionByPrefix(base64Decode(b64prefix))
}

// Tries to find the row object for a specific Furcadia prefix (not base64-encoded!)
function findInstructionRowByPrefix(prefix)
{
    var tBody = document.getElementById('instructions_body')
    if (tBody)
    {
        for (var row = tBody.firstChild; row; row = row.nextSibling)
        {
            if (row.furcInstructionPrefix == prefix)
                return row
        }
    }
    return null
}


// Generate an HTML string from an XML description.
function genDescription(instrObj)
{
    var container = document.createElement('span')
    container.innerHTML = instrObj.description

    for (var i = 0; i < container.childNodes.length; i++)
    {
        var e = container.childNodes[i]
        
        switch (e.nodeName.toLowerCase())
        {
        case "argument":
            if (!e.attributes) break

            var argName = e.attributes.getNamedItem('name')
            var argObj = argName ? instrObj.getArgumentByName(argName.value) : null
            
            if (argObj)
                container.replaceChild(transformArgument(argObj), e)
            else
                container.replaceChild(document.createTextNode(argObj.name), e)

            break
        case "command":
            if (!e.attributes) break
            
            var cmdName = e.attributes.getNamedItem('name')
            var cmdString = e.attributes.getNamedItem('cmd')            
            var elem = document.createElement('span')
            elem.className = "cmd"
            elem.title = "(Regular Command)"
            
            if (cmdString)
            {
                var cmdSaid = e.attributes.getNamedItem('said')
                if (cmdString.value[0] == '$' || (cmdSaid && cmdSaid.value != '0'))
                {
                    elem.className = 'cmd_said'
                    elem.title = "(Said Command)"
                }
                elem.innerHTML = cmdString.value
            }
            else if (cmdName)
                elem.innerHTML = cmdName.value
            else
                break
            
            container.replaceChild(elem, e)
            break
        case "instruction":
            if (!e.attributes) break
            
            var instrPrefix = e.attributes.getNamedItem('prefix')
            if (instrPrefix)
            {
                var prefix = base64Decode(instrPrefix.value)
                var dispInstrObj = findInstructionByPrefix(prefix)

				var span = createSpanObject('instr')
				span.innerHTML = sterilizeString(prefix)
				span.style.border = "#444 1px solid"
                if (dispInstrObj)
                {
                    span.title = dispInstrObj.name
                    
                    var row = findInstructionRowByPrefix(prefix)
                    if (row)
                    {
                        var attr = document.createAttribute('onclick')
                        attr.nodeValue = 'javascript:window.scrollTo(0,'+ row.offsetTop +')'
                        span.setAttributeNode(attr)
                    }
                    
                }
				container.replaceChild(span, e)
            }
            
            break
        default:
            break
        }
    }
    
    return container.innerHTML
}

// Generates and returns a DIV object with the description of a specific
// instruction object.
function genDescWindow(instrObj)
{
    var container = document.createElement('div')
    container.className="win"
    container.style.margin = 0
    
    var descTitleDiv = document.createElement('div')
    descTitleDiv.className = "win_title"
    descTitleDiv.innerHTML = "Description"
    container.appendChild(descTitleDiv)
    
    var descBodyDiv = document.createElement('div')
    descBodyDiv.className = "win_content instr_description"
    descBodyDiv.innerHTML = genDescription(instrObj)
    
    container.appendChild(descBodyDiv)
    return container
}

// Generates and returns the instruction argument table element.
function genArgumentTable(instrObj)
{
    var argTable = document.createElement('table')
    argTable.className = "verbose_args"
    argTable.width = 500
    
    // Arrange headers for the table.
    var thead = document.createElement('thead')
    var row = thead.insertRow(0)
    var headers = ["Type", "Size (bytes)", "Description", "Summary"]
    
    for (var i = 0; i < headers.length; i++)
    {
        var cell = document.createElement('th')
        cell.innerHTML = headers[i]
        row.appendChild(cell)
    }

    // Populate the table
    var tbody = document.createElement('tbody')
    
    for (var i = 0; i < instrObj.arguments.length; i++)
    {
        var arg = instrObj.arguments[i]
        var row = tbody.insertRow(i)
        var cells = new Array()
        for (var j = 0; j < headers.length; j++)
            cells.push(row.insertCell(j))
        
        cells[0].innerHTML = arg.type
        cells[1].innerHTML = arg.sizeStr
        cells[2].innerHTML = (arg.type != "static") ? arg.name : '<span class="text_insignificant">'+ arg.name +'</span>'
        cells[3].innerHTML = arg.summary
    }    

    argTable.appendChild(thead)
    argTable.appendChild(tbody)
    return argTable
}

// Generates the meta-data HTML from within an instruction object.
function genMetaData(instrObj)
{
    var metaTable = document.createElement('table')
    metaTable.className = "verbose_meta"
    metaTable.width = "100%"
    for (var i = 0; i < instrObj.metadata.length; i++)
    {
        var row = metaTable.insertRow(i)
        var cell = row.insertCell(0)
        
        var metaName = instrObj.metadata[i][0]
        cell.innerHTML = metaName
        cell.width = 150
        
        cell = row.insertCell(1)
        cell.innerHTML = instrObj.metadata[i][1]
        //cell.width = "100%"
    }
    
    return metaTable
}

// Generate verbose information about an instruction. Returns HTML
// data as a string.
function genVerbose(instrObj)
{
    var container = document.createElement('body')

    // Instruction title element
    var instrTitle = document.createElement('h3')
    instrTitle.innerHTML = instrObj.name ? instrObj.name : "Instruction `" + instrObj.prefix + "`"
    
    if (instrObj.deprecated)
        instrTitle.innerHTML += " <sub>(DEPRECATED)</sub>"
    if (instrObj.obsolete)
        instrTitle.innerHTML += " <sub>(OBSOLETE)</sub>"

    container.appendChild(instrTitle)
    
    // Metadata
    container.appendChild(genMetaData(instrObj))

    // Dividing table
    var divTable = document.createElement('table')
    divTable.width = "100%"
    divTable.style.marginTop = "16px"
    divTable.style.marginBottom = "16px"
    divTable.cellPadding = 0
    var row = divTable.insertRow(0)
    
    var elemRows = new Array()
    
    // Argument table (if any arguments present)
    if (instrObj.arguments.length > 0)
        elemRows.push(genArgumentTable(instrObj))
    
    // Description window DIV
    elemRows.push(genDescWindow(instrObj))
    
    // Pushing elements into the dividing table
    for (var i = 0; i < elemRows.length; i++)
    {
        var cell = row.insertCell(i)
        cell.vAlign = "top"
        cell.appendChild(elemRows[i])
        
        if (i+1 == elemRows.length) cell.width="100%"
    }
    
    container.appendChild(divTable)
    return container.innerHTML
}

// Handle mouseclick event on the row
function handleRowClick(rowObj)
{
    // Check if it's already open.
    if (rowObj.nextSibling == null || typeof(rowObj.nextSibling.furcInstructionPrefix) != 'undefined')
    {
        // Find the object we're talking about to extract the rest
        var obj = findInstructionByPrefix(rowObj.furcInstructionPrefix)
        if (obj)
        {
            rowObj.style.background = (obj.obsolete) ? '#300' : (obj.deprecated) ? '#330' : '#030'
            rowObj.style.height = "30px"
            var row = rowObj.parentNode.insertRow(rowObj.rowIndex)
            var cell = row.insertCell(0)
            cell.colSpan = 2
            cell.className = "instr_verbose"        
            cell.innerHTML = genVerbose(obj)
        }
    }
    else
    {
        rowObj.style.background = ""
        rowObj.style.height = ""
        rowObj.parentNode.deleteRow(rowObj.rowIndex)
    }
    
    return true
}

// Handle the received instruction set.
function handleData(data)
{
    document.getElementById('loading_instr').parentNode.style.display = 'none'
    instrSet = extractInstrSet(data)

    // Find TBODY's object where we display the data.
    var tbody = document.getElementById('instructions_body')
    if (tbody == null)
        return alert("Script error: Unable to locate instruction TBODY tag!")

    // Process the instruction set and display the table.
    var lastRow = tbody.rows.length
    for (var i = 0; i < instrSet.length; i++)
    {
        var instrObj = instrSet[i]

        // If they're marked deprecated, set the class to display that.
        var rowClass = (instrObj.obsolete) ? "instr_row_obsolete" : (instrObj.deprecated) ? "instr_row_deprecated" : "instr_row"

        // If we didn't get a name, assume the prefix to be the name.
        var instrName = instrObj.name

        // Set instruction name tags
        if (instrObj.deprecated) instrName += '<span class="subtext_deprecated">(deprecated)</span>'
        if (instrObj.obsolete)   instrName += '<span class="subtext_obsolete">(obsolete)</span>'
        
        // Add to the list
        var row = tbody.insertRow(lastRow++)

        row.className = rowClass
        row.style.cursor = 'pointer'
        
        row.furcInstructionPrefix = instrObj.prefix
        
        var attr = document.createAttribute('onclick')
        attr.nodeValue = 'javascript:handleRowClick(this)'
        row.setAttributeNode(attr)

        var colSyntax = row.insertCell(0)
        colSyntax.vAlign = "center"
        var colName = row.insertCell(1)
        colName.vAlign = "center"

        colSyntax.innerHTML = genSyntax(instrObj)
        colName.innerHTML = instrName
    }
}

// Show or hide a certain object.
function toggleObject(obj, show)
{
    obj.style.display = (obj.style.display == "" || show == false ) ? obj.style.display = 'none' : ''
}

// Show or hide a certain object represented by its ID name.
function toggleElementId(id, show)
{
    toggleObject( document.getElementById(id), show )
}

// Show or hide the glossary area - just the title will be visible if hidden.
function toggleGlossary(show)
{
    toggleElementId('glossary_content', show)
}


//--- Event Handling Functions ------------------------------------------------
function init()
{
    toggleGlossary(false)
    xmlhttp = GetXmlHttpObject();
    if (xmlhttp != false)
    {
        xmlhttp.onreadystatechange = handleReadyStateChange;
        beginGetInstrData();
    }
    else
        alert("Unable to initialize AJAX features - this page might not work!")
}



//--- Initialization ----------------------------------------------------------
var instrSet = null
var xmlhttp = null

// Make sure we run init() when the page loads.
AddEventListener(window, 'load', init, false)
