import datetime
import json
import logging
import re
import typing
import urllib

import requests
from django.core.cache import cache
from django.core.exceptions import ValidationError
from django.core.files.images import ImageFile
from django.forms.utils import ErrorList
from wagtail import blocks
from wagtail.blocks.struct_block import StructBlockValidationError
from wagtail.contrib.table_block.blocks import TableBlock
from wagtail.images.blocks import ImageChooserBlock
from wagtail.images.models import Image
from wagtail.models import Collection

from maps_utils.blocks import MapFeatureCollectionBlock, MapPointBlock
from shared.const import (
    RICH_TEXT_DEFAULT_FEATURES, COLOR_CHOICES, BLACK_ON_WHITE, COLOR_CSS,
    ALIGN_CHOICES, LEFT, ALIGN_CSS
)

logger = logging.getLogger(__name__)


class ColorBlock(blocks.StructBlock):
    """
    Intended as parent class for blocks with color option.
    """

    color = blocks.ChoiceBlock(COLOR_CHOICES, label="barva", default=BLACK_ON_WHITE)

    def get_context(self, value, parent_context=None):
        context = super().get_context(value, parent_context=parent_context)
        if "css_class" not in context:
            context["css_class"] = []
        context["css_class"] += COLOR_CSS[value["color"]]
        return context


class AlignBlock(blocks.StructBlock):
    """
    Intended as parent class for blocks with align option.
    """

    align = blocks.ChoiceBlock(ALIGN_CHOICES, label="zarovnání", default=LEFT)

    def get_context(self, value, parent_context=None):
        context = super().get_context(value, parent_context=parent_context)
        if "css_class" not in context:
            context["css_class"] = []
        context["css_class"] += ALIGN_CSS[value["align"]]
        return context


class GalleryBlock(blocks.StructBlock):
    gallery_items = blocks.ListBlock(
        ImageChooserBlock(label="obrázek", required=True),
        label="Galerie",
        icon="image",
        group="ostatní",
    )

    class Meta:
        label = "Galerie"
        icon = "image"
        template = "styleguide2/includes/molecules/gallery/gallery.html"


class FigureBlock(blocks.StructBlock):
    img = ImageChooserBlock(label="Obrázek", required=True)
    caption = blocks.TextBlock(label="Popisek", required=False)

    class Meta:
        label = "Obrázek"
        icon = "image"
        template = "styleguide2/includes/atoms/figure/figure.html"


class MenuItemBlock(blocks.StructBlock):
    title = blocks.CharBlock(
        label="Titulek",
        help_text="Pokud není odkazovaná stránka na Majáku, použij možnost zadání samotné adresy níže.",
        required=True,
    )
    page = blocks.PageChooserBlock(label="Stránka", required=False)
    link = blocks.URLBlock(label="Odkaz", required=False)

    class Meta:
        label = "Položka v menu"
        template = "styleguide/2.3.x/menu_item.html"

    def clean(self, value):
        errors = {}

        if value["page"] and value["link"]:
            errors["page"] = ErrorList(
                ["Stránka nemůže být vybrána současně s odkazem."]
            )
            errors["link"] = ErrorList(
                ["Odkaz nemůže být vybrán současně se stránkou."]
            )
        if errors:
            raise StructBlockValidationError(errors)
        return super().clean(value)


class MenuParentBlock(blocks.StructBlock):
    title = blocks.CharBlock(label="Titulek", required=True)
    menu_items = blocks.ListBlock(MenuItemBlock(), label="Položky menu")

    class Meta:
        label = "Vyskakovací menu"
        template = "styleguide2/includes/molecules/dropdown/dropdown.html"


class ProgramItemBlock(blocks.StructBlock):
    title = blocks.CharBlock(label="Název", required=True)
    completion_percentage = blocks.IntegerBlock(
        label="Procento dokončení", required=True
    )
    issue_link = blocks.URLBlock(label="Odkaz na Redmine issue", required=False)


