import os, re, db, sys, datetime
import common
from autotest_lib.client.common_lib import kernel_versions

MAX_RECORDS = 50000L
MAX_CELLS = 500000L

tko = os.path.dirname(os.path.realpath(os.path.abspath(__file__)))
root_url_file = os.path.join(tko, '.root_url')
if os.path.exists(root_url_file):
    html_root = open(root_url_file, 'r').readline().rstrip()
else:
    html_root = '/results/'


class status_cell:
    # One cell in the matrix of status data.
    def __init__(self):
        # Count is a dictionary: status -> count of tests with status
        self.status_count = {}
        self.reasons_list = []
        self.job_tag = None
        self.job_tag_count = 0


    def add(self, status, count, job_tags, reasons = None):
        assert count > 0

        self.job_tag = job_tags
        self.job_tag_count += count
        if self.job_tag_count > 1:
            self.job_tag = None

        self.status_count[status] = count
        ### status == 6 means 'GOOD'
        if status != 6:
            ## None implies sorting problems and extra CRs in a cell
            if reasons:
                self.reasons_list.append(reasons)


class status_data:
    def __init__(self, sql_rows, x_field, y_field, query_reasons = False):
        data = {}
        y_values = set()

        # Walk through the query, filing all results by x, y info
        for row in sql_rows:
            if query_reasons:
                (x,y, status, count, job_tags, reasons) = row
            else:
                (x,y, status, count, job_tags) = row
                reasons = None
            if not data.has_key(x):
                data[x] = {}
            if not data[x].has_key(y):
                y_values.add(y)
                data[x][y] = status_cell()
            data[x][y].add(status, count, job_tags, reasons)

        # 2-d hash of data - [x-value][y-value]
        self.data = data
        # List of possible columns (x-values)
        self.x_values = smart_sort(data.keys(), x_field)
        # List of rows columns (y-values)
        self.y_values = smart_sort(list(y_values), y_field)
        nCells = len(self.y_values)*len(self.x_values)
        if nCells > MAX_CELLS:
            msg = 'Exceeded allowed number of cells in a table'
            raise db.MySQLTooManyRows(msg)


def get_matrix_data(db_obj, x_axis, y_axis, where = None,
                    query_reasons = False):
    # Searches on the test_view table - x_axis and y_axis must both be
    # column names in that table.
    x_field = test_view_field_dict[x_axis]
    y_field = test_view_field_dict[y_axis]
    query_fields_list = [x_field, y_field, 'status','COUNT(status)']
    query_fields_list.append("LEFT(GROUP_CONCAT(job_tag),100)")
    if query_reasons:
        query_fields_list.append(
                "LEFT(GROUP_CONCAT(DISTINCT reason SEPARATOR '|'),500)"
                )
    fields = ','.join(query_fields_list)

    group_by = '%s, %s, status' % (x_field, y_field)
    rows = db_obj.select(fields, 'tko_test_view',
                    where=where, group_by=group_by, max_rows = MAX_RECORDS)
    return status_data(rows, x_field, y_field, query_reasons)


# Dictionary used simply for fast lookups from short reference names for users
# to fieldnames in test_view
test_view_field_dict = {
        'kernel'        : 'kernel_printable',
        'hostname'      : 'machine_hostname',
        'test'          : 'test',
        'label'         : 'job_label',
        'machine_group' : 'machine_group',
        'reason'        : 'reason',
        'tag'           : 'job_tag',
        'user'          : 'job_username',
        'status'        : 'status_word',
        'time'          : 'test_finished_time',
        'start_time'    : 'test_started_time',
        'time_daily'    : 'DATE(test_finished_time)'
}


def smart_sort(list, field):
    if field == 'kernel_printable':
        def kernel_encode(kernel):
            return kernel_versions.version_encode(kernel)
        list.sort(key = kernel_encode, reverse = True)
        return list
    ## old records may contain time=None
    ## make None comparable with timestamp datetime or date
    elif field == 'test_finished_time':
        def convert_None_to_datetime(date_time):
            if not date_time:
                return datetime.datetime(1970, 1, 1, 0, 0, 0)
            else:
                return date_time
        list = map(convert_None_to_datetime, list)
    elif field == 'DATE(test_finished_time)':
        def convert_None_to_date(date):
            if not date:
                return datetime.date(1970, 1, 1)
            else:
                return date
        list = map(convert_None_to_date, list)
    list.sort()
    return list


