# Copyright 2014 Christoph Reiter
#
# This program 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 2 of the License, or
# (at your option) any later version.

"""Some helper function for loading and converting image data."""

import math

from gi.repository import GdkPixbuf, Gtk, Gdk, GLib
import cairo


def get_surface_for_pixbuf(
    widget: Gtk.Widget, pixbuf: GdkPixbuf.Pixbuf | None
) -> cairo.Surface | None:
    """:returns: a cairo surface, if possible"""
    if not pixbuf:
        return None
    scale_factor = widget.get_scale_factor()
    return Gdk.cairo_surface_create_from_pixbuf(
        pixbuf, scale_factor, widget.get_window()
    )


def get_surface_extents(surface):
    """Gives (x, y, width, height) for a surface, scale independent"""

    ctx = cairo.Context(surface)
    x1, y1, x2, y2 = ctx.clip_extents()
    x1 = int(math.floor(x1))
    y1 = int(math.floor(y1))
    x2 = int(math.ceil(x2))
    y2 = int(math.ceil(y2))
    x2 -= x1
    y2 -= y1

    return (x1, y1, x2, y2)


def get_border_radius(_widgets=None):
    """Returns the border radius commonly used in the current theme.
    If there are no rounded corners 0 will be returned.
    """
    _widgets = _widgets or []
    if not _widgets:
        b = Gtk.Button()
        b.show()
        e = Gtk.Entry()
        e.show()
        _widgets += [b, e]

    radii = []
    for widget in _widgets:
        style_context = widget.get_style_context()
        radii.append(
            style_context.get_property(
                Gtk.STYLE_PROPERTY_BORDER_RADIUS, style_context.get_state()
            )
        )
    radius = max(radii)

    # Doesn't work on the default Ubuntu theme.
    # Not sure why, so fix manually for now
    theme_name = Gtk.Settings.get_default().props.gtk_theme_name
    if theme_name in ("Ambiance", "Radiance"):
        radius = int(radius / 1.5)

    return radius


def add_border(pixbuf, color, width=1, radius=0):
    """Add a border to the pixbuf and round of the edges.
    color is a Gdk.RGBA
    The resulting pixbuf will be width * 2px higher and wider.

    Can not fail.
    """

    pi = math.pi

    def new_border_path(ctx, w, h, r, margin=0):
        d = r + margin
        ctx.new_path()
        ctx.arc(w - d, d, r, -pi / 2, 0)
        ctx.arc(w - d, h - d, r, 0, pi / 2)
        ctx.arc(d, h - d, r, pi / 2, pi)
        ctx.arc(d, d, r, pi, pi * 3 / 2)
        ctx.close_path()

    w = pixbuf.get_width() + width * 2
    h = pixbuf.get_height() + width * 2
    r = min(radius, min(w, h) / 2)

    surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, w, h)
    ctx = cairo.Context(surface)

    new_border_path(ctx, w, h, r + width)
    ctx.clip()
    ctx.set_source_rgba(color.red, color.green, color.blue, color.alpha)
    ctx.paint()

    new_border_path(ctx, w, h, r, width)
    Gdk.cairo_set_source_pixbuf(ctx, pixbuf, width, width)
    ctx.fill()

    return Gdk.pixbuf_get_from_surface(surface, 0, 0, w, h)


def add_border_widget(pixbuf, widget):
    """Like add_border() but uses the widget to get a border color and a
    border width.
    """

    context = widget.get_style_context()
    color = context.get_color(context.get_state())
    color.alpha *= 0.1
    scale_factor = widget.get_scale_factor()
    border_radius = get_border_radius() * scale_factor

    return add_border(pixbuf, color, width=scale_factor, radius=border_radius)


def scale(pixbuf, boundary, scale_up=True, force_copy=False):
    """Scale a pixbuf so it fits into the boundary.
    (preserves image aspect ratio)

    If `scale_up` is True, the resulting pixbuf can be larger than
    the original one.

    If `force_copy` is False the resulting pixbuf might be the passed one.

    Can not fail.
    """

    size = pixbuf.get_width(), pixbuf.get_height()

    scale_w, scale_h = calc_scale_size(boundary, size, scale_up)

    if (scale_w, scale_h) == size:
        if force_copy:
            return pixbuf.copy()
        return pixbuf

    return pixbuf.scale_simple(scale_w, scale_h, GdkPixbuf.InterpType.BILINEAR)


def calc_scale_size(boundary, size, scale_up=True):
    """Returns the biggest possible size to fit into the boundary,
    respecting the aspect ratio.

    If `scale_up` is True the result can be larger than size.

    All sizes have to be > 0.
    """

    bwidth, bheight = boundary
    iwidth, iheight = size

    if bwidth <= 0 or bheight <= 0 or iwidth <= 0 or iheight <= 0:
        raise ValueError

    scale_w, scale_h = iwidth, iheight

    if iwidth > bwidth or iheight > bheight or scale_up:
        bratio = float(bwidth) / bheight
        iratio = float(iwidth) / iheight

        if iratio > bratio:
            scale_w = bwidth
            scale_h = int(bwidth / iratio)
        else:
            scale_w = int(bheight * iratio)
            scale_h = bheight

    return scale_w, scale_h


def pixbuf_from_file(fileobj, boundary, scale_factor=1):
    """Returns a pixbuf with the maximum size defined by boundary.

    Can raise GLib.GError and return None
    """

    try:
        pixbuf = GdkPixbuf.Pixbuf.new_from_file(fileobj.name)
    except GLib.GError:
        try:
            loader = GdkPixbuf.PixbufLoader()
            loader.write(fileobj.read())
            loader.close()
            fileobj.seek(0, 0)
            pixbuf = loader.get_pixbuf()
        except OSError:
            return None

    w, h = boundary
    w *= scale_factor
    h *= scale_factor

    return scale(pixbuf, (w, h), scale_up=True)
