from os import makedirs
from os.path import basename, dirname, join
from posixpath import normpath
from shutil import copyfile

from django import template
from django.conf import settings
from django.contrib.staticfiles import finders
from django.core.files.storage import default_storage
from sorl.thumbnail import default
from sorl.thumbnail.conf import settings as thumbnail_settings
from sorl.thumbnail.parsers import parse_geometry

try:
    from wagtail.images.models import Filter
    from wagtail.images.shortcuts import get_rendition_or_not_found

    WAGTAIL_THUMBNAILS_AVAILABLE = True
except Exception:  # pragma: no cover
    Filter = None
    get_rendition_or_not_found = None
    WAGTAIL_THUMBNAILS_AVAILABLE = False

register = template.Library()

MAXIMUM_SUPPORTED_SCREEN_WIDTH = 5120


def to_webp(image, options=None):
    if image is None:
        return None

    # pylint: disable=protected-access
    return default.backend.get_thumbnail(
        image,
        str(int(image.width)),
        upscale=False,
        format="WEBP",
        **(options or {}),
    )


class FakeFile(object):
    "fake django.core.files.File object"

    def __init__(self, name):
        self.name = name


def static_to_media(path):
    """
    Copy a static file to the media folder, if not allready present, so
    thumbnails can be generated.

    This only checks by filename, so if you update your static images the
    media representations will not be updated at all!
    """
    if not default_storage.exists(path):
        normalized_path = normpath(path).lstrip("/")
        absolute_path = finders.find(normalized_path)
        media_dir = join(settings.MEDIA_ROOT, dirname(path))
        makedirs(media_dir, mode=0o755, exist_ok=True)
        target_path = join(media_dir, basename(path))
        copyfile(absolute_path, target_path)

    return path


# pylint: disable=redefined-builtin


@register.inclusion_tag("ocyan/thumbnail/picture.html", takes_context=True)
def picture(
    context,
    image,
    geometry,
    format=thumbnail_settings.THUMBNAIL_FORMAT,
    alt=None,
    upscale=False,
    classes="img-fluid",
    **options,
):
    width, height = parse_geometry(geometry)
    return {
        "width": width,
        "height": height,
        "image": image,
        "geometry": geometry,
        "format": format,
        "alt": alt if alt is not None else context.get("alt"),
        "upscale": upscale,
        "classes": classes,
        "options": options,
    }


@register.inclusion_tag("ocyan/thumbnail/lazy_picture.html", takes_context=True)
def lazy_picture(
    context,
    image,
    geometry,
    format=thumbnail_settings.THUMBNAIL_FORMAT,
    alt=None,
    upscale=None,
    classes="img-fluid",
    **options,
):
    pic = picture(
        context,
        image,
        geometry,
        format=format,
        alt=alt,
        upscale=upscale,
        classes=classes,
        **options,
    )
    pic["lazy"] = True
    return pic


@register.inclusion_tag("ocyan/thumbnail/responsive_image.html", takes_context=True)
def responsive_image(
    context,
    image,
    format=thumbnail_settings.THUMBNAIL_FORMAT,
    alt=None,
    classes="img-fluid",
    mobile=None,
    **options,
):
    """
    responsive_image will create 3 different resolution images based on the size of the uploaded image.
    the ratios are: 100% 50% and 30%

    If needed a separate image can be passed for mobile devices. This is useful for large hero graphics
    that are landscape on desktop but must be in portrait mode for mobile.

    Example for wagtail image, this will crap the image around the focal point::
        {% load wagtailimages_tags ocyan_thumbnail %}

        {% image self.image fill-200x400-c100 as mobile %}
        {% lazy_responsive_image  self.image.file mobile=mobile.file %}

    Example for oscar image::
        {% load ocyan_thumbnail sorl_thumbnail %}

        {% thumbnail image '200x400' as portrait %}
        {% lazy_responsive_image  image mobile=portrait %}
        {% endthumbnail %}

    Note that you don't have to scale the image, you can also supply a completely
    different image.
    """
    pic = picture(
        context,
        image,
        str(MAXIMUM_SUPPORTED_SCREEN_WIDTH),
        format=format,
        alt=alt,
        upscale=False,
        classes=classes,
        mobile=mobile,
        **options,
    )
    pic["mobile"] = mobile
    pic["mobile_webp"] = to_webp(mobile)

    return pic


@register.inclusion_tag(
    "ocyan/thumbnail/lazy_responsive_image.html", takes_context=True
)
def lazy_responsive_image(
    context,
    image,
    format=thumbnail_settings.THUMBNAIL_FORMAT,
    alt=None,
    classes="img-fluid",
    mobile=None,
    **options,
):
    pic = lazy_picture(
        context,
        image,
        str(MAXIMUM_SUPPORTED_SCREEN_WIDTH),
        format=format,
        alt=alt,
        upscale=False,
        classes=classes,
        **options,
    )
    pic["mobile"] = mobile
    pic["mobile_webp"] = to_webp(mobile)

    return pic


@register.inclusion_tag("ocyan/thumbnail/owl_lazy_picture.html", takes_context=True)
def lazy_owl_picture(
    context,
    image,
    geometry,
    format=thumbnail_settings.THUMBNAIL_FORMAT,
    alt=None,
    upscale=None,
    classes="img-fluid",
    **options,
):
    return lazy_picture(
        context,
        image,
        geometry,
        format=format,
        alt=alt,
        upscale=upscale,
        classes=classes,
        **options,
    )


