"""
    This file is part of yacron.

    Copyright (C) 2016 Vadim Kuznetsov <vimusov@gmail.com>

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

    yacron 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 General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with yacron.  If not, see <http://www.gnu.org/licenses/>.
"""


class CronTime(object):
    """
    Parse and store scheduled time.
    """
    def __init__(self, minutes, hours, weekdays):
        """
        Parse and store the minutes, hours and weekdays values.
        :param minutes: Minutes (str)
        :param hours: Hours (str)
        :param weekdays: Weekdays (str)
        :raise ValueError if any of the values is invalid
        """
        self._minutes = self._parse_value(0, minutes, 59)
        self._hours = self._parse_value(0, hours, 23)
        # slashes are unacceptable in weekdays value
        self._weekdays = self._parse_value(1, weekdays, 7, slash_acceptable=False)

    @property
    def minutes(self):
        return self._minutes

    @property
    def hours(self):
        return self._hours

    @property
    def weekdays(self):
        return self._weekdays

    def _check_value_range(self, min_value, value, max_value):
        """
        Check is value in range.
        :param min_value: Minimal valid value
        :param value: Value
        :param max_value: Maximum valid value
        :return True if the value is in range
        :raise ValueError if the value is out of range
        """
        if not (min_value <= value <= max_value):
            raise ValueError("invalid value '{0:d}', must be in [{1:d}..{2:d}]".format(value, min_value, max_value))
        return True

    def _check_special_chars(self, value):
        """
        Check special characters in the value:
            1) value can not contains more than one '*' or '/' or '-' characters;
            2) special characters can not be mixed (there can be the only one except ',');
        :param value: Value.
        :raise ValueError if any invalid sequence of special characters found in the value.
        """
        all_count = value.count('*')
        slash_count = value.count('/')
        comma_count = value.count(',')
        hyphen_count = value.count('-')

        is_invalid = any((
            all_count > 1,
            slash_count > 1,
            hyphen_count > 1,
            all_count and (slash_count or comma_count or hyphen_count),
            slash_count and (all_count or comma_count or hyphen_count),
            comma_count and (all_count or slash_count or hyphen_count),
            hyphen_count and (all_count or slash_count or comma_count),
        ))

        if is_invalid:
            raise ValueError("invalid format in value '{0:s}'".format(value))

    def _parse_value(self, min_value, value, max_value, slash_acceptable=True):
        """
        Parse and check a value.
        :param min_value: Minimal valid value
        :param value: Value
        :param max_value: Maximum valid value
        :param slash_acceptable: Slash is valid in the value
        :return: List of values.
        :raise ValueError if parsing failed
        """
        self._check_special_chars(value)

        if value == '*':
            return list(range(min_value, max_value + 1))

        if value.startswith('/'):
            if not slash_acceptable:
                raise ValueError("value '{0:s}' can not contains slash".format(value))

            divisor = int(value[1:])
            self._check_value_range(min_value, divisor, max_value)

            return [n for n in range(min_value, max_value + 1) if n % divisor == 0]

        if '-' in value:
            start_value, stop_value = map(int, value.split('-'))

            self._check_value_range(min_value, start_value, max_value)
            self._check_value_range(min_value, stop_value, max_value)

            if start_value >= stop_value:
                raise ValueError("start value can not be greater or equal to stop value")

            return list(range(start_value, stop_value + 1))

        if ',' in value:
            return [n for n in map(int, value.split(',')) if self._check_value_range(min_value, n, max_value)]

        return [int(value)]

    def check_time(self, cur_time):
        """
        Compare parsed time and current time.
        :param cur_time: Current time (datetime).
        :return: True if current time matches with parser time and False otherwise
        """
        return all((
            cur_time.minute in self._minutes,
            cur_time.hour in self._hours,
            cur_time.isoweekday() in self._weekdays,
        ))
