/*
 * Copyright 2008 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * Original Author: Eric Pollmann <pollmann@google.com>
 */

var app = new App()

function ContainerSuite(spec, tests) {
  this.spec = spec
  this.tests = tests
}
ContainerSuite.prototype.getTests = function (filter) {
  var testsToReturn = {}
  for (var test_id in this.tests) {
    var test = this.tests[test_id]
    if (!filter || filter(test)) {
      testsToReturn[test_id] = test
    }
  }
  return testsToReturn
}

function Container(spec, suitename, user_id) {
  this.spec = spec
  this.suitename = suitename
  this.user_id = user_id
  this.suites = {}
}
Container.prototype.updateSuite = function (suite_id, spec, tests) {
  this.suites[suite_id] = new ContainerSuite(spec, tests)
}
Container.prototype.getStats = function (suite_id, filter) {
  var suite = this.suites[suite_id]
  var states = {}
  var tests = suite.getTests(filter)
  for (var test_id in tests) {
    var test = tests[test_id]
    if (!states[test.status]) {
      states[test.status] = 1
    } else {
      states[test.status]++
    }
  }
  return states
}
Container.prototype.getAllStats = function (filter) {
  var states = {}
  for (var suite_index in this.suites) {
    var tests = this.suites[suite_index].getTests(filter)
    for (var test_id in tests) {
      var test = tests[test_id]
      if (!states[test.status]) {
        states[test.status] = 1
      } else {
        states[test.status]++
      }
    }
  }
  return states
}
Container.prototype.getTests = function(suite_id, filter, test_id) {
  var suite = this.suites[suite_id]
  if (suite) {
    var tests = suite.getTests(filter)
    if (test_id) {
      tests = tests[test_id]
    }
  }
  return tests
}
Container.prototype.getAllTests = function(filter) {
  var tests = {}
  for (var suite_index in this.suites) {
    var suite_tests = this.suites[suite_index].getTests(filter)
    for (var test_id in suite_tests) {
      tests[test_id] = suite_tests[test_id]
    }
  }
  return tests
}

