#!/usr/bin/python

"""
Selects all rows and columns that satisfy the condition specified
and draws the matrix. There is a seperate SQL query made for every (x,y)
in the matrix.
"""

print "Content-type: text/html\n"

import sys, os, urllib, cgi, cgitb, re, datetime, time

total_wall_time_start = time.time()

import common
from autotest_lib.tko import display, frontend, db, query_lib
from autotest_lib.client.common_lib import kernel_versions

html_header = """\
<form action="/tko/compose_query.cgi" method="get">
<table border="0">
<tr>
  <td>Column: </td>
  <td>Row: </td>
  <td>Condition: </td>
  <td align="center">
  <a href="http://autotest.kernel.org/wiki/AutotestTKOCondition">Help</a>
  </td>
</tr>
<tr>
  <td>
  <SELECT NAME="columns">
  %s
 </SELECT>
  </td>
  <td>
  <SELECT NAME="rows">
  %s
  </SELECT>
  </td>
  <td>
    <input type="text" name="condition" size="30" value="%s">
    <input type="hidden" name="title" value="%s">
  </td>
  <td align="center"><input type="submit" value="Submit">
  </td>
</tr>
</table>
</form>
<form action="/tko/save_query.cgi" method="get">
<table border="0">
<tr>
 <td>Name your query:&nbsp;&nbsp;</td>
  <td>
    <input type="text" name="label" size="15" value="">
  </td>
  <td align="center">&nbsp;<input type="submit" value="Save Query">
  </td>
 <td>&nbsp;&nbsp;<a href="/tko/query_history.cgi">View saved queries</a></td>
  <td>
    <input type="hidden" name="columns" value="%s">
    <input type="hidden" name="rows" value="%s">
    <input type="hidden" name="condition" value="%s">
  </td>
</tr>
</table>
</form>
"""


next_field = {
    'machine_group': 'hostname',
    'hostname': 'tag',
    'tag': 'tag',

    'kernel': 'test',
    'test': 'label',
    'label': 'tag',

    'reason': 'tag',
    'user': 'tag',
    'status': 'tag',
   
    'time': 'tag',
    'time_daily': 'time',
}


def parse_field(form, form_field, field_default):
    if not form_field in form:
        return field_default
    field_input = form[form_field].value.lower()
    if field_input and field_input in frontend.test_view_field_dict:
        return field_input
    return field_default


def parse_condition(form, form_field, field_default):
    if not form_field in form:
        return field_default
    return form[form_field].value


form = cgi.FieldStorage()

title_field = parse_condition(form, 'title', '')
row = parse_field(form, 'rows', 'kernel')
column = parse_field(form, 'columns', 'machine_group')
condition_field = parse_condition(form, 'condition', '')

if 'brief' in form.keys() and form['brief'].value <> '0':
    display.set_brief_mode()

## caller can specify rows and columns that shall be included into the report
## regardless of whether actual test data is available yet
force_row_field = parse_condition(form,'force_row','')
force_column_field = parse_condition(form,'force_column','')


def split_forced_fields(force_field):
    if force_field:
        return force_field.split()
    else:
        return []

force_row =  split_forced_fields(force_row_field)
force_column =  split_forced_fields(force_column_field)
  
cgitb.enable()
db_obj = db.db()


def construct_link(x, y):
    next_row = row
    next_column = column
    condition_list = []
    if condition_field != '':
        condition_list.append(condition_field)
    if y:
        next_row = next_field[row]
        condition_list.append("%s='%s'" % (row, y))
    if x:
        next_column = next_field[column]
        condition_list.append("%s='%s'" % (column, x))
    next_condition = '&'.join(condition_list)
    link = '/tko/compose_query.cgi?' + urllib.urlencode({'columns': next_column,
               'rows': next_row, 'condition': next_condition,
               'title': title_field})
    return link


def construct_logs_link(x, y, job_tag):
    job_path = frontend.html_root + job_tag + '/'
    test = ''
    if (row == 'test' and
        not y.split('.')[0] in ('boot', 'build', 'install')):
        test = y
    if (column == 'test' and
        not x.split('.')[0] in ('boot', 'build', 'install')):
        test = x
    return '/tko/retrieve_logs.cgi?' + urllib.urlencode({'job' : job_path,
         'test' : test})


def create_select_options(selected_val):
    ret = ""
    for option in sorted(frontend.test_view_field_dict.keys()):
        if selected_val == option:
            selected = " SELECTED"
        else:
            selected = ""

        ret += '<OPTION VALUE="%s"%s>%s</OPTION>\n' % \
                        (option, selected, option)
    return ret


def map_kernel_base(kernel_name):
    ## insert <br> after each / in kernel name
    ## but spare consequtive //
    kernel_name = kernel_name.replace('/','/<br>')
    kernel_name = kernel_name.replace('/<br>/<br>','//')
    return kernel_name


def header_tuneup(field_name, header):
        ## header tune up depends on particular field name and may include:
        ## - breaking header into several strings if it is long url
        ## - creating date from datetime stamp
        ## - possibly, expect more various refinements for different fields
        if field_name == 'kernel':
                return  map_kernel_base(header)
        else:
                return header