class YouTubeVideoBlock(blocks.StructBlock):
    poster_image = ImageChooserBlock(
        label="Náhled videa (automatické pole)",
        required=False,
        help_text="Není třeba vyplňovat, náhled bude " "dohledán automaticky.",
    )
    video_url = blocks.URLBlock(
        label="Odkaz na video",
        required=False,
        help_text="Odkaz na YouTube video bude automaticky "
        "zkonvertován na ID videa a NEBUDE uložen.",
    )
    video_id = blocks.CharBlock(
        label="ID videa (automatické pole)",
        required=False,
        help_text="Není třeba vyplňovat, bude automaticky " "načteno z odkazu.",
    )

    class Meta:
        label = "YouTube video"
        icon = "media"
        template = "styleguide2/includes/atoms/youtube_video/youtube_video.html"

    def clean(self, value):
        errors = {}

        if not value["video_url"] and not value["video_id"]:
            errors["video_url"] = ErrorList(["Zadejte prosím odkaz na YouTube video."])

        if value["video_url"]:
            if not value["video_url"].startswith("https://youtu.be") and not value[
                "video_url"
            ].startswith("https://www.youtube.com"):
                errors["video_url"] = ErrorList(
                    [
                        'Odkaz na video musí začínat "https://www.youtube.com" '
                        'nebo "https://youtu.be"'
                    ]
                )

        if value["video_id"]:
            if not re.match("^[A-Za-z0-9_-]{11}$", value["video_id"]):
                errors["video_url"] = ErrorList(
                    ["Formát ID YouTube videa není validní"]
                )

        if errors:
            raise StructBlockValidationError(errors)
        return super().clean(value)

    def get_prep_value(self, value):
        value = super().get_prep_value(value)

        if value["video_url"]:
            value["video_id"] = self.convert_youtube_link_to_video_id(
                value["video_url"]
            )
            value["poster_image"] = self.get_wagtail_image_id_for_youtube_poster(
                value["video_id"]
            )

        value["video_url"] = ""

        return value

    @staticmethod
    def convert_youtube_link_to_video_id(url):
        reg_str = (
            "((?<=(v|V)/)|(?<=youtu\.be/)|(?<=youtube\.com/watch(\?|\&)v=)"
            "|(?<=embed/))([\w-]+)"
        )
        search_result = re.search(reg_str, url)

        if search_result:
            return search_result.group(0)

        logger.warning(
            "Nepodařilo se získat video ID z YouTube URL", extra={"url": url}
        )
        return ""

    @classmethod
    def get_wagtail_image_id_for_youtube_poster(cls, video_id) -> int or None:
        image_url = "https://img.youtube.com/vi/{}/hqdefault.jpg".format(video_id)

        img_path = "/tmp/{}.jpg".format(video_id)
        urllib.request.urlretrieve(image_url, img_path)
        file = ImageFile(open(img_path, "rb"), name=img_path)

        return cls.get_image_id(file, "YT_poster_v_{}".format(video_id))

    @classmethod
    def get_image_id(cls, file: ImageFile, image_title: str) -> int:
        try:
            image = Image.objects.get(title=image_title)
        except Image.DoesNotExist:
            image = Image(
                title=image_title, file=file, collection=cls.get_posters_collection()
            )
            image.save()
        return image.id

    @staticmethod
    def get_posters_collection() -> Collection:
        collection_name = "YouTube nahledy"

        try:
            collection = Collection.objects.get(name=collection_name)
        except Collection.DoesNotExist:
            root_collection = Collection.get_first_root_node()
            collection = root_collection.add_child(name=collection_name)

        return collection


class CardBlock(blocks.StructBlock):
    img = ImageChooserBlock(label="Obrázek", required=False)

    headline = blocks.TextBlock(label="Titulek", required=False)

    content = blocks.StreamBlock(
        label="Obsah",
        local_blocks=[
            (
                "text",
                blocks.RichTextBlock(
                    label="Textový editor", features=RICH_TEXT_DEFAULT_FEATURES
                ),
            ),
            (
                "table",
                TableBlock(
                    template="styleguide2/includes/atoms/table/table.html",
                    label="Tabulka",
                ),
            ),
            ("figure", FigureBlock()),
            ("youtube", YouTubeVideoBlock()),
            ("map_point", MapPointBlock(label="Špendlík na mapě")),
            ("map_collection", MapFeatureCollectionBlock(label="Mapová kolekce")),
        ],
        required=False,
    )

    page = blocks.PageChooserBlock(label="Stránka", required=False)

    link = blocks.URLBlock(label="Odkaz", required=False)

    class Meta:
        label = "Karta"
        icon = "form"
        template = "styleguide2/includes/molecules/boxes/card_box_block.html"

    def clean(self, value):
        errors = {}

        if value["page"] and value["link"]:
            errors["page"] = ErrorList(
                ["Stránka nemůže být vybrána současně s odkazem."]
            )
            errors["link"] = ErrorList(
                ["Odkaz nemůže být vybrán současně se stránkou."]
            )

        if errors:
            raise StructBlockValidationError(errors)

        return super().clean(value)