function Suite(id, name, filter, verbose, sort, unified, show_priority) {
  this.id = id
  this.name = name
  this.filter = filter
  this.verbose = verbose
  this.sort = sort
  this.unified = unified
  this.show_priority = show_priority
  this.containers = []
}
Suite.prototype.addContainer = function (container) {
  if (this.containers.indexOf(container) < 0) {
    this.containers.push(container)
  }
}
Suite.prototype.removeContainer = function (container_spec) {
  found_index = -1
  for (var i=0; i < this.containers.length; i++) {
    if (this.containers[i].spec == container_spec) {
      found_index = i
    }
  }
  if (found_index >= 0) {
    this.containers.splice(found_index, 1)
  }
}
Suite.prototype.testsToDisplay = function () {
  // Determine which rows to paint, one per test in any container
  var testsToDisplayDict = {}
  for (var c_index=0; c_index < this.containers.length; c_index++) {
    var tests = {}
    if (this.id == '_SUM') {
      tests = this.containers[c_index].getAllTests(this.filter)
    } else {
      tests = this.containers[c_index].getTests(this.id, this.filter)
    }
    for (var test_id in tests) {
        testsToDisplayDict[test_id] = tests[test_id]
    }
  }
  // Convert to list
  var testsToDisplay = []
  for (var test_id in testsToDisplayDict) {
    testsToDisplay.push(testsToDisplayDict[test_id])
  }
  return testsToDisplay.sort(this.sort)
}
Suite.prototype.render = function (is_top, full_render) {
  this.table = document.getElementById('results_table_')
  if ((!this.table && this.unified) || full_render) {
    this.paintContainer()
  }
  this.title.innerHTML=entityEscape(this.name)

  // Inefficient hack: clear head, then rerender, as needed
  if (!this.head || full_render) {
    this.head = document.createElement('thead')
    this.table.appendChild(this.head)
  }
  if (!this.verbose && this.unified && this.id != '_SUM') {
    this.head.className='hidden'
  } else {
    this.head.className=''
  }
  while (this.head.firstChild) {
    this.head.removeChild(this.head.firstChild)
  }

  if (this.verbose ||
      !this.unified ||
      is_top) {
    this.paintHeader(is_top)
  }

  // Inefficient hack: clear body, then rerender, as needed
  if (!this.body || full_render) {
    this.body = document.createElement('tbody')
    this.table.appendChild(this.body)
  }
  while (this.body.firstChild) {
    this.body.removeChild(this.body.firstChild)
  }
  if (this.verbose && this.id != '_SUM') {
    this.paintBody()
  }
  this.paintFooter()
}
Suite.prototype.paintContainer = function () {
  this.title = document.createElement('p')
  this.title.className = "subtitle"
  if (!this.unified) {
    var content = document.getElementById('content')
    content.appendChild(this.title)
  }

  if (!this.table) {
    var content = document.getElementById('content')

    this.table = document.createElement('table')
    if (this.unified) {
      var spacer = document.createElement('p')
      spacer.innerHTML = '&nbsp;'
      content.appendChild(spacer)
    }
    content.appendChild(this.table)
  }
  if (this.unified) {
    this.table.id = 'results_table_'
  } else {
    this.table.id = this.id
  }
  this.table.className = 'resultsTable'
}
// Utility method, used in paintHeader
Suite.prototype.fullYear = function(year) {
  year %= 100;
  return year + (year < 69) ? 2000 : 1900;
}
Suite.prototype.paintHeader = function (is_top) {
  var header = document.createElement('tr')
  header.className = 'header'

  var first_cell = document.createElement('td')
  first_cell.align = 'center'
  if (this.verbose) {
    if (this.show_priority) {
      if (this.id == '_SUM') {
        first_cell.innerHTML = '&nbsp;'
        first_cell.className = 'priorityHeader'
        header.appendChild(first_cell)
      } else {
        first_cell.innerHTML = 'Priority'
        first_cell.className = 'priorityHeader'
        header.appendChild(first_cell)
      }
    }
  } else {
    first_cell.innerHTML = '# Tests'
    first_cell.className = 'priorityHeader'
    header.appendChild(first_cell)
  }

  var second_cell = document.createElement('td')
  if (this.verbose) {
    if (this.unified) {
      second_text = entityEscape(this.name)
    } else {
      second_text = 'Test'
    }
  } else {
    second_text = 'Suite'
  }
  second_html = '<div class="tablesubtitle">' + second_text + '</div>'
  add_link = '<div class="changeContainers">Change <a href="about:blank" onclick="return app.showContainerSelector()">[+/-]</a></div>'
  if (is_top) {
    second_cell.innerHTML = '<nobr>' + second_html + ' ' + add_link + '</nobr>'
  } else {
    second_cell.innerHTML = second_html
  }
  second_cell.className = 'titleHeader'
  header.appendChild(second_cell)


  for (var c_index=0; c_index < this.containers.length; c_index++) {
    var container = this.containers[c_index]
    var header_cell = document.createElement('td')
    header_cell.className = 'containerHeader'
    close_link = '<a href="about:blank" onclick="return app.removeContainer(' +
                 "'" + container.spec + "'" + ')">[x]</a>'
    spec_time = new Date(app.containerTime(container.spec) * 1000)
    download_link = '<a href="' +
                    app.XMLURL(container.spec, container.suitename, true) +
                    '"><img src="images/download_xml.jpeg" ' +
                    'alt="Download XML" title="Download XML" border=0/></a>'
    time_string = '<div class="date" title="Submitted' + 
                  (container.user_id ? ' by ' + entityEscape(container.user_id) : '') +
                  ' at ' + spec_time.toLocaleString() + '">' +
                  this.fullYear(spec_time.getYear()) + '-' + (spec_time.getMonth()+1) + '-' +
                  spec_time.getDate() + ' ' + download_link + '</div>'
    if (is_top) {
      header_cell.innerHTML = '<nobr>' + app.containerName(container.spec) + 
                              ': ' + container.suitename +
                              ' ' + close_link +
                              '</nobr><br/>' + time_string
    } else {
      header_cell.innerHTML = app.containerName(container.spec)
    }
    header.appendChild(header_cell)
  }
  this.head.appendChild(header)
}
Suite.prototype.paintBody = function () {
  var tests = this.testsToDisplay()
  for (var t_index=0; t_index < tests.length; t_index++) {
    var test = tests[t_index]
    var test_row = document.createElement('tr')
    if (t_index % 2 == 0) { test_row.className = 'odd' }

    if (this.show_priority) {
      var first_cell = document.createElement('td')
      first_cell.innerHTML = entityEscape(test.priority)
      test_row.appendChild(first_cell)
    }

    var second_cell = document.createElement('td')
    // Here, we can truncate long test names, with the full name on hover.
    //if (test.name.length > 40) {
    //  shortname = test.name.substr(0, 40) + '...'
    //  second_cell.innerHTML = '<a href="about:blank" onclick="return false" ' +
    //                          'class="nolink" title="' +
    //                          entityEscape(test.name) +
    //                          '">' + shortname + '</a>'
    //} else {
    //  second_cell.innerHTML = entityEscape(test.name)
    //}
    second_cell.innerHTML = entityEscape(test.name)
    test_row.appendChild(second_cell)

    for (var c_index=0; c_index < this.containers.length; c_index++) {
      if (!test) break
      var container = this.containers[c_index]
      var c_test = container.getTests(this.id, null, test.id)
      var test_cell = document.createElement('td')
      if (c_test) {
        test_cell.className = c_test.status
        if (this.show_priority) {
          test_cell.className += ' ' + c_test.priority
        }
        test_cell.innerHTML = this.capitalizeStatus(c_test.status)
      } else {
        test_cell.className = 'UNVERIFIED'
        test_cell.innerHTML = 'missing'
      }
      test_row.appendChild(test_cell)
    }
    this.body.appendChild(test_row)
  }
}
Suite.prototype.capitalizeStatus = function (status) {
  return entityEscape(status.substring(0,1).toUpperCase() +
                      status.substring(1,status.length).toLowerCase())
}
Suite.prototype.paintFooter = function () {
  var tests = this.testsToDisplay()
  var total = tests.length

  var total_row = document.createElement('tr')
  total_row.id = this.id

  var first_cell = document.createElement('td')
  if (this.verbose) {
    if (this.show_priority) {
      first_cell.innerHTML = '&nbsp;'
      total_row.appendChild(first_cell)
    }
  } else {
    first_cell.innerHTML = total
    total_row.appendChild(first_cell)
  }

  var second_cell = document.createElement('td')
  if (this.verbose) {
    second_cell.innerHTML = total + ' test' + (total!=1 ? 's' : '')
  } else {
    if (this.id == '_SUM') {
      second_cell.innerHTML = 'Total'
    } else {
      second_cell.innerHTML = entityEscape(this.name)
    }
  }
  total_row.appendChild(second_cell)

  for (var c_index=0; c_index < this.containers.length; c_index++) {
    var container = this.containers[c_index]
    var stats_cell = document.createElement('td')
    stats_cell.className = 'statsCell'
    var stats_nobr = document.createElement('nobr')
    stats_nobr.className = 'statsNobr'

    var stats
    if (this.id == '_SUM') {
      stats = container.getAllStats(this.filter)
    } else {
      stats = container.getStats(this.id, this.filter)
    }
    var states = ['PASS', 'WARNING', 'FAILED', 'UNVERIFIED']
    for (var s_index=0; s_index < states.length; s_index++) {
      var state = states[s_index]
      if (stats[state]) {
        var count = stats[state]
        var stat_div = document.createElement('div')
        stat_div.className = 'totalBar ' + state
        if (this.verbose) {
          stat_div.className += ' verbose'
        }
        stat_div.style.width = count * 100 / total + '%'
        stat_div.innerHTML = count
        if (this.verbose) {
          stat_div.innerHTML += ' test' + (count!=1 ? 's' : '')
        }
        stat_div.title = this.capitalizeStatus(state)
        if (!this.verbose) {
          stat_div.title += ' test' + (count!=1 ? 's' : '')
        }
        stats_nobr.appendChild(stat_div)
      }
    }
    stats_cell.appendChild(stats_nobr)
    total_row.appendChild(stats_cell)
  }
  this.body.appendChild(total_row)
}

