# Copyright 2014 Huawei Technologies Co. Ltd
#
# 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.

"""Module to get the progress when found match with a line of the log."""
import logging
import re

from abc import ABCMeta

from compass.utils import util


class ProgressCalculator(object):
    """base class to generate progress."""

    __metaclass__ = ABCMeta

    @classmethod
    def update_progress(
        cls, progress_data, message,
        severity, log_history
    ):
        """Update progress with the given progress_data, message and severity.

        :param progress_data: installing progress.
        :type progress_data: float between 0 to 1.
        :param message: installing progress message.
        :param severity: installing message severity.
        :param progress: :class:`Progress` instance to update
        """
        # the progress is only updated when the new progress
        # is greater than the stored progress or the progress
        # to update is the same but the message is different.
        if (
            progress_data > log_history['percentage'] or (
                progress_data == log_history['percentage'] and
                message != log_history['message']
            )
        ):
            log_history['percentage'] = progress_data
            if message:
                log_history['message'] = message
            if severity:
                log_history['severity'] = severity
            logging.debug('update progress to %s', log_history)
        else:
            logging.info('ignore update progress %s to %s',
                         progress_data, log_history)

    def update(self, message, severity, log_history):
        """vritual method to update progress by message and severity.

        :param message: installing message.
        :param severity: installing severity.
        """
        raise NotImplementedError(str(self))

    def __repr__(self):
        return self.__class__.__name__


class IncrementalProgress(ProgressCalculator):
    """Class to increment the progress."""

    def __init__(self, min_progress,
                 max_progress, incremental_ratio):
        super(IncrementalProgress, self).__init__()
        if not 0.0 <= min_progress <= max_progress <= 1.0:
            raise IndexError(
                '%s restriction is not mat: 0.0 <= min_progress(%s)'
                ' <= max_progress(%s) <= 1.0' % (
                    self.__class__.__name__, min_progress, max_progress))

        if not 0.0 <= incremental_ratio <= 1.0:
            raise IndexError(
                '%s restriction is not mat: '
                '0.0 <= incremental_ratio(%s) <=  1.0' % (
                    self.__class__.__name__, incremental_ratio))

        self.min_progress_ = min_progress
        self.max_progress_ = max_progress
        self.incremental_progress_ = (
            incremental_ratio * (max_progress - min_progress))

    def __str__(self):
        return '%s[%s:%s:%s]' % (
            self.__class__.__name__,
            self.min_progress_,
            self.max_progress_,
            self.incremental_progress_
        )

    def update(self, message, severity, log_history):
        """update progress from message and severity."""
        progress_data = max(
            self.min_progress_,
            min(
                self.max_progress_,
                log_history['percentage'] + self.incremental_progress_
            )
        )
        self.update_progress(progress_data,
                             message, severity, log_history)


class RelativeProgress(ProgressCalculator):
    """class to update progress to the given relative progress."""

    def __init__(self, progress):
        super(RelativeProgress, self).__init__()
        if not 0.0 <= progress <= 1.0:
            raise IndexError(
                '%s restriction is not mat: 0.0 <= progress(%s) <= 1.0' % (
                    self.__class__.__name__, progress))

        self.progress_ = progress

    def __str__(self):
        return '%s[%s]' % (self.__class__.__name__, self.progress_)

    def update(self, message, severity, log_history):
        """update progress from message and severity."""
        self.update_progress(
            self.progress_, message, severity, log_history)


class SameProgress(ProgressCalculator):
    """class to update message and severity for  progress."""

    def update(self, message, severity, log_history):
        """update progress from the message and severity."""
        self.update_progress(log_history['percentage'], message,
                             severity, log_history)


class LineMatcher(object):
    """Progress matcher for each line."""

    def __init__(self, pattern, progress=None,
                 message_template='', severity=None,
                 unmatch_sameline_next_matcher_name='',
                 unmatch_nextline_next_matcher_name='',
                 match_sameline_next_matcher_name='',
                 match_nextline_next_matcher_name=''):
        self.regex_ = re.compile(pattern)
        if not progress:
            self.progress_ = SameProgress()
        elif isinstance(progress, ProgressCalculator):
            self.progress_ = progress
        elif util.is_instance(progress, [int, float]):
            self.progress_ = RelativeProgress(progress)
        else:
            raise TypeError(
                'progress unsupport type %s: %s' % (
                    type(progress), progress))

        self.message_template_ = message_template
        self.severity_ = severity
        self.unmatch_sameline_ = unmatch_sameline_next_matcher_name
        self.unmatch_nextline_ = unmatch_nextline_next_matcher_name
        self.match_sameline_ = match_sameline_next_matcher_name
        self.match_nextline_ = match_nextline_next_matcher_name

    def __str__(self):
        return '%s[pattern:%r, message_template:%r, severity:%r]' % (
            self.__class__.__name__, self.regex_.pattern,
            self.message_template_, self.severity_)

    def update_progress(self, line, log_history):
        """Update progress by the line.

        :param line: one line in log file to indicate the installing progress.
           .. note::
              The line may be partial if the latest line of the log file is
              not the whole line. But the whole line may be resent
              in the next run.
        :param progress: the :class:`Progress` instance to update.
        """
        mat = self.regex_.search(line)
        if not mat:
            return (
                self.unmatch_sameline_,
                self.unmatch_nextline_)

        try:
            message = self.message_template_ % mat.groupdict()
        except Exception as error:
            logging.error('failed to get message %s %% %s in line matcher %s',
                          self.message_template_, mat.groupdict(), self)
            raise error

        self.progress_.update(message, self.severity_, log_history)
        return (
            self.match_sameline_,
            self.match_nextline_)