import logging
import os

from autotest.client.shared import utils
from autotest.tko import utils as tko_utils


class job(object):

    def __init__(self, dir, user, label, machine, queued_time, started_time,
                 finished_time, machine_owner, machine_group, aborted_by,
                 aborted_on, keyval_dict):
        self.dir = dir
        self.tests = []
        self.user = user
        self.label = label
        self.machine = machine
        self.queued_time = queued_time
        self.started_time = started_time
        self.finished_time = finished_time
        self.machine_owner = machine_owner
        self.machine_group = machine_group
        self.aborted_by = aborted_by
        self.aborted_on = aborted_on
        self.keyval_dict = keyval_dict

    @staticmethod
    def read_keyval(dir):
        dir = os.path.normpath(dir)
        top_dir = tko_utils.find_toplevel_job_dir(dir)
        if not top_dir:
            top_dir = dir
        assert(dir.startswith(top_dir))

        # pull in and merge all the keyval files, with higher-level
        # overriding values in the lower-level ones
        keyval = {}
        while True:
            try:
                upper_keyval = utils.read_keyval(dir)
                # HACK: exclude hostname from the override - this is a special
                # case where we want lower to override higher
                if "hostname" in upper_keyval and "hostname" in keyval:
                    del upper_keyval["hostname"]
                keyval.update(upper_keyval)
            except IOError:
                pass  # if the keyval can't be read just move on to the next
            if dir == top_dir:
                break
            else:
                assert(dir != "/")
                dir = os.path.dirname(dir)
        return keyval


class kernel(object):

    def __init__(self, base, patches, kernel_hash):
        self.base = base
        self.patches = patches
        self.kernel_hash = kernel_hash

    @staticmethod
    def compute_hash(base, hashes):
        key_string = ','.join([base] + hashes)
        return utils.hash('md5', key_string).hexdigest()


class test(object):

    def __init__(self, subdir, testname, status, reason, test_kernel,
                 machine, started_time, finished_time, iterations,
                 attributes, labels):
        self.subdir = subdir
        self.testname = testname
        self.status = status
        self.reason = reason
        self.kernel = test_kernel
        self.machine = machine
        self.started_time = started_time
        self.finished_time = finished_time
        self.iterations = iterations
        self.attributes = attributes
        self.labels = labels

    @staticmethod
    def load_iterations(keyval_path):
        """Abstract method to load a list of iterations from a keyval
        file."""
        raise NotImplementedError

    @classmethod
    def parse_test(cls, job, subdir, testname, status, reason, test_kernel,
                   started_time, finished_time, existing_instance=None):
        """Given a job and the basic metadata about the test that
        can be extracted from the status logs, parse the test
        keyval files and use it to construct a complete test
        instance."""
        logging.info("parsing test %s %s" % (subdir, testname))

        if subdir:
            # grab iterations from the results keyval
            iteration_keyval = os.path.join(job.dir, subdir,
                                            "results", "keyval")
            iterations = cls.load_iterations(iteration_keyval)

            # grab test attributes from the subdir keyval
            test_keyval = os.path.join(job.dir, subdir, "keyval")
            attributes = test.load_attributes(test_keyval)
        else:
            iterations = []
            attributes = {}

        # grab test+host attributes from the host keyval
        host_keyval = cls.parse_host_keyval(job.dir, job.machine)
        attributes.update(dict(("host-%s" % k, v)
                               for k, v in host_keyval.iteritems()))

        if existing_instance:
            def constructor(*args, **dargs):
                existing_instance.__init__(*args, **dargs)
                return existing_instance
        else:
            constructor = cls
        return constructor(subdir, testname, status, reason, test_kernel,
                           job.machine, started_time, finished_time,
                           iterations, attributes, [])

    @classmethod
    def parse_partial_test(cls, job, subdir, testname, reason, test_kernel,
                           started_time):
        """Given a job and the basic metadata available when a test is
        started, create a test instance representing the partial result.
        Assume that since the test is not complete there are no results files
        actually available for parsing."""
        logging.info("parsing partial test %s %s", subdir, testname)

        return cls(subdir, testname, "RUNNING", reason, test_kernel,
                   job.machine, started_time, None, [], {}, [])

    @staticmethod
    def load_attributes(keyval_path):
        """Load the test attributes into a dictionary from a test
        keyval path. Does not assume that the path actually exists."""
        if not os.path.exists(keyval_path):
            return {}
        return utils.read_keyval(keyval_path)

    @staticmethod
    def parse_host_keyval(job_dir, hostname):
        # the "real" job dir may be higher up in the directory tree
        job_dir = tko_utils.find_toplevel_job_dir(job_dir)
        if not job_dir:
            return {}  # we can't find a top-level job dir with host keyvals

        # the keyval is <job_dir>/host_keyvals/<hostname> if it exists
        keyval_path = os.path.join(job_dir, "host_keyvals", hostname)
        if os.path.isfile(keyval_path):
            return utils.read_keyval(keyval_path)
        else:
            return {}


class patch(object):

    def __init__(self, spec, reference, hash):
        self.spec = spec
        self.reference = reference
        self.hash = hash


class iteration(object):

    def __init__(self, index, attr_keyval, perf_keyval):
        self.index = index
        self.attr_keyval = attr_keyval
        self.perf_keyval = perf_keyval

    @staticmethod
    def parse_line_into_dicts(line, attr_dict, perf_dict):
        """Abstract method to parse a keyval line and insert it into
        the appropriate dictionary.
                attr_dict: generic iteration attributes
                perf_dict: iteration performance results
        """
        raise NotImplementedError

    @classmethod
    def load_from_keyval(cls, keyval_path):
        """Load a list of iterations from an iteration keyval file.
        Keyval data from separate iterations is separated by blank
        lines. Makes use of the parse_line_into_dicts method to
        actually parse the individual lines."""
        if not os.path.exists(keyval_path):
            return []

        iterations = []
        index = 1
        attr, perf = {}, {}
        for line in file(keyval_path):
            line = line.strip()
            if line:
                cls.parse_line_into_dicts(line, attr, perf)
            else:
                iterations.append(cls(index, attr, perf))
                index += 1
                attr, perf = {}, {}
        if attr or perf:
            iterations.append(cls(index, attr, perf))
        return iterations
