#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Episode title
"""
from collections import defaultdict

from rebulk import Rebulk, Rule, AppendMatch, RemoveMatch, RenameMatch, POST_PROCESS

from ..common import seps, title_seps
from ..common.formatters import cleanup
from ..common.pattern import is_disabled
from ..common.validators import or_
from ..properties.title import TitleFromPosition, TitleBaseRule
from ..properties.type import TypeProcessor


def episode_title(config):  # pylint:disable=unused-argument
    """
    Builder for rebulk object.

    :param config: rule configuration
    :type config: dict
    :return: Created Rebulk object
    :rtype: Rebulk
    """
    previous_names = ('episode', 'episode_count',
                      'season', 'season_count', 'date', 'title', 'year')

    rebulk = Rebulk(disabled=lambda context: is_disabled(context, 'episode_title'))
    rebulk = rebulk.rules(RemoveConflictsWithEpisodeTitle(previous_names),
                          EpisodeTitleFromPosition(previous_names),
                          AlternativeTitleReplace(previous_names),
                          TitleToEpisodeTitle,
                          Filepart3EpisodeTitle,
                          Filepart2EpisodeTitle,
                          RenameEpisodeTitleWhenMovieType)
    return rebulk


class RemoveConflictsWithEpisodeTitle(Rule):
    """
    Remove conflicting matches that might lead to wrong episode_title parsing.
    """

    priority = 64
    consequence = RemoveMatch

    def __init__(self, previous_names):
        super().__init__()
        self.previous_names = previous_names
        self.next_names = ('streaming_service', 'screen_size', 'source',
                           'video_codec', 'audio_codec', 'other', 'container')
        self.affected_if_holes_after = ('part', )
        self.affected_names = ('part', 'year')

    def when(self, matches, context):
        to_remove = []
        for filepart in matches.markers.named('path'):
            for match in matches.range(filepart.start, filepart.end,
                                       predicate=lambda m: m.name in self.affected_names):
                before = matches.range(filepart.start, match.start, predicate=lambda m: not m.private, index=-1)
                if not before or before.name not in self.previous_names:
                    continue

                after = matches.range(match.end, filepart.end, predicate=lambda m: not m.private, index=0)
                if not after or after.name not in self.next_names:
                    continue

                group = matches.markers.at_match(match, predicate=lambda m: m.name == 'group', index=0)

                def has_value_in_same_group(current_match, current_group=group):
                    """Return true if current match has value and belongs to the current group."""
                    return current_match.value.strip(seps) and (
                        current_group == matches.markers.at_match(current_match,
                                                                  predicate=lambda mm: mm.name == 'group', index=0)
                    )

                holes_before = matches.holes(before.end, match.start, predicate=has_value_in_same_group)
                holes_after = matches.holes(match.end, after.start, predicate=has_value_in_same_group)

                if not holes_before and not holes_after:
                    continue

                if match.name in self.affected_if_holes_after and not holes_after:
                    continue

                to_remove.append(match)
                if match.parent:
                    to_remove.append(match.parent)

        return to_remove


class TitleToEpisodeTitle(Rule):
    """
    If multiple different title are found, convert the one following episode number to episode_title.
    """
    dependency = TitleFromPosition

    def when(self, matches, context):
        titles = matches.named('title')
        title_groups = defaultdict(list)
        for title in titles:
            title_groups[title.value].append(title)

        episode_titles = []
        if len(title_groups) < 2:
            return episode_titles

        for title in titles:
            if matches.previous(title, lambda match: match.name == 'episode'):
                episode_titles.append(title)

        return episode_titles

    def then(self, matches, when_response, context):
        for title in when_response:
            matches.remove(title)
            title.name = 'episode_title'
            matches.append(title)


class EpisodeTitleFromPosition(TitleBaseRule):
    """
    Add episode title match in existing matches
    Must run after TitleFromPosition rule.
    """
    dependency = TitleToEpisodeTitle

    def __init__(self, previous_names):
        super().__init__('episode_title', ['title'])
        self.previous_names = previous_names

    def hole_filter(self, hole, matches):
        episode = matches.previous(hole,
                                   lambda previous: previous.named(*self.previous_names),
                                   0)

        crc32 = matches.named('crc32')

        return episode or crc32

    def filepart_filter(self, filepart, matches):
        # Filepart where title was found.
        if matches.range(filepart.start, filepart.end, lambda match: match.name == 'title'):
            return True
        return False

    def should_remove(self, match, matches, filepart, hole, context):
        if match.name == 'episode_details':
            return False
        return super().should_remove(match, matches, filepart, hole, context)

    def when(self, matches, context):  # pylint:disable=inconsistent-return-statements
        if matches.named('episode_title'):
            return
        return super().when(matches, context)


class AlternativeTitleReplace(Rule):
    """
    If alternateTitle was found and title is next to episode, season or date, replace it with episode_title.
    """
    dependency = EpisodeTitleFromPosition
    consequence = RenameMatch

    def __init__(self, previous_names):
        super().__init__()
        self.previous_names = previous_names

    def when(self, matches, context):  # pylint:disable=inconsistent-return-statements
        if matches.named('episode_title'):
            return

        alternative_title = matches.range(predicate=lambda match: match.name == 'alternative_title', index=0)
        if alternative_title:
            main_title = matches.chain_before(alternative_title.start, seps=seps,
                                              predicate=lambda match: 'title' in match.tags, index=0)
            if main_title:
                episode = matches.previous(main_title,
                                           lambda previous: previous.named(*self.previous_names),
                                           0)

                crc32 = matches.named('crc32')

                if episode or crc32:
                    return alternative_title

    def then(self, matches, when_response, context):
        matches.remove(when_response)
        when_response.name = 'episode_title'
        when_response.tags.append('alternative-replaced')
        matches.append(when_response)


class RenameEpisodeTitleWhenMovieType(Rule):
    """
    Rename episode_title by alternative_title when type is movie.
    """
    priority = POST_PROCESS

    dependency = TypeProcessor
    consequence = RenameMatch

    def when(self, matches, context):  # pylint:disable=inconsistent-return-statements
        if matches.named('episode_title', lambda m: 'alternative-replaced' not in m.tags) \
                and not matches.named('type', lambda m: m.value == 'episode'):
            return matches.named('episode_title')

    def then(self, matches, when_response, context):
        for match in when_response:
            matches.remove(match)
            match.name = 'alternative_title'
            matches.append(match)


class Filepart3EpisodeTitle(Rule):
    """
    If we have at least 3 filepart structured like this:

    Serie name/SO1/E01-episode_title.mkv
    AAAAAAAAAA/BBB/CCCCCCCCCCCCCCCCCCCC

    Serie name/SO1/episode_title-E01.mkv
    AAAAAAAAAA/BBB/CCCCCCCCCCCCCCCCCCCC

    If CCCC contains episode and BBB contains seasonNumber
    Then title is to be found in AAAA.
    """
    consequence = AppendMatch('title')

    def when(self, matches, context):  # pylint:disable=inconsistent-return-statements
        if matches.tagged('filepart-title'):
            return

        fileparts = matches.markers.named('path')
        if len(fileparts) < 3:
            return

        filename = fileparts[-1]
        directory = fileparts[-2]
        subdirectory = fileparts[-3]

        episode_number = matches.range(filename.start, filename.end, lambda match: match.name == 'episode', 0)
        if episode_number:
            season = matches.range(directory.start, directory.end, lambda match: match.name == 'season', 0)

            if season:
                hole = matches.holes(subdirectory.start, subdirectory.end,
                                     ignore=or_(lambda match: 'weak-episode' in match.tags, TitleBaseRule.is_ignored),
                                     formatter=cleanup, seps=title_seps, predicate=lambda match: match.value,
                                     index=0)
                if hole:
                    return hole


class Filepart2EpisodeTitle(Rule):
    """
    If we have at least 2 filepart structured like this:

    Serie name SO1/E01-episode_title.mkv
    AAAAAAAAAAAAA/BBBBBBBBBBBBBBBBBBBBB

    If BBBB contains episode and AAA contains a hole followed by seasonNumber
    then title is to be found in AAAA.

    or

    Serie name/SO1E01-episode_title.mkv
    AAAAAAAAAA/BBBBBBBBBBBBBBBBBBBBB

    If BBBB contains season and episode and AAA contains a hole
    then title is to be found in AAAA.
    """
    consequence = AppendMatch('title')

    def when(self, matches, context):  # pylint:disable=inconsistent-return-statements
        if matches.tagged('filepart-title'):
            return

        fileparts = matches.markers.named('path')
        if len(fileparts) < 2:
            return

        filename = fileparts[-1]
        directory = fileparts[-2]

        episode_number = matches.range(filename.start, filename.end, lambda match: match.name == 'episode', 0)
        if episode_number:
            season = (matches.range(directory.start, directory.end, lambda match: match.name == 'season', 0) or
                      matches.range(filename.start, filename.end, lambda match: match.name == 'season', 0))
            if season:
                hole = matches.holes(directory.start, directory.end,
                                     ignore=or_(lambda match: 'weak-episode' in match.tags, TitleBaseRule.is_ignored),
                                     formatter=cleanup, seps=title_seps,
                                     predicate=lambda match: match.value, index=0)
                if hole:
                    hole.tags.append('filepart-title')
                    return hole