###################################################################################
# LAVA QA tool - Class to fetch job output logs
# Copyright (C) 2017 Collabora Ltd.

# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.

# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.

# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  US
###################################################################################

import sys
import time
try:
    from xmlrpc.client import Fault
except ImportError:
    from xmlrpclib import Fault

from lqa_api.job import Job

class OutputLogError(Exception):
    """Base exception for Output Logs errors"""
    def __init__(self, msg, code):
        self.msg = msg
        self.code = code

    def __str__(self):
        return "{} {}".format(self.msg, self.code)

class OutputLog(object):

    def __init__(self, job_id, server, is_live=False, outfile=None, logger=None):
        self._job_id = job_id
        self._server = server
        self._is_live = is_live
        self._outfile = outfile
        self._logger = logger

    def run(self):
        job = Job(self._job_id, self._server)
        if self._is_live:
            # If is_live, read incrementing offset of the output to show live.
            # Wait for the job to start if it is in a submitted state.
            if job.is_submitted():
                if self._logger:
                    self._logger.info("Waiting for job to start: {}".format(job))
                while job.is_submitted():
                    time.sleep(1)

            offset = 0
            while job.is_running():
                offset = self._fetch_and_write_output(offset, job)
                time.sleep(0.5)
            # Run one more time to catch any left data from the time.sleep delay.
            # This call also helps to fetch the output for non-running states.
            self._fetch_and_write_output(offset, job)
        else:
            # Otherwise just fetch and show the output file at once.
            try:
                output_data = job.output()
                if self._outfile:
                    with open(self._outfile, 'wb+') as f:
                        f.write(output_data)
                else:
                    # Print to stdout
                    sys.stdout.flush()
                    sys.stdout.buffer.write(output_data)
            except Fault as e:
                # 404 Job output not found.
                if e.faultCode == 404:
                    if self._logger:
                        self._logger.error("{}: Status for job '{}': {}" \
                                           .format(e.faultString, job, job.status))
                elif self._logger:
                    self._logger.error("output: error: {}".format(e))
                raise OutputLogError(e.faultString, e.faultCode)

    def _fetch_and_write_output(self, offset, job):
        try:
            output_data = job.output(offset)
            if output_data:
                sys.stdout.flush()
                sys.stdout.buffer.write(output_data)
                offset = offset + len(output_data)
        except Fault as e:
            if e.faultCode == 404:
                # Skip this error code only during the short time when the job
                # started running but the log is not yet available.
                if job.is_running():
                    return offset
                if self._logger:
                    self._logger.error("{}: Status for job '{}': {}" \
                                       .format(e.faultString, job, job.status))
            else:
                if self._logger:
                    self._logger.error("output: error: {}".format(e))
            raise OutputLogError(e.faultString, e.faultCode)
        # Everytihng went fine, just return offset
        return offset