# Kernel name mappings -- the kernels table 'printable' field is
# effectively a sortable identifier for the kernel It encodes the base
# release which is used for overall sorting, plus where patches are
# applied it adds an increasing pNNN patch combination identifier
# (actually the kernel_idx for the entry).  This allows sorting
# as normal by the base kernel version and then sub-sorting by the
# "first time we saw" a patch combination which should keep them in
# approximatly date order.  This patch identifier is not suitable
# for display, so we have to map it to a suitable html fragment for
# display.  This contains the kernel base version plus the truncated
# names of all the patches,
#
#     2.6.24-mm1 p112
#     +add-new-string-functions-
#     +x86-amd-thermal-interrupt
# 
# This mapping is produced when the first mapping is request, with
# a single query over the patches table; the result is then cached.
#
# Note: that we only count a base version as patched if it contains
# patches which are not already "expressed" in the base version.
# This includes both -gitN and -mmN kernels.
map_kernel_map = None


def map_kernel_init():
    fields = ['base', 'k.kernel_idx', 'name', 'url']
    map = {}
    for (base, idx, name, url) in db_obj.select(','.join(fields),
            'tko_kernels k, tko_patches p', 'k.kernel_idx=p.kernel_idx'):
        match = re.match(r'.*(-mm[0-9]+|-git[0-9]+)\.(bz2|gz)$', url)
        if match:
            continue

        key = base + ' p%d' % (idx)
        if not map.has_key(key):
            map[key] = map_kernel_base(base) + ' p%d' % (idx)
        map[key] += ('<br>+<span title="' + name + '">' +
                 name[0:25] + '</span>')

    return map


def map_kernel(name):
    global map_kernel_map
    if map_kernel_map is None:
        map_kernel_map = map_kernel_init()

    if map_kernel_map.has_key(name):
        return map_kernel_map[name]

    return map_kernel_base(name.split(' ')[0])


field_map = {
    'kernel':map_kernel
}

sql_wall_time = 0

def gen_matrix():
    where = None
    if condition_field.strip() != '':
        try:
            where = query_lib.parse_scrub_and_gen_condition(
                condition_field, frontend.test_view_field_dict)
            print "<!-- where clause: %s -->" % (where,)
        except:
            msg = "Unspecified error when parsing condition"
            return [[display.box(msg)]]

    wall_time_start = time.time()
    try:
        ## Unfortunately, we can not request reasons of failure always
        ## because it may result in an inflated size of data transfer
        ## (at the moment we fetch 500 bytes of reason descriptions into
        ## each cell )
        ## If 'status' in [row,column] then either width or height
        ## of the table <=7, hence table is not really 2D, and
        ## query_reason is relatively save.
        ## At the same time view when either rows or columns grouped
        ## by status is when users need reasons of failures the most.
        
        ## TO DO: implement [Show/Hide reasons] button or link in
        ## all views and make thorough performance testing 
        test_data = frontend.get_matrix_data(db_obj, column, row, where,
                query_reasons = ('status' in [row,column])
                )
        global sql_wall_time
        sql_wall_time = time.time() - wall_time_start

    except db.MySQLTooManyRows, error:
        return [[display.box(str(error))]]            
    
    for f_row in force_row:
        if not f_row in test_data.y_values:
            test_data.y_values.append(f_row)
    for f_column in force_column:
        if not f_column in test_data.x_values:
            test_data.x_values.append(f_column)

    if not test_data.y_values:
        msg = "There are no results for this query (yet?)."
        return [[display.box(msg)]]

    dict_url = {'columns': row,
               'rows': column, 'condition': condition_field,
               'title': title_field}
    link = '/tko/compose_query.cgi?' + urllib.urlencode(dict_url)
    header_row = [display.box("<center>(Flip Axis)</center>", link=link)]

    for x in test_data.x_values:
        dx = x
        if field_map.has_key(column):
            dx = field_map[column](x)
        x_header = header_tuneup(column, dx)
        link = construct_link(x, None)
        header_row.append(display.box(x_header,header=True,link=link))

    matrix = [header_row]
    # For each row, we are looping horizontally over the columns.
    for y in test_data.y_values:
        dy = y
        if field_map.has_key(row):
            dy = field_map[row](y)
        y_header = header_tuneup(row, dy)
        link = construct_link(None, y)
        cur_row = [display.box(y_header, header=True, link=link)]
        for x in test_data.x_values:
            ## next 2 lines: temporary, until non timestamped
            ## records are in the database
            if x==datetime.datetime(1970,1,1): x = None
            if y==datetime.datetime(1970,1,1): y = None
            try:
                box_data = test_data.data[x][y]
            except:
                cur_row.append(display.box(None, None, 
                           row_label=y, column_label=x))
                continue
            job_tag = test_data.data[x][y].job_tag
            if job_tag:
                link = construct_logs_link(x, y, job_tag)
            else:
                link = construct_link(x, y)

            apnd = display.status_precounted_box(db_obj, box_data,
                                 link, y, x)
            cur_row.append(apnd)
        matrix.append(cur_row)
    return matrix


def main():
    if display.is_brief_mode():
        ## create main grid table only as provided by gen_matrix()
        display.print_table(gen_matrix())
    else:
        # create the actual page 
        print '<html><head><title>'
        print 'Filtered Autotest Results'
        print '</title></head><body>'
        display.print_main_header()
        print html_header % (create_select_options(column),
                     create_select_options(row),
                     condition_field, title_field,
                     ## history form
                     column,row,condition_field)
        if title_field:
            print '<h1> %s </h1>' % (title_field)
        print display.color_keys_row()
        display.print_table(gen_matrix())
        print display.color_keys_row()
        total_wall_time = time.time() - total_wall_time_start
        
        perf_info = '<p style="font-size:x-small;">'
        perf_info += 'sql access wall time = %s secs,' % sql_wall_time
        perf_info += 'total wall time = %s secs</p>' % total_wall_time
        print perf_info
        print '</body></html>'


main()