//
// DelayedRPC
//
// Tracks timer, disabled elements, throbbers
//
function DelayedRPC(obj, url, method, action, disableds, throbbers) {
  this.timer = setTimeout("alert('Timeout ' + this.action);" +
                          "obj.dismiss()",
                          10000)
  this.disableds = disableds
  this.throbbers = throbbers

  var this_obj = this
  obj.app.rpc(url, 
              function(result) {
                if (result.success) {
                  obj[method](result)
                } else {
                  alert(result.error)
                }
                this_obj.called()
              })
  for (var d_index=0; d_index < this.disableds.length; d_index++) {
    document.getElementById(this.disableds[d_index]).disabled = true
  }
  for (var t_index=0; t_index < this.throbbers.length; t_index++) {
    document.getElementById(this.throbbers[t_index]).className = ''
  }
}
DelayedRPC.prototype.called = function() {
  clearTimeout(this.timer)
  for (var d_index=0; d_index < this.disableds.length; d_index++) {
    document.getElementById(this.disableds[d_index]).disabled = false
  }
  for (var t_index=0; t_index < this.throbbers.length; t_index++) {
    document.getElementById(this.throbbers[t_index]).className = 'hidden'
  }
}


//
// ContainerSelector
//
// Display container selector UI
//
function ContainerSelector(app) {
  this.app = app
  this.fader = document.getElementById('fader')
  if (!this.fader.references) {
    this.fader.references = 0
  }
  this.selector = document.getElementById('selector')
  this.topn_popular_default = 0
  this.suite = ''
}

ContainerSelector.prototype.show = function (containers, select_default) {
  this.containers = {}
  for (var container in containers) {
    this.containers[this.app.containerName(container)] = true
  }
  this.select_default = select_default

  new DelayedRPC(this, '/suites', 'gotSuites', 'getting suite list',
                 ['suiteSelectSelect'], ['suiteSelectThrobber'])
  this.fetchContainers()

  this.fader.references += 1
  this.fader.className = ''
  this.selector.className = ''
  document.getElementById('containerSelectSubmit').focus()
}

ContainerSelector.prototype.fetchContainers = function () {
  var url = '/containers' + (this.suite ? '?suite=' + this.suite : '')
  new DelayedRPC(this, url, 'gotContainers', 'getting container list',
      ['containerSelectSelect', 'containerSelectSelected'],
      ['containerSelectThrobber'])
}

ContainerSelector.prototype.gotContainers = function (result) {
  select = document.getElementById('containerSelectSelect')
  while (select.firstChild) {
    select.removeChild(select.firstChild)
  }

  selected = document.getElementById('containerSelectSelected')
  while (selected.firstChild) {
    selected.removeChild(selected.firstChild)
  }

  var seen = {}
  for (var i=0; i<result.results.length; i++) {
    container = result.results[i].container
    if (!seen[container]) {
      seen[container] = true
      option = document.createElement('option')
      option.value=entityEscape(container)
      option.innerHTML=entityEscape(container)
      if (this.containers[container] || this.select_default &&
          i < this.topn_popular_default) {
        selected.appendChild(option)
      } else {
        select.appendChild(option)
      }
    }
  }
}

