# coding: utf-8
"""
This is part of the MSS Python's module.
Source: https://github.com/BoboTiG/python-mss
"""

# pylint: disable=import-error, too-many-locals

from __future__ import division

import ctypes
import ctypes.util
import sys

from .base import MSSBase
from .exception import ScreenShotError
from .screenshot import Size

__all__ = ('MSS',)


def cgfloat():
    # type: () -> Any
    """ Get the appropriate value for a float. """

    return ctypes.c_double if sys.maxsize > 2 ** 32 else ctypes.c_float


class CGPoint(ctypes.Structure):
    """ Structure that contains coordinates of a rectangle. """

    _fields_ = [('x', cgfloat()), ('y', cgfloat())]

    def __repr__(self):
        return '{0}(left={1} top={2})'.format(
            type(self).__name__, self.x, self.y)


class CGSize(ctypes.Structure):
    """ Structure that contains dimensions of an rectangle. """

    _fields_ = [('width', cgfloat()), ('height', cgfloat())]

    def __repr__(self):
        return '{0}(width={1} height={2})'.format(
            type(self).__name__, self.width, self.height)


class CGRect(ctypes.Structure):
    """ Structure that contains informations about a rectangle. """

    _fields_ = [('origin', CGPoint), ('size', CGSize)]

    def __repr__(self):
        return '{0}<{1} {2}>'.format(
            type(self).__name__, self.origin, self.size)