class ButtonBlock(blocks.StructBlock):
    title = blocks.CharBlock(label="Titulek", max_length=128, required=True)

    color = blocks.ChoiceBlock(
        choices=(
            ("black", "Černá"),
            ("white", "Bílá"),
            ("pirati-yellow", "Žlutá"),
            ("grey-125", "Světle šedá"),
            ("blue-300", "Modrá"),
            ("cyan-200", "Tyrkysová"),
            ("green-400", "Zelená"),
            ("violet-400", "Vínová"),
            ("red-600", "Červená"),
        ),
        label="Barva",
        default="black",
    )

    hoveractive = blocks.BooleanBlock(
        label="Animovat na hover",
        default=True,
        help_text="Pokud je zapnuto, tlačítko při najetí kurzorem ukáže žlutou šipku.",
        required=False,
    )

    page = blocks.PageChooserBlock(label="Stránka", required=False)

    link = blocks.URLBlock(label="Odkaz", required=False)

    align = blocks.ChoiceBlock(
        choices=(
            ("auto", "Automaticky"),
            ("center", "Na střed"),
        ),
        label="Zarovnání",
        default="auto",
    )

    class Meta:
        label = "Tlačítko"
        icon = "code"
        template = "styleguide2/includes/atoms/buttons/round_button_block.html"

    def get_context(self, value, parent_context=None):
        context = super().get_context(value, parent_context)

        context["background_color"] = f"bg-{value['color']}"

        context["text_color"] = (
            "text-white"
            if value["color"]
            in (
                "black",
                "red-600",
                "blue-300",
                "cyan-200",
                "green-400",
                "violet-400",
                "red-600",
            )
            else "text-black"
        )

        context[
            "color_classes"
        ] = f"{context['background_color']} {context['text_color']}"

        return context

    def clean(self, value):
        errors = {}

        if value["page"] and value["link"]:
            errors["page"] = ErrorList(
                ["Stránka nemůže být vybrána současně s odkazem."]
            )
            errors["link"] = ErrorList(
                ["Odkaz nemůže být vybrán současně se stránkou."]
            )

        if not value["page"] and not value["link"]:
            errors["page"] = ErrorList(["Stránka nebo odkaz musí být vyplněna."])
            errors["link"] = ErrorList(["Stránka nebo odkaz musí být vyplněna."])

        if value["hoveractive"] and value["color"] not in (
            "black",
            "white",
            "grey-125",
        ):
            errors["hoveractive"] = ErrorList(
                ["Šipku lze ukazovat pouze s černým, bílým nebo šedým pozadím."]
            )

        if errors:
            raise StructBlockValidationError(errors)

        return super().clean(value)


class ButtonGroupBlock(blocks.StructBlock):
    buttons = blocks.ListBlock(ButtonBlock(), label="Tlačítka")

    class Meta:
        label = "Skupina tlačítek"
        icon = "list-ul"
        template = "styleguide2/includes/atoms/buttons/round_button_group_block.html"


class FullSizeHeaderBlock(blocks.StructBlock):
    title = blocks.CharBlock(label="Titulek", required=True)
    image_background = ImageChooserBlock(label="Obrázek v pozadí", required=True)
    image_foreground = ImageChooserBlock(label="Obrázek v popředí", required=False)
    button_group = blocks.ListBlock(ButtonBlock(), label="Tlačítka")

    class Meta:
        template = "styleguide/2.3.x/blocks/full_size_header_block.html"
        icon = "placeholder"
        label = "Nadpis s obrázkem a tlačítky přes celou stránku"