ContainerSelector.prototype.gotSuites = function (result) {
  select = document.getElementById('suiteSelectSelect')
  while (select.firstChild) {
    select.removeChild(select.firstChild)
  }

  option = document.createElement('option')
  option.value=''
  option.innerHTML="(all)"
  select.appendChild(option)

  for (var i=0; i<result.results.length; i++) {
    suite = result.results[i].suite
    if (suite) {
      option = document.createElement('option')
      option.value=entityEscape(suite)
      option.innerHTML=entityEscape(suite)
      if (this.suite == suite) {
        option.selected = true
      }
    }
    select.appendChild(option)
  }
}

ContainerSelector.prototype.updateSuite = function () {
  select = document.getElementById('suiteSelectSelect')
  this.suite = select.value
  this.fetchContainers()
}

ContainerSelector.prototype.update = function (select_all, remove_all) {
  select = document.getElementById('containerSelectSelect')
  selected = document.getElementById('containerSelectSelected')
  for (var i=select.childNodes.length-1; i>=0; i--) {
    option = select.childNodes[i]
    if (option.selected || select_all) {
      select.removeChild(option)
      selected.appendChild(option)
      option.selected = false
    }
  }
  for (var i=selected.childNodes.length-1; i>=0; i--) {
    option = selected.childNodes[i]
    if (option.selected || remove_all) {
      selected.removeChild(option)
      select.appendChild(option)
      option.selected = false
    }
  }
  document.getElementById('containerSelectSubmit').focus()
  return false
}

ContainerSelector.prototype.dismiss = function (selected) {
  this.fader.references -= 1
  if (! this.fader.references) {
    this.fader.className = 'hidden'
  }
  this.selector.className = 'hidden'
}

ContainerSelector.prototype.selectionMade = function () {
  suite = document.getElementById('suiteSelectSelect').value
  selected = document.getElementById('containerSelectSelected')
  selectedContainers = []
  for (var i=0; i<selected.childNodes.length; i++) {
    selectedContainers.push(selected.childNodes[i].value)
  }
  this.dismiss()
  this.app.updateContainers(selectedContainers, suite)
  return false
}