class group:
    @classmethod
    def select(klass, db):
        """Return all possible machine groups"""
        rows = db.select('distinct machine_group', 'tko_machines',
                                        'machine_group is not null')
        groupnames = sorted([row[0] for row in rows])
        return [klass(db, groupname) for groupname in groupnames]


    def __init__(self, db, name):
        self.name = name
        self.db = db


    def machines(self):
        return machine.select(self.db, { 'machine_group' : self.name })


    def tests(self, where = {}):
        values = [self.name]
        sql = 't inner join tko_machines m on m.machine_idx=t.machine_idx'
        sql += ' where m.machine_group=%s'
        for key in where.keys():
            sql += ' and %s=%%s' % key
            values.append(where[key])
        return test.select_sql(self.db, sql, values)


class machine:
    @classmethod
    def select(klass, db, where = {}):
        fields = ['machine_idx', 'hostname', 'machine_group', 'owner']
        machines = []
        for row in db.select(','.join(fields), 'tko_machines', where):
            machines.append(klass(db, *row))
        return machines


    def __init__(self, db, idx, hostname, group, owner):
        self.db = db
        self.idx = idx
        self.hostname = hostname
        self.group = group
        self.owner = owner


class kernel:
    @classmethod
    def select(klass, db, where = {}):
        fields = ['kernel_idx', 'kernel_hash', 'base', 'printable']
        rows = db.select(','.join(fields), 'tko_kernels', where)
        return [klass(db, *row) for row in rows]


    def __init__(self, db, idx, hash, base, printable):
        self.db = db
        self.idx = idx
        self.hash = hash
        self.base = base
        self.printable = printable
        self.patches = []    # THIS SHOULD PULL IN PATCHES!


class test:
    @classmethod
    def select(klass, db, where={}, distinct=False):
        fields = ['test_idx', 'job_idx', 'test', 'subdir',
                  'kernel_idx', 'status', 'reason', 'machine_idx']
        tests = []
        for row in db.select(','.join(fields), 'tko_tests', where,
                             distinct):
            tests.append(klass(db, *row))
        return tests


    @classmethod
    def select_sql(klass, db, sql, values):
        fields = ['test_idx', 'job_idx', 'test', 'subdir',
                  'kernel_idx', 'status', 'reason', 'machine_idx']
        fields = ['t.'+field for field in fields]
        rows = db.select_sql(','.join(fields), 'tko_tests', sql, values)
        return [klass(db, *row) for row in rows]


    def __init__(self, db, test_idx, job_idx, testname, subdir, kernel_idx,
                 status_num, reason, machine_idx):
        self.idx = test_idx
        self.job = job(db, job_idx)
        self.testname = testname
        self.subdir = subdir
        self.kernel_idx = kernel_idx
        self.__kernel = None
        self.__iterations = None
        self.machine_idx = machine_idx
        self.__machine = None
        self.status_num = status_num
        self.status_word = db.status_word[status_num]
        self.reason = reason
        self.db = db
        if self.subdir:
            self.url = html_root + self.job.tag + '/' + self.subdir
        else:
            self.url = None


    def iterations(self):
        """
        Caching function for iterations
        """
        if not self.__iterations:
            self.__iterations = {}
            # A dictionary - dict{key} = [value1, value2, ....]
            where = {'test_idx' : self.idx}
            for i in iteration.select(self.db, where):
                if self.__iterations.has_key(i.key):
                    self.__iterations[i.key].append(i.value)
                else:
                    self.__iterations[i.key] = [i.value]
        return self.__iterations


    def kernel(self):
        """
        Caching function for kernels
        """
        if not self.__kernel:
            where = {'kernel_idx' : self.kernel_idx}
            self.__kernel = kernel.select(self.db, where)[0]
        return self.__kernel


    def machine(self):
        """
        Caching function for kernels
        """
        if not self.__machine:
            where = {'machine_idx' : self.machine_idx}
            self.__machine = machine.select(self.db, where)[0]
        return self.__machine


class job:
    def __init__(self, db, job_idx):
        where = {'job_idx' : job_idx}
        rows = db.select('tag, machine_idx', 'tko_jobs', where)
        if rows:
            self.tag, self.machine_idx = rows[0]
            self.job_idx = job_idx


class iteration:
    @classmethod
    def select(klass, db, where):
        fields = ['iteration', 'attribute', 'value']
        iterations = []
        rows = db.select(','.join(fields), 'tko_iteration_result', where)
        for row in rows:
            iterations.append(klass(*row))
        return iterations


    def __init__(self, iteration, key, value):
        self.iteration = iteration
        self.key = key
        self.value = value

# class patch:
#       def __init__(self):
#               self.spec = None