class HeadlineBlock(blocks.StructBlock):
    headline = blocks.CharBlock(label="Nadpis", max_length=300, required=True)

    tag = blocks.ChoiceBlock(
        choices=(
            ("h1", "H1"),
            ("h2", "H2"),
            ("h3", "H3"),
            ("h4", "H4"),
            ("h5", "H5"),
            ("h6", "H6"),
        ),
        label="Úroveň nadpisu",
        help_text="Čím nižší číslo, tím vyšší úroveň.",
        required=True,
        default="h1",
    )

    style = blocks.ChoiceBlock(
        choices=(
            ("head-alt-xl", "Velký, Bebas Neue - 6XL"),
            ("head-alt-lg", "Střední, Bebas Neue - 4XL"),
            ("head-alt-md", "Základní velikost - Roboto - MD"),
            ("head-alt-sm", "Malý - Roboto - SM"),
            ("head-alt-xs", "Extra malý - Roboto - XS"),
        ),
        label="Velikost",
        help_text="Náhled si prohlédněte na https://styleguide2.pirati.cz/pattern/patterns/atoms/text/headings.html.",
        default="head-alt-xl",
        required=True,
    )

    align = blocks.ChoiceBlock(
        choices=(
            ("auto", "Automaticky"),
            ("center", "Na střed"),
        ),
        label="Zarovnání",
        default="auto",
        required=True,
    )

    def get_context(self, value, parent_context=None):
        context = super().get_context(value, parent_context)

        context["responsive_style"] = {
            "head-alt-xl": "head-6xl",
            "head-alt-lg": "head-4xl",
            "head-alt-md": "head-base",
            "head-alt-sm": "head-sm",
            "head-alt-xs": "head-xs",
        }.get(value["style"], "head-4xl")

        return context

    class Meta:
        label = "Nadpis"
        icon = "bold"
        template = "styleguide2/includes/atoms/text/heading.html"


class TwoColumnBlock(blocks.StructBlock):
    left_column_content = blocks.StreamBlock(
        label="Obsah levého sloupce",
        local_blocks=[
            (
                "text",
                blocks.RichTextBlock(
                    label="Textový editor", features=RICH_TEXT_DEFAULT_FEATURES
                ),
            ),
            (
                "table",
                TableBlock(
                    template="styleguide2/includes/atoms/table/table.html",
                    label="Tabulka",
                ),
            ),
            ("card", CardBlock()),
            ("figure", FigureBlock()),
            ("youtube", YouTubeVideoBlock()),
            ("map_point", MapPointBlock(label="Špendlík na mapě")),
            ("map_collection", MapFeatureCollectionBlock(label="Mapová kolekce")),
            ("button", ButtonBlock()),
            ("button_group", ButtonGroupBlock()),
        ],
        required=True,
    )
    right_column_content = blocks.StreamBlock(
        label="Obsah pravého sloupce",
        local_blocks=[
            (
                "text",
                blocks.RichTextBlock(
                    label="Textový editor", features=RICH_TEXT_DEFAULT_FEATURES
                ),
            ),
            (
                "table",
                TableBlock(
                    template="styleguide2/includes/atoms/table/table.html",
                    label="Tabulka",
                ),
            ),
            ("card", CardBlock()),
            ("figure", FigureBlock()),
            ("youtube", YouTubeVideoBlock()),
            ("map_point", MapPointBlock(label="Špendlík na mapě")),
            ("map_collection", MapFeatureCollectionBlock(label="Mapová kolekce")),
            ("button", ButtonBlock()),
            ("button_group", ButtonGroupBlock()),
        ],
        required=True,
    )

    class Meta:
        label = "Dva sloupce"
        icon = "grip"
        template = "styleguide2/includes/atoms/grids/two_columns.html"