//
// AdminConsole
//
// Display admin console
//
function AdminConsole(app) {
  this.app = app
  this.admin = document.getElementById('admin')
  this.adminLogin = document.getElementById('adminLogin')
  this.adminLogout = document.getElementById('adminLogout')
  this.adminDisallow = document.getElementById('adminDisallow')
  this.adminPanel = document.getElementById('adminPanel')
  this.fader = document.getElementById('fader')
  if (!this.fader.references) {
    this.fader.references = 0
  }
  this.loadingLogin = false
  this.suite = ''
  this.container = ''
}
AdminConsole.prototype.show = function () {
  this.fader.references += 1
  this.fader.className = ''
  this.admin.className = ''
  this.checkAdmin()
  new DelayedRPC(this, '/suites', 'gotSuites', 'getting suite list',
                 ['adminSuiteSelectSelect'], ['adminSuiteSelectThrobber'])
  this.fetchContainers()
}
AdminConsole.prototype.dismiss = function () {
  this.fader.references -= 1
  if (! this.fader.references) {
    this.fader.className = 'hidden'
  }
  this.admin.className = 'hidden'
  return false
}
AdminConsole.prototype.adminLoginLoaded = function() {
  if (this.loadingLogin) {
    this.loadingLogin = false
  } else {
    this.checkAdmin()
  }
}
AdminConsole.prototype.checkAdmin = function() {
  new DelayedRPC(this, '/admin', 'checkedAdmin', 'checking admin capability',
                 [], ['adminThrobber'])
}
AdminConsole.prototype.checkedAdmin = function (result) {
  if (result.username == '') {
    this.adminLogin.src = result.login_url
    this.loadingLogin = true
  } else {
    this.adminLogout.href = result.logout_url
  }
  this.adminLogin.className = (result.username == '' ? '' : 'hidden')
  this.adminLogout.className = (result.username == '' ? 'hidden' : '')
  this.adminPanel.className = (result.username != '' && result.is_admin ?
                               '' : 'hidden')
  this.adminDisallow.className = (result.username == '' || result.is_admin ?
                                  'hidden' : '')
}
AdminConsole.prototype.gotSuites = function (result) {
  var select = document.getElementById('adminSuiteSelectSelect')
  while (select.firstChild) {
    select.removeChild(select.firstChild)
  }

  var option = document.createElement('option')
  option.value=''
  option.innerHTML="(all)"
  select.appendChild(option)

  for (var i=0; i<result.results.length; i++) {
    var suite = result.results[i].suite
    if (suite) {
      option = document.createElement('option')
      option.value=entityEscape(suite)
      option.innerHTML=entityEscape(suite)
      if (this.suite == suite) {
        option.selected = true
      }
    }
    select.appendChild(option)
  }
}
AdminConsole.prototype.updateSuite = function () {
  var select = document.getElementById('adminSuiteSelectSelect')
  this.suite = select.value
  this.fetchContainers()
}
AdminConsole.prototype.fetchContainers = function () {
  var url = '/containers' + (this.suite ? '?suite=' + this.suite : '')
  new DelayedRPC(this, url, 'gotContainers', 'getting container list',
      ['adminContainerSelectSelect'], ['adminSuiteSelectThrobber'])
}
AdminConsole.prototype.gotContainers = function (result) {
  var select = document.getElementById('adminContainerSelectSelect')
  while (select.firstChild) {
    select.removeChild(select.firstChild)
  }

  var seen = {}
  for (var i=0; i<result.results.length; i++) {
    var container = result.results[i].container
    if (!seen[container]) {
      seen[container] = true
      var option = document.createElement('option')
      option.value=entityEscape(container)
      option.innerHTML=entityEscape(container)
      if (this.container == container) {
        option.selected = true
      }
      select.appendChild(option)
    }
  }
}
AdminConsole.prototype.updateContainer = function () {
  var select = document.getElementById('adminContainerSelectSelect')
  this.container = select.value
  this.fetchResults()
}
AdminConsole.prototype.fetchResults = function () {
  var url = '/results?suite=' + this.suite + '&container=' + this.container +
            '&max_results=50'
  new DelayedRPC(this, url, 'gotResults', 'getting results list',
      ['adminResultsSelectSelect'], ['adminThrobber'])
}
AdminConsole.prototype.gotResults = function (result) {
  var select = document.getElementById('adminResultsSelectSelect')
  while (select.firstChild) {
    select.removeChild(select.firstChild)
  }
  var seen = {}
  for (var i=0; i<result.results.length; i++) {
    var create_timestamp = result.results[i].create_time
    var create_time = (new Date(create_timestamp * 1000)).toLocaleString()
    if (!seen[create_time]) {
      seen[create_time] = true
      var option = document.createElement('option')
      option.value=create_timestamp
      option.innerHTML=entityEscape(create_time)
      select.appendChild(option)
    }
  }
}
AdminConsole.prototype.updateResults = function (select_all) {
  var select = document.getElementById('adminResultsSelectSelect')
  child = select.firstChild
  if (select_all) {
    while (child) {
      child.selected = true
      child = child.nextSibling
    }
  }
  this.results = select.value
  return false
}
AdminConsole.prototype.adminDo = function (action, form) {
  suite = form.adminSuiteSelectSelect.value
  container = form.adminContainerSelectSelect.value
  results = []
  resultsSelect = form.adminResultsSelectSelect
  for (var i=resultsSelect.childNodes.length-1; i>=0; i--) {
    option = resultsSelect.childNodes[i]
    if (option.selected) {
      results.push(option.value)
    }
  }
  if (action == 'display') {
    var url = ''
    for (var i=0; i<results.length; i++) {
      if (url != '') {
        url += ','
      }
      url += container + '@' + results[i]
    }
    this.app.start(url)
  } else {
    for (var i=0; i<results.length; i++) {
      var url = '/results?delete=1&suite=' + suite +
                '&container=' + container + '&create_time=' + results[i]
      var capAction = action.substr(0,1).toUpperCase() +
                      action.substr(1, action.length -1)
      if (confirm(capAction + ' ' + suite + ':' + container + '@' + results + '?')) {
        new DelayedRPC(this, url, 'adminDone', '',
            ['adminResultsSelectSelect', 'adminResults' + capAction],
            ['adminThrobber'])
      }
    }
  }
  return false
}
AdminConsole.prototype.adminDone = function (result) {
  this.updateContainer()
}

//
// UploadForm
//
// Display upload form UI
//
function UploadForm(app) {
  this.app = app
  this.upload = document.getElementById('upload')
  this.fader = document.getElementById('fader')
  if (!this.fader.references) {
    this.fader.references = 0
  }
}

UploadForm.prototype.show = function () {
  this.fader.references += 1
  this.fader.className = ''
  this.upload.className = ''
  document.getElementById('uploadFormSubmit').focus()
}

UploadForm.prototype.dismiss = function () {
  this.fader.references -= 1
  if (! this.fader.references) {
    this.fader.className = 'hidden'
  }
  this.upload.className = 'hidden'
}
UploadForm.prototype.uploadStart = function() {
  document.getElementById('uploadFormSubmit').disabled=true
  document.getElementById('uploadThrobber').className=''
  obj = this
  this.uploadTimer = setTimeout("alert('Timeout uploading results'); obj.dismiss()",
                                 15000)
  return true // Don't stop form from submitting.
}
UploadForm.prototype.uploadDone = function() {
  clearTimeout(this.uploadTimer)

  document.getElementById('uploadFormSubmit').disabled=false
  document.getElementById('uploadThrobber').className='hidden'
  iframe = document.getElementById('uploadResult')
  contents = iframe.contentWindow.document.body.innerHTML
  // Some browsers *ahem*Firefox*ahem* wrap contents in a <pre>
  js_start = contents.indexOf('{')
  js = contents.substring(js_start, contents.indexOf('}', js_start) + 1)
  this.dismiss()
  eval('r=' + js)
  if (r.success) {
    // On upload, add new results, remove any old results for this container
    containers = []
    containers.push(app.containerSpec(r.container, r.create_time))
    for (var container in this.app.containers) {
      if (this.app.containerName(container) != r.container) {
        containers.push(container)
      }
    }
    this.app.updateContainers(containers)
    if (r.message) {
      alert(r.message)
    }
  } else {
    if (r.error) {
      alert('ERROR: ' + r.error)
    }
  }
  return true // Don't stop onload from firing.
}

