#!/usr/bin/env python
# -*- coding: utf-8 -*-
# pycoast, Writing of coastlines, borders and rivers to images in Python
#
# Copyright (C) 2011-2020 PyCoast Developers
#
# 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 <http://www.gnu.org/licenses/>.
"""ContourWriter based on the aggdraw library."""

import logging

import aggdraw
from PIL import Image

from pycoast.cw_base import ContourWriterBase

logger = logging.getLogger(__name__)


class ContourWriterAGG(ContourWriterBase):
    """Adds countours from GSHHS and WDBII to images using the AGG engine for high quality images.

    :Parameters:
        db_root_path : str
            Path to root dir of GSHHS and WDBII shapefiles

    """

    _draw_module = "AGG"
    # This is a flag to make _add_grid aware of which text draw routine
    # from PIL or from aggdraw should be used
    # (unfortunately they are not fully compatible)

    def _get_canvas(self, image):
        """Return AGG image object."""
        return aggdraw.Draw(image)

    def _engine_text_draw(self, draw, x_pos, y_pos, txt, font, **kwargs):
        draw.text((x_pos, y_pos), txt, font)

    def _draw_polygon(self, draw, coordinates, **kwargs):
        """Draw polygon."""
        pen = aggdraw.Pen(kwargs["outline"], kwargs["width"], kwargs["outline_opacity"])

        fill_opacity = kwargs.get("fill_opacity", 255) if kwargs["fill"] else 0

        brush = aggdraw.Brush(kwargs["fill"], fill_opacity)
        draw.polygon(coordinates, pen, brush)

    def _draw_rectangle(self, draw, coordinates, **kwargs):
        """Draw rectangle."""
        pen = aggdraw.Pen(kwargs["outline"], kwargs["width"], kwargs["outline_opacity"])

        fill_opacity = kwargs.get("fill_opacity", 255) if kwargs["fill"] else 0

        brush = aggdraw.Brush(kwargs["fill"], fill_opacity)

        draw.rectangle(coordinates, pen, brush)

    def _draw_ellipse(self, draw, coordinates, **kwargs):
        """Draw ellipse."""
        pen = aggdraw.Pen(kwargs["outline"], kwargs["width"], kwargs["outline_opacity"])

        fill_opacity = kwargs.get("fill_opacity", 255) if kwargs["fill"] else 0

        brush = aggdraw.Brush(kwargs["fill"], fill_opacity)

        draw.ellipse(coordinates, brush, pen)

    def _draw_text_box(
        self,
        draw,
        text_position,
        text,
        font,
        outline,
        box_outline,
        box_opacity,
        **kwargs,
    ):
        """Add a text box at position (x,y)."""
        if box_outline is not None:
            if hasattr(draw, "textsize"):
                text_size = draw.textsize(text, font)
            else:
                left, top, right, bottom = draw.textbbox(text_position, text, font)
                text_size = right - left, top - bottom
            margin = 2
            xUL = text_position[0] - margin
            yUL = text_position[1]
            xLR = xUL + text_size[0] + (2 * margin)
            yLR = yUL + text_size[1]
            box_size = (xUL, yUL, xLR, yLR)

            width = kwargs.get("box_linewidth", 1)
            fill = kwargs.get("box_fill", None)

            self._draw_rectangle(
                draw,
                box_size,
                outline=box_outline,
                width=width,
                outline_opacity=box_opacity,
                fill=fill,
                fill_opacity=box_opacity,
            )

        self._draw_text(draw, text_position, text, font, align="no")

    def _draw_line(self, draw, coordinates, **kwargs):
        """Draw line."""
        pen = aggdraw.Pen(kwargs["outline"], kwargs["width"], kwargs["outline_opacity"])
        draw.line(coordinates, pen)

    def _draw_asterisk(self, draw, pt_size, coordinate, **kwargs):
        """Draw a asterisk sign '*' at the given coordinate."""
        half_ptsize = int(round(pt_size / 2.0))
        x, y = coordinate

        outline = kwargs.get("outline", "white")
        width = kwargs.get("width", 1.0)
        outline_opacity = kwargs.get("outline_opacity", 255)

        # draw '|'
        (x_bm, y_bm) = (x, y - half_ptsize)  # bottom middle point
        (x_tm, y_tm) = (x, y + half_ptsize)  # top middle point
        self._draw_line(
            draw,
            [x_bm, y_bm, x_tm, y_tm],
            outline=outline,
            width=width,
            outline_opacity=outline_opacity,
        )
        # draw '-'
        (x_lm, y_lm) = (x - half_ptsize, y)  # left middle point
        (x_rm, y_rm) = (x + half_ptsize, y)  # right middle point
        self._draw_line(
            draw,
            [x_lm, y_lm, x_rm, y_rm],
            outline=outline,
            width=width,
            outline_opacity=outline_opacity,
        )
        # draw '/'
        (x_bl, y_bl) = (x - half_ptsize, y - half_ptsize)  # bottom left point
        (x_tr, y_tr) = (x + half_ptsize, y + half_ptsize)  # top right point
        self._draw_line(
            draw,
            [x_bl, y_bl, x_tr, y_tr],
            outline=outline,
            width=width,
            outline_opacity=outline_opacity,
        )
        # draw '\'
        (x_tl, y_tl) = (x - half_ptsize, y + half_ptsize)  # top left point
        (x_br, y_br) = (x + half_ptsize, y - half_ptsize)  # bottom right point
        self._draw_line(
            draw,
            [x_tl, y_tl, x_br, y_br],
            outline=outline,
            width=width,
            outline_opacity=outline_opacity,
        )

    def _finalize(self, draw):
        """Flush the AGG image object."""
        draw.flush()

    def add_shapefile_shapes(
        self,
        image,
        area_def,
        filename,
        feature_type=None,
        fill=None,
        fill_opacity=255,
        outline="white",
        width=1,
        outline_opacity=255,
        x_offset=0,
        y_offset=0,
    ):
        """Add shape file shapes from an ESRI shapefile.

        Note: Currently only supports lon-lat formatted coordinates.

        :Parameters:
            image : object
                PIL image object
            area_def : list [proj4_string, area_extent]
              | proj4_string : str
              |     Projection of area as Proj.4 string
              | area_extent : list
              |     Area extent as a list (LL_x, LL_y, UR_x, UR_y)
            filename : str
                Path to ESRI shape file
            feature_type : 'polygon' or 'line',
                only to override the shape type defined in shapefile, optional
            fill : str or (R, G, B), optional
                Polygon fill color
            fill_opacity : int, optional {0; 255}
                Opacity of polygon fill
            outline : str or (R, G, B), optional
                line color
            width : float, optional
                line width
            outline_opacity : int, optional {0; 255}
                Opacity of lines
            x_offset : float, optional
                Pixel offset in x direction
            y_offset : float, optional
                Pixel offset in y direction

        """
        self._add_shapefile_shapes(
            image=image,
            area_def=area_def,
            filename=filename,
            feature_type=feature_type,
            x_offset=x_offset,
            y_offset=y_offset,
            fill=fill,
            fill_opacity=fill_opacity,
            outline=outline,
            width=width,
            outline_opacity=outline_opacity,
        )

    def add_shapefile_shape(
        self,
        image,
        area_def,
        filename,
        shape_id,
        feature_type=None,
        fill=None,
        fill_opacity=255,
        outline="white",
        width=1,
        outline_opacity=255,
        x_offset=0,
        y_offset=0,
    ):
        """Add a single shape file shape from an ESRI shapefile.

        Note: To add all shapes in file use the 'add_shape_file_shapes' routine

        Note: Currently only supports lon-lat formatted coordinates.

        :Parameters:
            image : object
                PIL image object
            area_def : list [proj4_string, area_extent]
              | proj4_string : str
              |     Projection of area as Proj.4 string
              | area_extent : list
              |     Area extent as a list (LL_x, LL_y, UR_x, UR_y)
            filename : str
                Path to ESRI shape file
            shape_id : int
                integer id of shape in shape file {0; ... }
            feature_type : 'polygon' or 'line',
                only to override the shape type defined in shapefile, optional
            fill : str or (R, G, B), optional
                Polygon fill color
            fill_opacity : int, optional {0; 255}
                Opacity of polygon fill
            outline : str or (R, G, B), optional
                line color
            width : float, optional
                line width
            outline_opacity : int, optional {0; 255}
                Opacity of lines
            x_offset : float, optional
                Pixel offset in x direction
            y_offset : float, optional
                Pixel offset in y direction

        """
        self._add_shapefile_shape(
            image=image,
            area_def=area_def,
            filename=filename,
            shape_id=shape_id,
            feature_type=feature_type,
            x_offset=x_offset,
            y_offset=y_offset,
            fill=fill,
            fill_opacity=fill_opacity,
            outline=outline,
            width=width,
            outline_opacity=outline_opacity,
        )

    def add_line(
        self,
        image,
        area_def,
        lonlats,
        fill=None,
        fill_opacity=255,
        outline="white",
        width=1,
        outline_opacity=255,
        x_offset=0,
        y_offset=0,
    ):
        """Add a user defined poly-line from a list of (lon,lat) coordinates.

        :Parameters:
            image : object
                PIL image object
            area_def : list [proj4_string, area_extent]
              | proj4_string : str
              |     Projection of area as Proj.4 string
              | area_extent : list
              |     Area extent as a list (LL_x, LL_y, UR_x, UR_y)
            lonlats : list of lon lat pairs
                e.g. [(10,20),(20,30),...,(20,20)]
            outline : str or (R, G, B), optional
                line color
            width : float, optional
                line width
            outline_opacity : int, optional {0; 255}
                Opacity of lines
            x_offset : float, optional
                Pixel offset in x direction
            y_offset : float, optional
                Pixel offset in y direction

        """
        self._add_line(
            image,
            area_def,
            lonlats,
            x_offset=x_offset,
            y_offset=y_offset,
            fill=fill,
            fill_opacity=fill_opacity,
            outline=outline,
            width=width,
            outline_opacity=outline_opacity,
        )

    def add_polygon(
        self,
        image,
        area_def,
        lonlats,
        fill=None,
        fill_opacity=255,
        outline="white",
        width=1,
        outline_opacity=255,
        x_offset=0,
        y_offset=0,
    ):
        """Add a user defined polygon from a list of (lon,lat) coordinates.

        :Parameters:
            image : object
                PIL image object
            area_def : list [proj4_string, area_extent]
              | proj4_string : str
              |     Projection of area as Proj.4 string
              | area_extent : list
              |     Area extent as a list (LL_x, LL_y, UR_x, UR_y)
            lonlats : list of lon lat pairs
                e.g. [(10,20),(20,30),...,(20,20)]
            fill : str or (R, G, B), optional
                Polygon fill color
            fill_opacity : int, optional {0; 255}
                Opacity of polygon fill
            outline : str or (R, G, B), optional
                line color
            width : float, optional
                line width
            outline_opacity : int, optional {0; 255}
                Opacity of lines
            x_offset : float, optional
                Pixel offset in x direction
            y_offset : float, optional
                Pixel offset in y direction

        """
        self._add_polygon(
            image,
            area_def,
            lonlats,
            x_offset=x_offset,
            y_offset=y_offset,
            fill=fill,
            fill_opacity=fill_opacity,
            outline=outline,
            width=width,
            outline_opacity=outline_opacity,
        )

    def add_grid(
        self,
        image,
        area_def,
        Dlonlat,
        dlonlat,
        font=None,
        write_text=True,
        fill=None,
        fill_opacity=255,
        outline="white",
        width=1.0,
        outline_opacity=255,
        minor_outline="white",
        minor_width=0.5,
        minor_outline_opacity=255,
        minor_is_tick=True,
        lon_placement="tb",
        lat_placement="lr",
    ):
        """Add a lon-lat grid to a PIL image object.

        :Parameters:
            image : object
                PIL image object
            proj4_string : str
                Projection of area as Proj.4 string
            Dlonlat: (float, float)
                Major grid line separation
            dlonlat: (float, float)
                Minor grid line separation
            font: Aggdraw Font object, optional
                Font for major line markings
            write_text : boolean, optional
                Determine if line markings are enabled
            fill_opacity : int, optional {0; 255}
                Opacity of text
            outline : str or (R, G, B), optional
                Major line color
            width : float, optional
                Major line width
            outline_opacity : int, optional {0; 255}
                Opacity of major lines
            minor_outline : str or (R, G, B), optional
                Minor line/tick color
            minor_width : float, optional
                Minor line width
            minor_outline_opacity : int, optional {0; 255}
                Opacity of minor lines/ticks
            minor_is_tick : boolean, optional
                Use tick minor line style (True) or full minor line style (False)

        """
        Dlon, Dlat = Dlonlat
        dlon, dlat = dlonlat
        self._add_grid(
            image,
            area_def,
            Dlon,
            Dlat,
            dlon,
            dlat,
            font=font,
            write_text=write_text,
            fill=fill,
            fill_opacity=fill_opacity,
            outline=outline,
            width=width,
            outline_opacity=outline_opacity,
            minor_outline=minor_outline,
            minor_width=minor_width,
            minor_outline_opacity=minor_outline_opacity,
            minor_is_tick=minor_is_tick,
            lon_placement=lon_placement,
            lat_placement=lat_placement,
        )

    def add_grid_to_file(
        self,
        filename,
        area_def,
        Dlonlat,
        dlonlat,
        font=None,
        write_text=True,
        fill=None,
        fill_opacity=255,
        outline="white",
        width=1,
        outline_opacity=255,
        minor_outline="white",
        minor_width=0.5,
        minor_outline_opacity=255,
        minor_is_tick=True,
        lon_placement="tb",
        lat_placement="lr",
    ):
        """Add a lon-lat grid to an image. The resulting image is in 'RGBA' mode.

        :Parameters:
            image : object
                PIL image object
            proj4_string : str
                Projection of area as Proj.4 string
            Dlonlat: (float, float)
                Major grid line separation
            dlonlat: (float, float)
                Minor grid line separation
            font: Aggdraw Font object, optional
                Font for major line markings
            write_text : boolean, optional
                Determine if line markings are enabled
            fill_opacity : int, optional {0; 255}
                Opacity of text
            outline : str or (R, G, B), optional
                Major line color
            width : float, optional
                Major line width
            outline_opacity : int, optional {0; 255}
                Opacity of major lines
            minor_outline : str or (R, G, B), optional
                Minor line/tick color
            minor_width : float, optional
                Minor line width
            minor_outline_opacity : int, optional {0; 255}
                Opacity of minor lines/ticks
            minor_is_tick : boolean, optional
                Use tick minor line style (True) or full minor line style (False)

        """
        image = Image.open(filename)
        image = image.convert("RGBA")
        self.add_grid(
            image,
            area_def,
            Dlonlat,
            dlonlat,
            font=font,
            write_text=write_text,
            fill=fill,
            fill_opacity=fill_opacity,
            outline=outline,
            width=width,
            outline_opacity=outline_opacity,
            minor_outline=minor_outline,
            minor_width=minor_width,
            minor_outline_opacity=minor_outline_opacity,
            minor_is_tick=minor_is_tick,
            lon_placement=lon_placement,
            lat_placement=lat_placement,
        )
        image.save(filename)

    def add_coastlines(
        self,
        image,
        area_def,
        resolution="c",
        level=1,
        fill=None,
        fill_opacity=255,
        outline="white",
        width=1,
        outline_opacity=255,
        x_offset=0,
        y_offset=0,
    ):
        """Add coastlines to a PIL image object.

        :Parameters:
            image : object
                PIL image object
            proj4_string : str
                Projection of area as Proj.4 string
            area_extent : list
                Area extent as a list (LL_x, LL_y, UR_x, UR_y)
            resolution : str, optional {'c', 'l', 'i', 'h', 'f'}
                Dataset resolution to use
            level : int, optional {1, 2, 3, 4}
                Detail level of dataset
            fill : str or (R, G, B), optional
                Land color
            fill_opacity : int, optional {0; 255}
                Opacity of land color
            outline : str or (R, G, B), optional
                Coastline color
            width : float, optional
                Width of coastline
            outline_opacity : int, optional {0; 255}
                Opacity of coastline color
            x_offset : float, optional
                Pixel offset in x direction
            y_offset : float, optional
                Pixel offset in y direction

        """
        self._add_feature(
            image,
            area_def,
            "polygon",
            "GSHHS",
            resolution=resolution,
            level=level,
            fill=fill,
            fill_opacity=fill_opacity,
            outline=outline,
            width=width,
            outline_opacity=outline_opacity,
            x_offset=x_offset,
            y_offset=y_offset,
        )

    def add_coastlines_to_file(
        self,
        filename,
        area_def,
        resolution="c",
        level=1,
        fill=None,
        fill_opacity=255,
        outline="white",
        width=1,
        outline_opacity=255,
        x_offset=0,
        y_offset=0,
    ):
        """Add coastlines to an image file. The resulting image is in 'RGBA' mode.

        :Parameters:
            filename : str
                Image file
            proj4_string : str
                Projection of area as Proj.4 string
            area_extent : list
                Area extent as a list (LL_x, LL_y, UR_x, UR_y)
            resolution : str, optional {'c', 'l', 'i', 'h', 'f'}
                Dataset resolution to use
            level : int, optional {1, 2, 3, 4}
                Detail level of dataset
            fill : str or (R, G, B), optional
                Land color
            fill_opacity : int, optional {0; 255}
                Opacity of land color
            outline : str or (R, G, B), optional
                Coastline color
            width : float, optional
                Width of coastline
            outline_opacity : int, optional {0; 255}
                Opacity of coastline color
            x_offset : float, optional
                Pixel offset in x direction
            y_offset : float, optional
                Pixel offset in y direction

        """
        image = Image.open(filename)
        image = image.convert("RGBA")
        self.add_coastlines(
            image,
            area_def,
            resolution=resolution,
            level=level,
            fill=fill,
            fill_opacity=fill_opacity,
            outline=outline,
            width=width,
            outline_opacity=outline_opacity,
            x_offset=x_offset,
            y_offset=y_offset,
        )
        image.save(filename)

    def add_borders(
        self,
        image,
        area_def,
        resolution="c",
        level=1,
        outline="white",
        width=1,
        outline_opacity=255,
        x_offset=0,
        y_offset=0,
    ):
        """Add borders to a PIL image object.

        :Parameters:
            image : object
                PIL image object
            proj4_string : str
                Projection of area as Proj.4 string
            area_extent : list
                Area extent as a list (LL_x, LL_y, UR_x, UR_y)
            resolution : str, optional {'c', 'l', 'i', 'h', 'f'}
                Dataset resolution to use
            level : int, optional {1, 2, 3}
                Detail level of dataset
            outline : str or (R, G, B), optional
                Border color
            width : float, optional
                Width of coastline
            outline_opacity : int, optional {0; 255}
                Opacity of coastline color
            x_offset : float, optional
                Pixel offset in x direction
            y_offset : float, optional
                Pixel offset in y direction

        """
        self._add_feature(
            image,
            area_def,
            "line",
            "WDBII",
            tag="border",
            resolution=resolution,
            level=level,
            outline=outline,
            width=width,
            outline_opacity=outline_opacity,
            x_offset=x_offset,
            y_offset=y_offset,
        )

    def add_borders_to_file(
        self,
        filename,
        area_def,
        resolution="c",
        level=1,
        outline="white",
        width=1,
        outline_opacity=255,
        x_offset=0,
        y_offset=0,
    ):
        """Add borders to an image file. The resulting image is in 'RGBA' mode.

        :Parameters:
            image : object
                Image file
            proj4_string : str
                Projection of area as Proj.4 string
            area_extent : list
                Area extent as a list (LL_x, LL_y, UR_x, UR_y)
            resolution : str, optional {'c', 'l', 'i', 'h', 'f'}
                Dataset resolution to use
            level : int, optional {1, 2, 3}
                Detail level of dataset
            outline : str or (R, G, B), optional
                Border color
            width : float, optional
                Width of coastline
            outline_opacity : int, optional {0; 255}
                Opacity of coastline color
            x_offset : float, optional
                Pixel offset in x direction
            y_offset : float, optional
                Pixel offset in y direction

        """
        image = Image.open(filename)
        image = image.convert("RGBA")
        self.add_borders(
            image,
            area_def,
            resolution=resolution,
            level=level,
            outline=outline,
            width=width,
            outline_opacity=outline_opacity,
            x_offset=x_offset,
            y_offset=y_offset,
        )
        image.save(filename)

    def add_rivers(
        self,
        image,
        area_def,
        resolution="c",
        level=1,
        outline="white",
        width=1,
        outline_opacity=255,
        x_offset=0,
        y_offset=0,
    ):
        """Add rivers to a PIL image object.

        :Parameters:
            image : object
                PIL image object
            proj4_string : str
                Projection of area as Proj.4 string
            area_extent : list
                Area extent as a list (LL_x, LL_y, UR_x, UR_y)
            resolution : str, optional {'c', 'l', 'i', 'h', 'f'}
                Dataset resolution to use
            level : int, optional {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}
                Detail level of dataset
            outline : str or (R, G, B), optional
                River color
            width : float, optional
                Width of coastline
            outline_opacity : int, optional {0; 255}
                Opacity of coastline color
            x_offset : float, optional
                Pixel offset in x direction
            y_offset : float, optional
                Pixel offset in y direction

        """
        self._add_feature(
            image,
            area_def,
            "line",
            "WDBII",
            tag="river",
            zero_pad=True,
            resolution=resolution,
            level=level,
            outline=outline,
            width=width,
            outline_opacity=outline_opacity,
            x_offset=x_offset,
            y_offset=y_offset,
        )

    def add_rivers_to_file(
        self,
        filename,
        area_def,
        resolution="c",
        level=1,
        outline="white",
        width=1,
        outline_opacity=255,
        x_offset=0,
        y_offset=0,
    ):
        """Add rivers to an image file. The resulting image is in 'RGBA' mode.

        :Parameters:
            image : object
                Image file
            proj4_string : str
                Projection of area as Proj.4 string
            area_extent : list
                Area extent as a list (LL_x, LL_y, UR_x, UR_y)
            resolution : str, optional {'c', 'l', 'i', 'h', 'f'}
                Dataset resolution to use
            level : int, optional {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}
                Detail level of dataset
            outline : str or (R, G, B), optional
                River color
            width : float, optional
                Width of coastline
            outline_opacity : int, optional {0; 255}
                Opacity of coastline color
            x_offset : float, optional
                Pixel offset in x direction
            y_offset : float, optional
                Pixel offset in y direction

        """
        image = Image.open(filename)
        image = image.convert("RGBA")
        self.add_rivers(
            image,
            area_def,
            resolution=resolution,
            level=level,
            outline=outline,
            width=width,
            outline_opacity=outline_opacity,
            x_offset=x_offset,
            y_offset=y_offset,
        )
        image.save(filename)

    def _get_font(self, outline, font_file, font_size):
        """Return a font."""
        return aggdraw.Font(outline, font_file, size=font_size)