class ThreeColumnBlock(blocks.StructBlock):
    left_column_content = blocks.StreamBlock(
        label="Obsah levého sloupce",
        local_blocks=[
            (
                "text",
                blocks.RichTextBlock(
                    label="Textový editor", features=RICH_TEXT_DEFAULT_FEATURES
                ),
            ),
            (
                "table",
                TableBlock(
                    template="styleguide2/includes/atoms/table/table.html",
                    label="Tabulka",
                ),
            ),
            ("card", CardBlock()),
            ("figure", FigureBlock()),
            ("youtube", YouTubeVideoBlock()),
            ("map_point", MapPointBlock(label="Špendlík na mapě")),
            ("map_collection", MapFeatureCollectionBlock(label="Mapová kolekce")),
            ("button", ButtonBlock()),
            ("button_group", ButtonGroupBlock()),
        ],
        required=True,
    )
    middle_column_content = blocks.StreamBlock(
        label="Obsah prostředního sloupce",
        local_blocks=[
            (
                "text",
                blocks.RichTextBlock(
                    label="Textový editor", features=RICH_TEXT_DEFAULT_FEATURES
                ),
            ),
            (
                "table",
                TableBlock(
                    template="styleguide2/includes/atoms/table/table.html",
                    label="Tabulka",
                ),
            ),
            ("card", CardBlock()),
            ("figure", FigureBlock()),
            ("youtube", YouTubeVideoBlock()),
            ("map_point", MapPointBlock(label="Špendlík na mapě")),
            ("map_collection", MapFeatureCollectionBlock(label="Mapová kolekce")),
            ("button", ButtonBlock()),
            ("button_group", ButtonGroupBlock()),
        ],
        required=True,
    )
    right_column_content = blocks.StreamBlock(
        label="Obsah pravého sloupce",
        local_blocks=[
            (
                "text",
                blocks.RichTextBlock(
                    label="Textový editor", features=RICH_TEXT_DEFAULT_FEATURES
                ),
            ),
            (
                "table",
                TableBlock(
                    template="styleguide2/includes/atoms/table/table.html",
                    label="Tabulka",
                ),
            ),
            ("card", CardBlock()),
            ("figure", FigureBlock()),
            ("youtube", YouTubeVideoBlock()),
            ("map_point", MapPointBlock(label="Špendlík na mapě")),
            ("map_collection", MapFeatureCollectionBlock(label="Mapová kolekce")),
            ("button", ButtonBlock()),
            ("button_group", ButtonGroupBlock()),
        ],
        required=True,
    )

    class Meta:
        label = "Tři sloupce"
        icon = "grip"
        template = "styleguide2/includes/atoms/grids/three_columns.html"


class CardLinkBlockMixin(blocks.StructBlock):
    """
    Shared mixin for cards, which vary in templates and the types of pages
    they're allowed to link to.
    """

    image = ImageChooserBlock(label="Obrázek")
    title = blocks.CharBlock(label="Titulek", required=True)
    text = blocks.RichTextBlock(label="Krátký text pod nadpisem", required=False)

    page = blocks.PageChooserBlock(
        label="Stránka",
        page_type=[],
        required=False,
    )
    link = blocks.URLBlock(label="Odkaz", required=False)

    class Meta:
        # template = ""
        icon = "link"
        label = "Karta s odkazem"

    def clean(self, value):
        errors = {}

        if value["page"] and value["link"]:
            errors["page"] = ErrorList(
                ["Stránka nemůže být vybrána současně s odkazem."]
            )
            errors["link"] = ErrorList(
                ["Odkaz nemůže být vybrán současně se stránkou."]
            )
        elif not value["page"] and not value["link"]:
            errors["page"] = ErrorList(["Zvolte stránku nebo vyplňte odkaz."])
            errors["link"] = ErrorList(["Vyplňte odkaz nebo zvolte stránku."])
        if errors:
            raise StructBlockValidationError(errors)
        return super().clean(value)


class CardLinkWithHeadlineBlockMixin(blocks.StructBlock):
    headline = blocks.CharBlock(label="Titulek bloku", required=False)
    card_items = blocks.ListBlock(CardLinkBlockMixin(), label="Karty s odkazy")

    class Meta:
        # template = ""
        icon = "link"
        label = "Karty odkazů s nadpisem"


class ChartDataset(blocks.StructBlock):
    label = blocks.CharBlock(
        label="Označení zdroje dat",
        max_length=120,
    )

    data = blocks.ListBlock(
        blocks.IntegerBlock(),
        label="Data",
        default=[0],
    )

    class Meta:
        label = "Zdroj dat"