//
// Application
//
// Application level control: load XML file, change display mode
// rerender, add a test suite, add the summary table
//
function cmp(a, b, attr) { return (b[attr] < a[attr]) - (a[attr] < b[attr]) }
function sortByPriority(a, b) { return cmp(a, b, 'priority') }
function sortByName(a, b) { return cmp(a, b, 'name') }
function p0Tests(t) { return t.priority=='P0' }
function entityEscape(str) {
  if (str != null) {
    return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
             .replace(/"/g, '&quot;').replace(/'/g, '&apos;')
  } else {
    return str
  }
}

function App() {
  this.filter = null
  this.verbose = true
  this.extra = false
  this.sort = sortByPriority
  this.unified = true
  this.show_priority = true
  this.debug = false
  this.admin = false
  this.suites = []
  this.containers = {}
  this.xmlDoc = {}
  this.requestTimers = {}
  this.historic_view = false
}

App.prototype.start = function (hash) {
  if (hash) {
    location.hash = hash
  }
  if (location.hash) {
    hash = location.hash.substr(1)
    var colon_pos = hash.indexOf(':')
    var suite = ''
    if (colon_pos >= 0) {
      suite = hash.substr(0, colon_pos)
      hash = hash.substr(colon_pos + 1)
    }
    containers = hash.split(',')
    this.updateContainers(containers, suite)
  } else {
    this.showContainerSelector(true)
  }
}

App.prototype.showContainerSelector = function (default_selection) {
  this.log('Info: Showing container selector.')  
  if (! this['containerSelector']) {
    this.containerSelector = new ContainerSelector(this)
  }
  this.containerSelector.show(this.containers, default_selection)
  return false
}

App.prototype.updateContainers = function (containers, suite, force_historic) {
  this.containers_remaining = containers.length // Render once at end
  this.historic_view = force_historic // (force_historic ||
                       // this.containerTime(containers[0]) === null) &&
                       // containers.length == 1)
  for (var container_spec in this.containers) {
    if (containers.indexOf(container_spec) < 0 || this.historic_view) {
      this.removeContainer(container_spec, true) // Skip render
    } else {
      this.containers_remaining-- // Already have it, one less to load
    }
  }
  rendered = false
  for (var c_index = 0; c_index < containers.length; c_index++) {
    container = containers[c_index]
    if (!this.containers[container]) {
      this.requestXML(container, suite)
      rendered = true
    }
  }
  if (!rendered) {
    this.render(false)
  }

  // Force selection of at least one container in containerSelector.
  if (containers.length < 1) {
    this.showContainerSelector(true)
  }
}

App.prototype.showAdminConsole = function() {
  this.log('Info: Showing admin.')
  if (! this['adminConsole']) {
    this.adminConsole = new AdminConsole(this)
  }
  this.adminConsole.show()
  return false
}

App.prototype.showUploadForm = function () {
  this.log('Info: Showing upload form.')
  if (! this['uploadForm']) {
    this.uploadForm = new UploadForm(this)
  }
  this.uploadForm.show()
  return false
}

// Assumes response is JSON
App.prototype.rpc = function (url, callback) {
  // Verbose RPC tracing
  // app.log('Calling RPC: ' + url + ' with callback: ' + callback)
  var xmlHttp=new XMLHttpRequest()
  xmlHttp.onreadystatechange = function () {
    if (xmlHttp.readyState == 4) {
      try { 
        ajax = xmlHttp.responseText
        eval('r=' + ajax)
        // Verbose RPC tracing
        // app.log('Got response from RPC: ' + url + ' => ' + r)
      } catch(e) {
        r = {'success': false,
             'error': 'AJAX syntax error for: ' + url }
      }
      callback(r)
    }
  }
  xmlHttp.open("GET", url, true)
  xmlHttp.send(null)
}
App.prototype.finishedLoad = function() {
  this.containers_remaining-- // Failed, don't expect to render this.
  if (this.containers_remaining == 0) {
    this.render(false)
  }
}
App.prototype.containerName = function(containerspec) {
  containername = containerspec
  specs = containerspec.split('@')
  if (specs.length > 1) {
    containername = specs[0]
    time = specs[1]
  }
  return entityEscape(containername)
}

App.prototype.containerTime = function(containerspec) {
  time = null
  specs = containerspec.split('@')
  if (specs.length > 1) {
    containername = specs[0]
    time = specs[1]
  }
  return entityEscape(time)
}

App.prototype.containerSpec = function(containerName, containerTime) {
  return entityEscape(containerName + '@' + containerTime)
}

App.prototype.XMLURL = function (container_spec, suitename, raw) {
  var container_name = this.containerName(container_spec)
  var create_time = this.containerTime(container_spec)
  var max_results = ''
  if (this.historic_view && ! raw) {
    max_results = 5
    create_time = ''
  }
  return 'results?container=' + container_name +
         (max_results > 1 ? '&max_results=' + max_results : '') +
         (create_time ? '&create_time=' + create_time : '') +
         (suitename ? '&suite=' + suitename : '') +
         (raw ? '&format=xml' : '')
}

App.prototype.requestXML = function (container_spec, suitename, raw) {
  this.log('Requesting XML for: ' + container_spec + ': ' + suitename)
  obj = this
  this.requestTimers[container_spec] =
    setTimeout("alert('Timeout loading test results for " + container_spec +
               "')", 15000)
  this.rpc(this.XMLURL(container_spec, suitename, raw),
           function(result) {
             clearTimeout(obj.requestTimers[container_spec])
             delete(obj.requestTimers[container_spec])
             if (result.success && (result.results.length > 0)) {
               if (result.results.length > 1) {
                 obj.containers_remaining += result.results.length - 1
               }
               for (var i=0; i < result.results.length; i++) {
                 r = result.results[i]
                 spec = obj.containerSpec(r.container, r.create_time)
                 obj.parseXML(spec, r.suite, r.results_xml, r.user_id)
                 // XXX Validate?
                 //     r.num_tests, r.num_tests_passed
               }
             } else {
               obj.finishedLoad() // Can't parse, don't expect container
               if (result.error) {
                 alert(result.error)
               }
             }
           })
}

App.prototype.parseXML = function (container_spec, suitename, results_xml, user_id) {
  var isBad = false
  var ele = null
  try {
    parser = new DOMParser()
    ele = parser.parseFromString(results_xml, 'text/xml')
    if (cb_isWebkit && ele && ele.documentElement &&
        ele.documentElement.getElementsByTagName('parsererror').length) {
      isBad = true
    } else {
      isBad = !ele || !ele.documentElement ||
              ele.documentElement.nodeName=='parsererror' ||
              (ele.parseError && ele.parseError.errorCode !== 0)
    }
  } catch(e) {
    isBad = true
  }
  if (isBad) {
    this.log('Error: Bad XML for ' + container_spec + '\n' +
             'XML: ' + results_xml)
  } else {
    this.processXML(container_spec, suitename, ele, user_id)
  }
}
  
App.prototype.processXML = function (container_spec, suitename, ele, user_id) {
  this.log('Info: Parsing ' + container_spec + ': ' + suitename + '...')
  var runs = ele.getElementsByTagName('TestRun')

  // XXX Validate against XML's container parameter?
  // var containername = runs[0].getAttribute('container')

  if (!this.containers[container_spec]) {
    this.log('Info: New container ' + container_spec)
    this.containers[container_spec] = new Container(container_spec, suitename, user_id)
  }
  var container = this.containers[container_spec]
  var suites = runs[0].getElementsByTagName('Suite')
  for (var ds_index=0; ds_index < suites.length; ds_index++) {
    var suite = suites[ds_index]
    try {
      var suite_id = suite.getAttribute('id')
      var suite_name = suite.getAttribute('name')
    } catch(e) {
      suite = null
    }

    if (suite) {
      var testlist = {}
      var tests = suite.getElementsByTagName('Test')
      for (var t_index=0; t_index < tests.length; t_index++) {
        var t = tests.item(t_index)
        try {
          var id = t.getAttribute('id')
          testlist[id] = {}
          testlist[id].id = id
          testlist[id].name = t.getAttribute('name')
          testlist[id].status = t.getAttribute('status')
          testlist[id].priority = t.getAttribute('priority')
        } catch(e) {
          this.log('Error: Parsing XML for test: ' + t)
        }
      }
      container.updateSuite(suite_id, suite_name, testlist)
      this.addSuite(suite_id, suite_name)
      for (var ts_index=0; ts_index < this.suites.length; ts_index++) {
        suite = this.suites[ts_index]
        if (suite.id == suite_id) {
          suite.addContainer(container)
        }
      }
      if (this.summarySuite) {
        this.summarySuite.addContainer(container)
      }
    }
  }
  this.finishedLoad()
}
App.prototype.addSuite = function (id, name) {
  // Search for existing suite with this id => bail early
  for (var s_index=0; s_index < this.suites.length; s_index++) {
    if (this.suites[s_index].id == id) {
      return
    }
  }
  suite = new Suite(id, name, this.filter, this.verbose, this.sort,
                    this.unified, this.show_priority)
  if (id == '_SUM') {
    this.summarySuite = suite
  } else {
    this.suites.push(suite)
  }
  this.log('Info: Added suite ' + suite.id + ' size: ' + this.suites.length)
}

App.prototype.removeContainer = function (container, internal) {
  this.log('Info: App: remove container "' + container + '".')
  delete this.containers[container]
  num_containers = 0
  last_container = ''
  for (var temp_container in this.containers) {
    num_containers++
    last_container = temp_container
  }
  for (var s_index = 0; s_index < this.suites.length; s_index++) {
    this.suites[s_index].removeContainer(container)
  }
  this.summarySuite.removeContainer(container)
  if (! internal) {
    if (this.historic_view) { // corrupted, make static now
      this.historic_view = false
    }
    this.render(false)

    // Automatically pop up container selector if all are removed
    if (!num_containers) {
      this.showContainerSelector(true)
    }
  }

  return false
}

App.prototype.setProperty_ = function (element, property, value, rendertype) {
  this.log('Info: Setting property ' + property + ' to ' + value)
  if (this[property] == value) {
    return false
  }
  var classname = 'selected ' + property
  var previous_selected = document.getElementsByClassName(classname)[0]
  if (previous_selected) {
    previous_selected.className = 'unselected ' + property
  }
  element.className = classname

  this[property] = value
  for (var s_index = 0; s_index < this.suites.length; s_index++) {
    this.suites[s_index][property] = value
  }
  if (this.summarySuite) {
    this.summarySuite[property] = value
  }

  if (rendertype > 1) {
    this.render(true)
  } else if (rendertype) {
    this.render(false)
  }
  return false
}
App.prototype.setFilter = function (element, filter) {
  return this.setProperty_(element, 'filter', filter, 1)
}
App.prototype.setVerbose = function (element, verbose) {
  return this.setProperty_(element, 'verbose', verbose, 1)
}
App.prototype.setExtra = function (element, extra) {
  extraSpan = document.getElementById('extra')
  if (extra) {
    extraSpan.className = ''
  } else {
    extraSpan.className = 'hidden'
  }
  return this.setProperty_(element, 'extra', extra, 0)
}
App.prototype.setSort = function (element, sort) {
  return this.setProperty_(element, 'sort', sort, 1)
}
App.prototype.setUnified = function (element, unified) {
  return this.setProperty_(element, 'unified', unified, 2)
}
App.prototype.setShowPriority = function (element, show, paint_type) {
  if (paint_type === undefined) {
    paint_type = 1
  }
  return this.setProperty_(element, 'show_priority', show, paint_type)
}
App.prototype.setDebug = function (element, debug) {
  debugDiv = document.getElementById('debug')
  if (debug) {
    debugDiv.className = ''
  } else {
    debugDiv.className = 'hidden'
  }
  return this.setProperty_(element, 'debug', debug, 0)
}
App.prototype.setHistoric = function(element, historic) {
  container_list = []
  for (var container in this.containers) {
    if (container_list.indexOf(this.containerName(container)) < 0) {
      container_list.push(this.containerName(container))
    }
  }
  this.updateContainers(container_list, '', true)
  return false
}  
App.prototype.updateLocation = function () {
  hash = '#'
  if (this.historic_view) {
    for (var container_spec in this.containers) { // Ugh
      hash += this.containerName(container_spec)
      break
    }
  } else {
    for (var spec in this.containers) {
      if (hash != '#') {
        hash += ',' + spec
      } else {
        hash += spec
      }
    }
  }

  // Don't set location to '', forces reload, and also skip update if we
  // are already set to the correct hash (store as member)
  if (hash && this.location != hash) {
    this.log('Info: Updating location to ' + hash)
    try {
      location = hash
    } catch(e) {}
    this.location = hash
  }
}
App.prototype.render = function (full_render) {
  if (!this.summarySuite) {
    this.addSummaryTable()
    full_render = true
  }
  this.log('Info: App render called.  full: ' + full_render)
  this.updateLocation()
  num_container_names = 0
  seen_container_names = {}
  for (var container in this.containers) {
    if (!seen_container_names[this.containerName(container)]) {
      num_container_names++
      seen_container_names[this.containerName(container)] = true
    }
  }
  if (num_container_names == 1 && !this.historic_view) {
    document.getElementById('switchToHistoric').className = ''
  } else {
    document.getElementById('switchToHistoric').className = 'hidden'
  }
  if (full_render || !num_container_names) {
    var content = document.getElementById('content')
    while (content.firstChild) {
      content.removeChild(content.firstChild)
    }
  }
  if (num_container_names) {
    if (this.summarySuite) {
      this.summarySuite.render(true, full_render)
    }
    // Show People suite first
    for (var s_index = 0; s_index < this.suites.length; s_index++) {
      suite = this.suites[s_index]
      if (suite.id == 'PPL') {
        suite.render(false, full_render)
      }
    }
    for (var s_index = 0; s_index < this.suites.length; s_index++) {
      suite = this.suites[s_index]
      if (suite.id != 'PPL') {
        suite.render(false, full_render)
      }
    }
    document.getElementById('noContent').className = 'hidden'
  } else {
    this.summarySuite = null
    document.getElementById('noContent').className = ''
  }
}
App.prototype.addSummaryTable = function () {
  this.addSuite('_SUM', 'Total')
  for (var container_spec in this.containers) {
    this.summarySuite.addContainer(this.containers[container_spec])
  }
}
App.prototype.log = function (message) {
  debugLog = document.getElementById('debugLog')
  debugLog.innerHTML += message + '<br/>'
  debug = document.getElementById('debug')
  debug.scrollTop = debug.scrollHeight
}
