# grid_samp - Grid-based image sampling toolbox
# Copyright (C) 2026 Maarten Leemans, Crhrisophe Bossens, Johan Wagemans
#
# 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 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.

from PIL import Image

from grid_samp import ImageRegion
from grid_samp import ImageRegionList

class RegionBasedGrid:
    def __init__(self, anchor_region: ImageRegion, image: Image.Image):
        """
        Initialize an RegionBasedGrid from an anchor image region.

        This grid is generated by propagating the spatial properties of an
        existing image region (the anchor region) across the given image, 
        dividing the image into equally sized regions based on the anchor.

        Parameters
        ----------
        anchor_region : ImageRegion
            Reference image region that defines the spatial layout of the grid.
            Its position and dimensions are used as the basis for generating
            additional image regions.
        image : PIL.Image.Image
            Source image over which the grid is generated.

        Raises
        ------
        TypeError
            If `anchor_region` is not an instance of ImageRegion.
        ValueError
            If the anchor region does not contain sufficient spatial
            information to generate a grid.

        Notes
        -----
        The generated image regions are computed at initialization time and
        stored internally. The grid is immutable after creation.

        Examples
        --------
        >>> from grid_samp.image_region import ImageRegion
        >>> from grid_samp.grids.image_region_based_grid import RegionBasedGrid
        >>> anchor = ImageRegion(x=0, y=0, width=64, height=64)
        >>> grid = RegionBasedGrid(anchor, image)
        """
        # --- Type validation ---
        if not isinstance(anchor_region, ImageRegion):
            raise TypeError(
                "anchor_region must be an instance of ImageRegion, "
                f"got {type(anchor_region).__name__}"
            )
        
        if not isinstance(image, Image.Image):
            raise TypeError(
                "image must be an instance of Image.Image, "
                f"got {type(image).__name__}"
            )
        
        # --- Spatial validation ---
        required_attributes = ("_width", "_height", "_x0", "_y0")
        missing_attributes = [attribute for attribute in required_attributes if not hasattr(anchor_region, attribute)]

        if missing_attributes:
            raise ValueError(
                "anchor_region does not contain sufficient spatial information "
                "to generate a grid. Missing attributes: "
                f"{', '.join(missing_attributes)}"
            )
        
        self._image_regions = RegionBasedGrid.generate(anchor_region, image)
        
    def get_image_region(self, index):
        return self._image_regions[index]
    
    @property
    def image_region_list(self):
        return ImageRegionList(self._image_regions)
    
    @staticmethod
    def generate(source_Image_Region, image):
        width, height = image.size
        
        width_division = RegionBasedGrid.subdivide(width, source_Image_Region._width, source_Image_Region._x0)
        height_division = RegionBasedGrid.subdivide(height, source_Image_Region._height, source_Image_Region._y0)
        
        image_regions = []
        for row_index, y in enumerate(height_division):
            for col_index, x in enumerate(width_division):
                new_region = ImageRegion(x[0], y[0], x[1], y[1])
                new_region.set_grid_data(row_index + 1, col_index + 1)
                new_region._grid['x'] = x[0]
                new_region._grid['y'] = y[0]
                image_regions.append(new_region)
                
        return image_regions
        
    @staticmethod
    def subdivide(full_size, Image_Region_size, starting_point):
        """
        Generates a list of tuples, containing the starting points and widths
        of each subdivision
        """
        # Get the offset
        offset = starting_point % Image_Region_size
        starting_points = [ [x + offset, Image_Region_size] for x in range(0, full_size, Image_Region_size)]
    
        # Adjust the size of the last element
        if starting_points[-1][0] > full_size:
            starting_points.pop()
            
        if starting_points[-1][0] + starting_points[-1][1] > full_size:
            starting_points[-1][1] = full_size - starting_points[-1][0]
            
        
        # Insert a starting element
        if starting_points[0][0] != 0:
            new_point = [0, starting_points[0][0]]
            starting_points.insert(0, new_point)
                
        return starting_points
        