class MSS(MSSBase):
    """
    Multiple ScreenShots implementation for macOS.
    It uses intensively the CoreGraphics library.
    """

    max_displays = 32  # type: int

    def __init__(self):
        # type: () -> None
        """ macOS initialisations. """

        coregraphics = ctypes.util.find_library('CoreGraphics')
        if not coregraphics:
            raise ScreenShotError('No CoreGraphics library found.', locals())
        self.core = ctypes.cdll.LoadLibrary(coregraphics)

        self._set_argtypes()
        self._set_restypes()

    def _set_argtypes(self):
        # type: () -> None
        """ Functions arguments. """

        self.core.CGGetActiveDisplayList.argtypes = [
            ctypes.c_uint32,
            ctypes.POINTER(ctypes.c_uint32),
            ctypes.POINTER(ctypes.c_uint32)]
        self.core.CGDisplayBounds.argtypes = [ctypes.c_uint32]
        self.core.CGRectStandardize.argtypes = [CGRect]
        self.core.CGRectUnion.argtypes = [CGRect, CGRect]
        self.core.CGDisplayRotation.argtypes = [ctypes.c_uint32]
        self.core.CGWindowListCreateImage.argtypes = [
            CGRect,
            ctypes.c_uint32,
            ctypes.c_uint32,
            ctypes.c_uint32]
        self.core.CGImageGetWidth.argtypes = [ctypes.c_void_p]
        self.core.CGImageGetHeight.argtypes = [ctypes.c_void_p]
        self.core.CGImageGetDataProvider.argtypes = [ctypes.c_void_p]
        self.core.CGDataProviderCopyData.argtypes = [ctypes.c_void_p]
        self.core.CFDataGetBytePtr.argtypes = [ctypes.c_void_p]
        self.core.CFDataGetLength.argtypes = [ctypes.c_void_p]
        self.core.CGImageGetBytesPerRow.argtypes = [ctypes.c_void_p]
        self.core.CGImageGetBitsPerPixel.argtypes = [ctypes.c_void_p]
        self.core.CGDataProviderRelease.argtypes = [ctypes.c_void_p]
        self.core.CFRelease.argtypes = [ctypes.c_void_p]

    def _set_restypes(self):
        # type: () -> None
        """ Functions return type. """

        self.core.CGGetActiveDisplayList.restype = ctypes.c_int32
        self.core.CGDisplayBounds.restype = CGRect
        self.core.CGRectStandardize.restype = CGRect
        self.core.CGRectUnion.restype = CGRect
        self.core.CGDisplayRotation.restype = ctypes.c_float
        self.core.CGWindowListCreateImage.restype = ctypes.c_void_p
        self.core.CGImageGetWidth.restype = ctypes.c_size_t
        self.core.CGImageGetHeight.restype = ctypes.c_size_t
        self.core.CGImageGetDataProvider.restype = ctypes.c_void_p
        self.core.CGDataProviderCopyData.restype = ctypes.c_void_p
        self.core.CFDataGetBytePtr.restype = ctypes.c_void_p
        self.core.CFDataGetLength.restype = ctypes.c_uint64
        self.core.CGImageGetBytesPerRow.restype = ctypes.c_size_t
        self.core.CGImageGetBitsPerPixel.restype = ctypes.c_size_t
        self.core.CGDataProviderRelease.restype = ctypes.c_void_p
        self.core.CFRelease.restype = ctypes.c_void_p

    @property
    def monitors(self):
        # type: () -> List[Dict[str, int]]
        """ Get positions of monitors (see parent class). """

        if not self._monitors:
            # All monitors
            # We need to update the value with every single monitor found
            # using CGRectUnion.  Else we will end with infinite values.
            all_monitors = CGRect()
            self._monitors.append({})

            # Each monitors
            display_count = ctypes.c_uint32(0)
            active_displays = (ctypes.c_uint32 * self.max_displays)()
            self.core.CGGetActiveDisplayList(self.max_displays,
                                             active_displays,
                                             ctypes.byref(display_count))
            rotations = {0.0: 'normal', 90.0: 'right', -90.0: 'left'}
            for idx in range(display_count.value):
                display = active_displays[idx]
                rect = self.core.CGDisplayBounds(display)
                rect = self.core.CGRectStandardize(rect)
                width, height = rect.size.width, rect.size.height
                rot = self.core.CGDisplayRotation(display)
                if rotations[rot] in ['left', 'right']:
                    width, height = height, width
                self._monitors.append({
                    'left': int(rect.origin.x),
                    'top': int(rect.origin.y),
                    'width': int(width),
                    'height': int(height),
                })

                # Update AiO monitor's values
                all_monitors = self.core.CGRectUnion(all_monitors, rect)

            # Set the AiO monitor's values
            self._monitors[0] = {
                'left': int(all_monitors.origin.x),
                'top': int(all_monitors.origin.y),
                'width': int(all_monitors.size.width),
                'height': int(all_monitors.size.height),
            }

        return self._monitors

    def grab(self, monitor):
        # type: (Dict[str, int]) -> ScreenShot
        """
        See :meth:`MSSBase.grab <mss.base.MSSBase.grab>` for full details.
        """

        # Convert PIL bbox style
        if isinstance(monitor, tuple):
            monitor = {
                'left': monitor[0],
                'top': monitor[1],
                'width': monitor[2] - monitor[0],
                'height': monitor[3] - monitor[1],
            }

        rect = CGRect((monitor['left'], monitor['top']),
                      (monitor['width'], monitor['height']))

        image_ref = self.core.CGWindowListCreateImage(rect, 1, 0, 0)
        if not image_ref:
            raise ScreenShotError(
                'CoreGraphics.CGWindowListCreateImage() failed.', locals())

        width = int(self.core.CGImageGetWidth(image_ref))
        height = int(self.core.CGImageGetHeight(image_ref))
        prov = copy_data = None
        try:
            prov = self.core.CGImageGetDataProvider(image_ref)
            copy_data = self.core.CGDataProviderCopyData(prov)
            data_ref = self.core.CFDataGetBytePtr(copy_data)
            buf_len = self.core.CFDataGetLength(copy_data)
            raw = ctypes.cast(
                data_ref, ctypes.POINTER(ctypes.c_ubyte * buf_len))
            data = bytearray(raw.contents)

            # Remove padding per row
            bytes_per_row = int(self.core.CGImageGetBytesPerRow(image_ref))
            bytes_per_pixel = int(self.core.CGImageGetBitsPerPixel(image_ref))
            bytes_per_pixel = (bytes_per_pixel + 7) // 8

            if bytes_per_pixel * width != bytes_per_row:
                cropped = bytearray()
                for row in range(height):
                    start = row * bytes_per_row
                    end = start + width * bytes_per_pixel
                    cropped.extend(data[start:end])
                data = cropped
        finally:
            if prov:
                self.core.CGDataProviderRelease(prov)
            if copy_data:
                self.core.CFRelease(copy_data)

        return self.cls_image(data, monitor, size=Size(width, height))