@register.inclusion_tag("ocyan/thumbnail/static_picture.html", takes_context=True)
def static_picture(
    context,
    staticfile,
    geometry,
    format=thumbnail_settings.THUMBNAIL_FORMAT,
    alt=None,
    upscale=None,
    classes="img-fluid",
    **options,
):
    "Try not to use this, thnx"
    media_path = static_to_media(staticfile)
    pic = picture(
        context,
        media_path,
        geometry,
        format=format,
        alt=alt,
        upscale=upscale,
        classes=classes,
        **options,
    )
    # pylint: disable=protected-access
    pic["format"] = default.backend._get_format(FakeFile(media_path))
    return pic


@register.inclusion_tag("ocyan/thumbnail/static_lazy_picture.html", takes_context=True)
def static_lazy_picture(
    context,
    staticfile,
    geometry,
    format=thumbnail_settings.THUMBNAIL_FORMAT,
    alt=None,
    upscale=None,
    classes="img-fluid",
    **options,
):
    "Try to never use this, only to quickfix annoying lighthouse issues in customizations"
    pic = static_picture(
        context,
        staticfile,
        geometry,
        format=format,
        alt=alt,
        upscale=upscale,
        classes=classes,
        **options,
    )
    pic["lazy"] = True
    return pic


@register.simple_tag(takes_context=True)
def retina(context, thumb, multiplier, options=None):
    # pylint: disable=protected-access
    try:
        _format = default.backend._get_format(thumb)
    except AttributeError:  # it is the dummy image haha
        _format = "JPEG"

    times = float(multiplier)
    return default.backend.get_thumbnail(
        context["image"],
        "%sx%s" % (int(thumb.width * times), int(thumb.height * times)),
        upscale=False,
        format=_format,
        **(options or {}),
    )


@register.inclusion_tag("ocyan/thumbnail/focuspoint_picture.html")
def focuspoint_picture(
    wagtailimage,
    classes="img-fluid",
):
    """
    This templatetage will render a picture tag with images with sizes 2560, 1200 and 768 pixel wide::

        <picture>
            <source srcset="henk.width-2560.jpg 2560w, henk.width-2560.fill-1200x800-c100.jpg 1200w, henk.width-2560.fill-768x512-c100.jpg 768w">
            <source type="image/webp" srcset="henk.width-2560.format-webp.webp 2560w, henk.width-2560.fill-1200x800-c100.format-webp.webp 1200w, henk.width-2560.fill-768x512-c100.format-webp.webp 768w">
            <img src="henk.width-2560.jpg" alt="alt" width="2560" height="1080" class="img-fluid">
        </picture>

    If the images has a focal area defined, the image will use the focal area and crop
    towards the focal area. If not, the image will just be scaled.

    If the image is too small to get the renditions 2560, 1200 and 768, no upscaling
    will be performed and the images that can not be rendered will be left out.
    """
    if not WAGTAIL_THUMBNAILS_AVAILABLE or wagtailimage is None:
        return {"img": wagtailimage, "classes": classes}

    img2560 = get_rendition_or_not_found(wagtailimage, Filter(spec="width-2560"))
    img2560_webp = get_rendition_or_not_found(
        wagtailimage, Filter(spec="width-2560|format-webp")
    )
    img1200 = None
    img1200_webp = None
    img768 = None
    img768_webp = None

    if wagtailimage.has_focal_point():
        img1200_scale = 1200 / img2560.width
        if img1200_scale < 1.0:
            img1200 = get_rendition_or_not_found(
                wagtailimage,
                Filter(spec="fill-1200x%s-c50" % (int(img2560.height * img1200_scale))),
            )
            img1200_webp = get_rendition_or_not_found(
                wagtailimage,
                Filter(
                    spec="fill-1200x%s-c50|format-webp"
                    % (int(img2560.height * img1200_scale))
                ),
            )

        img768_scale = 768 / img2560.width
        if img768_scale < 1.0:
            img768 = get_rendition_or_not_found(
                wagtailimage,
                Filter(spec="fill-768x%s-c100" % (int(img2560.height * img768_scale))),
            )
            img768_webp = get_rendition_or_not_found(
                wagtailimage,
                Filter(
                    spec="fill-768x%s-c100|format-webp"
                    % (int(img2560.height * img768_scale))
                ),
            )
    else:
        if img2560.width > 1200:
            img1200 = get_rendition_or_not_found(
                wagtailimage, Filter(spec="width-1200")
            )
            img1200_webp = get_rendition_or_not_found(
                wagtailimage, Filter(spec="width-1200|format-webp")
            )

        if img2560.width > 768:
            img768 = get_rendition_or_not_found(wagtailimage, Filter(spec="width-768"))
            img768_webp = get_rendition_or_not_found(
                wagtailimage, Filter(spec="width-768|format-webp")
            )

    return {
        # pass the original image along for the cache key
        "img": wagtailimage,
        "img2560": img2560,
        "img1200": img1200,
        "img768": img768,
        "img2560_webp": img2560_webp,
        "img1200_webp": img1200_webp,
        "img768_webp": img768_webp,
        "classes": classes,
    }


@register.inclusion_tag("ocyan/thumbnail/lazy_focuspoint_picture.html")
def lazy_focuspoint_picture(
    wagtailimage,
    classes="img-fluid",
):
    """
    Same as focuspoint_picture, but lazy.
    """
    return focuspoint_picture(wagtailimage, classes)


@register.inclusion_tag("ocyan/thumbnail/lazy_tns_focuspoint_picture.html")
def lazy_tns_focuspoint_picture(wagtailimage, classes="img-fluid"):
    return focuspoint_picture(wagtailimage, classes)