def get_redmine_projects():
    projects = requests.get("https://redmine.pirati.cz/projects.json?limit=10000")

    if projects.ok:
        projects = projects.json()["projects"]
    else:
        projects = []

    return [(project["id"], project["name"]) for project in projects]


class ChartRedmineIssueDataset(blocks.StructBlock):
    projects = blocks.MultipleChoiceBlock(
        label="Projekty", choices=get_redmine_projects
    )

    is_open = blocks.BooleanBlock(
        label="Jen otevřené",
        required=False,
    )
    is_closed = blocks.BooleanBlock(
        label="Jen uzavřené",
        required=False,
    )

    created_on_min_date = blocks.DateBlock(label="Min. datum vytvoření", required=True)
    created_on_max_date = blocks.DateBlock(label="Max. datum vytvoření", required=True)
    updated_on = blocks.CharBlock(
        label="Filtr pro datum aktualizace",
        max_length=128,
        help_text="Např. <=2023-01-01. Více informací na pi2.cz/redmine-api",
        required=False,
    )

    issue_label = blocks.CharBlock(
        label="Označení úkolů uvnitř grafu",
        max_length=128,
        required=True,
    )

    split_per_project = blocks.BooleanBlock(
        label="Rozdělit podle projektu",
        required=False,
    )

    only_grow = blocks.BooleanBlock(
        label="Pouze růst nahoru",
        required=False,
    )

    def _get_issues_url(
        self, value, project_id: typing.Union[None, str, list[str]] = None
    ):
        url = "https://redmine.pirati.cz/issues.json"
        params = [
            ("sort", "created_on"),
            ("limit", "100"),
            (
                "created_on",
                f"><{value['created_on_min_date']}|{value['created_on_max_date']}",
            ),
        ]

        if isinstance(project_id, str):
            params.append(("project_id", project_id))
        elif isinstance(project_id, list):
            params.append(("project_id", ",".join(project_id)))

        is_open = value.get("is_open", False)
        is_closed = value.get("is_closed", False)

        if is_open and is_closed:
            params.append(("status_id", "*"))
        elif is_open:
            params.append(("status_id", "open"))
        elif is_closed:
            params.append(("status_id", "closed"))

        if value.get("updated_on", "") != "":
            params.append(("updated_on", value["updated_on"]))

        is_first = True

        for param_set in params:
            param, param_value = param_set

            url += "?" if is_first else "&"
            url += f"{param}={urllib.parse.quote(param_value)}"

            is_first = False

        return url

    def _get_parsed_issues(self, value, labels, issues_url) -> tuple:
        issues_response = cache.get(f"redmine_{issues_url}")

        if issues_response is None:
            issues_response = requests.get(issues_url)
            issues_response.raise_for_status()
            issues_response = issues_response.json()

            cache.set(
                f"redmine_{issues_url}",
                issues_response,
                timeout=604800,  # 1 week
            )

        only_grow = value.get("only_grow", False)

        collected_issues = issues_response["issues"]
        offset = 0

        while issues_response["total_count"] - offset > len(issues_response["issues"]):
            offset += 100
            url_with_offset = f"{issues_url}&offset={offset}"

            issues_response = cache.get(f"redmine_{url_with_offset}")

            if issues_response is None:
                issues_response = requests.get(url_with_offset)
                issues_response.raise_for_status()
                issues_response = issues_response.json()

                cache.set(
                    f"redmine_{url_with_offset}",
                    issues_response,
                    timeout=604800,  # 1 week
                )

            collected_issues += issues_response["issues"]

        ending_position = len(collected_issues) - 1

        data = None

        current_issue_count = 0
        current_label = datetime.date.fromisoformat(
            collected_issues[0]["created_on"].split("T")[0]
        )

        if not only_grow:
            data = [0] * len(labels)

            for position, issue in enumerate(
                collected_issues
            ):  # Assume correct sorting order
                created_on_date = datetime.date.fromisoformat(
                    issue["created_on"].split("T")[0]
                )

                if current_label != created_on_date or position == ending_position:
                    data[
                        labels.index(current_label)
                    ] = current_issue_count  # Assume labels are unique
                    current_label = created_on_date

                    if position != ending_position:
                        current_issue_count = 0
                    else:
                        data[labels.index(current_label)] = 1
                        break

                current_issue_count += 1
        else:
            data = []
            issue_count_by_date = {}

            for position, issue in enumerate(
                collected_issues
            ):  # Assume correct sorting order
                created_on_date = datetime.date.fromisoformat(
                    issue["created_on"].split("T")[0]
                )

                if current_label not in issue_count_by_date:
                    issue_count_by_date[current_label] = 0

                if current_label != created_on_date or position == ending_position:
                    issue_count_by_date[
                        current_label
                    ] = current_issue_count  # Assume labels are unique
                    current_label = created_on_date

                    if position == ending_position:
                        issue_count_by_date[current_label] = current_issue_count + 1
                        break

                current_issue_count += 1

            previous_date = None

            for date in labels:
                if date not in issue_count_by_date:
                    if previous_date is None:
                        data.append(0)
                        continue

                    data.append(issue_count_by_date[previous_date])
                    continue

                data.append(issue_count_by_date[date])
                previous_date = date

        return data

    def get_context(self, value) -> list:
        context = super().get_context(value)

        labels = []
        datasets = []

        for day_count in range(
            (value["created_on_max_date"] - value["created_on_min_date"]).days + 1
        ):
            day = value["created_on_min_date"] + datetime.timedelta(days=day_count)
            labels.append(day)

        if value.get("split_per_project", False):
            project_choices_lookup = dict(get_redmine_projects())

            for project_id in value["projects"]:
                issues_url = self._get_issues_url(value, project_id)

                datasets.append(
                    {
                        "label": f"{value['issue_label']} - {project_choices_lookup[int(project_id)]}",
                        "data": self._get_parsed_issues(value, labels, issues_url),
                    }
                )
        else:
            issues_url = self._get_issues_url(value, value["projects"])

            datasets.append(
                {
                    "label": value["issue_label"],
                    "data": self._get_parsed_issues(value, labels, issues_url),
                }
            )

        labels = [date.strftime("%d. %m. %Y") for date in labels]

        context["parsed_issue_labels"] = labels
        context["parsed_issues"] = datasets

        return context

    class Meta:
        label = "Zdroj dat z Redmine (úkoly vytvořené za den)"
        help_text = (
            "Po prvním otevření se bude stránka otevírat delší dobu, "
            "zatímco se na pozadí načítají data do grafu. Poté bude "
            "fungovat běžně."
        )


class ChartBlock(blocks.StructBlock):
    title = blocks.CharBlock(
        label="Název",
        max_length=120,
    )
    chart_type = blocks.ChoiceBlock(
        label="Typ",
        choices=[
            ("bar", "Graf se sloupci"),
            ("horizontalBar", "Graf s vodorovnými sloupci"),
            ("pie", "Koláčový graf"),
            ("doughnut", "Donutový graf"),
            ("polarArea", "Graf polární oblasti"),
            ("radar", "Radarový graf"),
            ("line", "Graf s liniemi"),
        ],
        default="bar",
    )

    hide_points = blocks.BooleanBlock(
        label="Schovat body",
        required=False,
        help_text="Mění vzhled pouze u linových grafů.",
    )

    local_labels = blocks.ListBlock(
        blocks.CharBlock(
            max_length=40,
            label="Skupina",
        ),
        default=[],
        blank=True,
        required=False,
        collapsed=True,
        label="Místně definované skupiny",
    )
    local_datasets = blocks.ListBlock(
        ChartDataset(),
        default=[],
        blank=True,
        required=False,
        collapsed=True,
        label="Místní zdroje dat",
    )

    redmine_issue_datasets = blocks.ListBlock(
        ChartRedmineIssueDataset(label="Redmine úkoly"),
        default=[],
        blank=True,
        required=False,
        label="Zdroje dat z Redmine (úkoly)",
        help_text=(
            "Úkoly, podle doby vytvoření. Pokud definuješ "
            "více zdrojů, datumy v nich musí být stejné."
        ),
    )

    def clean(self, value):
        result = super().clean(value)

        redmine_issues_exist = len(value.get("redmine_issue_datasets", [])) != 0

        if len(value.get("local_datasets", [])) != 0 and redmine_issues_exist:
            raise ValidationError(
                "Definuj pouze jeden typ zdroje dat - místní, nebo z Redmine."
            )

        if redmine_issues_exist:
            min_date = value["redmine_issue_datasets"][0]["created_on_min_date"]
            max_date = value["redmine_issue_datasets"][0]["created_on_max_date"]

            if len(value["redmine_issue_datasets"]) > 1:
                for dataset in value["redmine_issue_datasets"]:
                    if (
                        dataset["created_on_min_date"] != min_date
                        or dataset["created_on_max_date"] != max_date
                    ):
                        raise ValidationError(
                            "Maximální a minimální data všech zdrojů z Redmine musí být stejné"
                        )

        return result

    def get_context(self, value, parent_context=None):
        context = super().get_context(value, parent_context=parent_context)

        datasets = []
        labels = []

        if len(value["local_datasets"]) != 0:
            labels = value["local_labels"]

            for dataset in value["local_datasets"]:
                datasets.append(
                    {
                        "label": dataset["label"],
                        "data": [item for item in dataset["data"]],
                    }
                )
        elif len(value["redmine_issue_datasets"]) != 0:
            for dataset_wrapper in value["redmine_issue_datasets"]:
                redmine_context = ChartRedmineIssueDataset().get_context(
                    dataset_wrapper
                )

                labels = redmine_context["parsed_issue_labels"]
                datasets += redmine_context["parsed_issues"]

        value["datasets"] = json.dumps(datasets)
        value["labels"] = json.dumps([label for label in labels])

        return context

    class Meta:
        template = "styleguide2/includes/molecules/blocks/chart.html"
        label = "Graf"
        icon = "form"
        help_text = """Všechny položky zdrojů dat se chovají jako sloupce.
Zobrazí se tolik definovaných sloupců, kolik existuje skupin."""


class FooterLinksBlock(blocks.StructBlock):
    label = blocks.CharBlock(label="Titulek zápatí", required=True)
    items = blocks.ListBlock(
        blocks.StructBlock(
            [
                ("url", blocks.URLBlock(label="Odkaz")),
                ("text", blocks.CharBlock(label="Text v odkazu")),
            ]
        ),
        label="Odkazy",
        required=True,
    )

    class Meta:
        label = "Seznam odkazů v zápatí"
        icon = "list-ul"
        template = "shared/blocks/footer_links_block.html"


class NewsletterSubscriptionBlock(blocks.StructBlock):
    list_id = blocks.CharBlock(label="ID newsletteru", required=True)
    description = blocks.CharBlock(
        label="Popis newsletteru",
        required=True,
        default="Fake news tam nenajdeš, ale dozvíš se, co chystáme doopravdy!",
    )

    class Meta:
        label = "Formulář pro odebírání newsletteru"
        icon = "form"
        template = "styleguide2/includes/organisms/main_section/newsletter_section.html"


class AdvancedTextBlock(ColorBlock, AlignBlock):
    text = blocks.RichTextBlock(
        label="Textový editor",
        features=RICH_TEXT_DEFAULT_FEATURES,
    )

    class Meta:
        label = "Textový editor (pokročilý)"
        icon = "doc-full"
        template = "styleguide2/includes/atoms/text/prose_advanced_richtext.html"


DEFAULT_CONTENT_BLOCKS = [
    (
        "text",
        blocks.RichTextBlock(
            label="Textový editor",
            features=RICH_TEXT_DEFAULT_FEATURES,
            template="styleguide2/includes/atoms/text/prose_richtext.html",
        ),
    ),
    (
        "advanced_text",
        AdvancedTextBlock()
    ),
    ("headline", HeadlineBlock()),
    (
        "table",
        TableBlock(
            template="styleguide2/includes/atoms/table/table.html", label="Tabulka"
        ),
    ),
    ("gallery", GalleryBlock(label="Galerie")),
    ("figure", FigureBlock()),
    ("card", CardBlock()),
    ("two_columns", TwoColumnBlock()),
    ("three_columns", ThreeColumnBlock()),
    ("youtube", YouTubeVideoBlock(label="YouTube video")),
    ("map_point", MapPointBlock(label="Špendlík na mapě")),
    ("map_collection", MapFeatureCollectionBlock(label="Mapová kolekce")),
    ("button", ButtonBlock()),
    ("button_group", ButtonGroupBlock()),
]