# encoding: utf8
import logging
import os
import pathlib
import pint
import datetime
import arrow
import geopandas
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
import matplotlib.patches as mpatches
import matplotlib.cm as mcolormap
from iso3166 import countries
from staticmap import StaticMap, IconMarker
from resif_delivery_stats_plotter.services.clients.statistics import ServiceClientStatistics as service_stats
from resif_delivery_stats_plotter.services.clients.availabilty import ServiceClientAvailability as service_availability
from . import ServicePlotterAbstract

logger = logging.getLogger(__name__)


class ServicePlotterPNG(ServicePlotterAbstract):
    """
    Static PNG plotter
    """

    DEFAULT_PX_WIDTH = 800
    DEFAULT_PX_HEIGHT = 600
    MINIMAL_PX_WIDTH = 380

    @classmethod
    def _footer_bar(cls, fig, text=None, fontsize=10, pad=5, xpos=20, ypos=7.5, rect_kw = {"facecolor":"grey", "edgecolor":None}, text_kw = {"color":"w"}):
        """
        Adds a footer bar to matplotlib plots
        :param fig: The matplotlib figure
        :param text: The text of the footer bar
        :param fontsize: The fontsize (default=10)
        :param pad: The padding of the footer bar (default=5)
        :param xpos: The X position of the footer bar (default=20)
        :param ypos: The Y position of the footer bar (default=7.5)
        :param rect_kw: The other parameters of the footer bar
        :param text_kw: The other parameters of the footer text
        """
        today = datetime.datetime.utcnow()
        text = text or "Generated by Résif on %s" % today.strftime("%Y-%m-%d at %Hh%M UTC")
        w, h = fig.get_size_inches()
        height = ((fontsize+2*pad)/fig.dpi)/h
        rect = plt.Rectangle((0, 0), 1, height, transform=fig.transFigure, clip_on=False, **rect_kw)
        fig.axes[0].add_patch(rect)
        fig.text(xpos/fig.dpi/w, ypos/fig.dpi/h, text, fontsize=fontsize, **text_kw)
        fig.subplots_adjust(bottom=fig.subplotpars.bottom+height)

    @classmethod
    def _colors(cls, colormap=None, number=None, default_colormap='tab20'):
        """
        Gets a list of colors from the selected matplotlib colormap
        :param colormap: The colormap
        :param number: The number of colors to return
        :param default_colormap: The default colormap
        :return: A list of colors
        :rtype: list
        """
        logger.debug("ServicePlotterPNG._colors(%s, %s, %s)" % (colormap, number, default_colormap))

        colormap = colormap or default_colormap
        colors = mcolormap.get_cmap(colormap, number).colors

        if number:
            logger.debug("Using %i colors from '%s' colormap: %s" % (number, colormap, colors))
        else:
            logger.debug("Using '%s' colormap" % colormap)

        return colors

    @classmethod
    def plot_network_availability(cls, network, year=None, width=None, height=None, vertical=False, instrument=None, dpi=100):
        """
        Plots the annual availability of each station of a network
        :param network: The network object
        :param year: The year
        :param width: The width of the output image
        :param height: The height of the output image
        :param vertical: Enables the vertical layout (default=false)
        :param instrument: The instrument code to filter
        :param dpi: The DPI (default=100)
        :return: The plot output file path
        :rtype: str
        """
        logger.debug("ServicePlotterPNG.plot_network_availability(%s, %s, %s, %s, %s, %s)" % (network, year, width, height, vertical, instrument))
        year = year or datetime.datetime.now().year
        logger.info("Plotting availability of network %s in %i" % (network.code, year))

        # Get plot data
        plot_data = {}
        for station in network.stations:
            for channel in station.representative_channels(year):
                station_data = {}
                qualities = channel.availability(year)
                if qualities:
                    for quality, timespans in qualities.items():
                        station_data[quality] = []
                        for timespan in timespans:
                            station_data[quality].append((arrow.get(timespan[0]).datetime, arrow.get(timespan[1]).datetime))

                if station_data:
                    plot_data["%s.%s.%s" % (network.code, station.code, channel.location_code)] = station_data

        nb_lines = len(plot_data)
        logger.debug("Got %i channels to plot" % nb_lines)

        # Calculate figure dimensions
        line_px = 15
        margin_px_top = 20
        margin_px_right = 10
        if vertical:
            margin_px_left = 100
            margin_px_bottom = 180
            width_px = width or max(cls.MINIMAL_PX_WIDTH, (line_px * nb_lines + margin_px_left + margin_px_right))
            height_px = height or cls.DEFAULT_PX_HEIGHT
        else:
            margin_px_left = 180
            margin_px_bottom = 100
            width_px = width or cls.DEFAULT_PX_WIDTH
            height_px = height or (line_px * nb_lines + margin_px_top + margin_px_bottom)

        width_in = width_px / dpi
        height_in = height_px / dpi

        margin_in_left = margin_px_left / dpi
        margin_in_right = margin_px_right / dpi
        margin_in_top = margin_px_top / dpi
        margin_in_bottom = margin_px_bottom / dpi

        x_plot = margin_in_left / width_in
        y_plot = margin_in_bottom / height_in
        w_plot = (width_in - margin_in_left - margin_in_right) / width_in
        h_plot = (height_in - margin_in_bottom - margin_in_top) / height_in

        # Initialize image
        logger.debug("Figure size (Pixels): w=%s, h=%s" % (width_px, height_px))
        fig = plt.figure(figsize=(width_in, height_in))
        ax = plt.axes([x_plot, y_plot, w_plot, h_plot])

        # Initialize colors
        colors = {}
        handles = []

        color = ax._get_lines.get_next_color()
        for quality in (service_availability.QUALITY_Q_CONTROLLED, service_availability.QUALITY_M_MODIFIED):
            colors[quality] = color
        handles.append(mpatches.Patch(color=color, label='Valid'))

        color = ax._get_lines.get_next_color()
        for quality in (service_availability.QUALITY_R_RAW, service_availability.QUALITY_D_INDETERMINATE):
            colors[quality] = color
        handles.append(mpatches.Patch(color=color, label='Raw'))

        # Plot data
        if vertical:
            for trace_id in sorted(plot_data.keys()):
                for quality in plot_data[trace_id]:
                    color = colors.get(quality)
                    trace = (trace_id, trace_id)
                    for segment in plot_data[trace_id][quality]:
                        ax.plot(trace, segment, color=color)
        else:
            for trace_id in sorted(plot_data.keys()):
                for quality in plot_data[trace_id]:
                    color = colors.get(quality)
                    trace = (trace_id, trace_id)
                    for segment in plot_data[trace_id][quality]:
                        ax.plot(segment, trace, color=color)

        # Configure axis
        ax.xaxis.set_tick_params(rotation=90)
        if vertical:
            ax.yaxis.set_major_formatter(mdates.DateFormatter("%Y-%m"))
            ax.yaxis.set_major_locator(mdates.MonthLocator())
            ax.yaxis.set_minor_locator(mdates.WeekdayLocator())
            ax.grid(axis='y')
            ax.invert_xaxis()
        else:
            ax.xaxis.set_major_formatter(mdates.DateFormatter("%Y-%m"))
            ax.xaxis.set_major_locator(mdates.MonthLocator())
            ax.xaxis.set_minor_locator(mdates.WeekdayLocator())
            ax.grid(axis='x')
            ax.invert_yaxis()

        # Add title
        #ax.set_title("%s %i" % (network.code, year), loc='left', fontsize=10)

        # Add legend
        ax.legend(handles=handles, bbox_to_anchor=(1, 1), loc='lower right', borderaxespad=0, ncol=2, frameon=False)

        # Add footer
        cls._footer_bar(fig)

        # Save file
        output_filename = 'plot_network_availability.png'
        output_filepath = os.path.join(cls.output_dir(network, year), output_filename)
        fig.savefig(output_filepath, transparent=True)
        logger.info("Plot saved as %s" % output_filepath)

        return output_filename

    @classmethod
    def plot_data_send_yearly(cls, network, year=None, width=None, height=None, vertical=True, start_year=None, unit=None, colormap=None):
        """
        Plots the amount of data send yearly for a network
        :param network: The network object
        :param year: The year
        :param width: The width of the output image
        :param height: The height of the output image
        :param vertical: Enables the vertical layout (default=false)
        :param start_year: The starting year
        :param unit: The unit in which the values are converted
        :param colormap: The colormap to use
        :return: The plot output file path
        :rtype: str
        """
        logger.debug("ServicePlotterPNG.plot_data_send_yearly(%s, %s, %s, %s, %s, %s, %s, %s)" % (network, year, width, height, vertical, start_year, unit, colormap))
        year = year or datetime.datetime.now().year
        logger.info("Plotting data send yearly by network %s until %i" % (network.code, year))

        ureg = pint.UnitRegistry()
        ureg.setup_matplotlib(True)

        # Calculate starting year
        if start_year:
            start_year = max(network.year_start, start_year)
        else:
            start_year = network.year_start
        logger.debug('Starting year: %s' % start_year)

        # Get plot data
        plot_data_years = []
        plot_data_dataselect = []
        plot_data_seedlink = []
        has_data = False
        for y in range(start_year, year+1):
            send_dataselect = network.data_send_dataselect(year=y)
            send_seedlink = network.data_send_seedlink(year=y)
            if has_data or send_dataselect or send_seedlink:
                has_data = True
                plot_data_years.append(arrow.get(y, 1, 1).datetime)
                plot_data_dataselect.append((send_dataselect or 0) * ureg.byte)
                plot_data_seedlink.append((send_seedlink or 0) * ureg.byte)

        # Build image
        if plot_data_years:
            logger.debug("Plotted years: %s" % len(plot_data_years))
            bar_width = 0.75 * ureg.year
            fig, ax = plt.subplots()

            # Calculate dimensions of image
            bar_px = 50
            margin_px_top = 20
            margin_px_right = 10
            margin_px_left_bottom = 100
            if vertical:
                width_px = width or max(cls.MINIMAL_PX_WIDTH, (bar_px * len(plot_data_years) + margin_px_left_bottom + margin_px_right))
                height_px = height or cls.DEFAULT_PX_HEIGHT
            else:
                width_px = width or cls.DEFAULT_PX_WIDTH
                height_px = height or (bar_px * len(plot_data_years) + margin_px_top + margin_px_left_bottom)

            logger.debug("Figure size (Pixels): w=%s, h=%s" % (width_px, height_px))
            fig.set_size_inches(width_px/fig.dpi, height_px/fig.dpi)

            # Initialize colors
            colors = cls._colors(colormap, 2)

            # Configure axis and plot data
            ax.xaxis.set_tick_params(rotation=90)
            if vertical:
                ax.yaxis.set_label_text('Data send (%s) for %s' % (unit or 'Bytes', network.code))
                if unit:
                    ax.yaxis.set_units(ureg.Unit(unit))
                ax.xaxis.set_major_formatter(mdates.DateFormatter("%Y"))
                ax.xaxis.set_major_locator(mdates.YearLocator())
                ax.bar(plot_data_years, plot_data_seedlink, bar_width, label='Seedlink', color=colors[0])
                ax.bar(plot_data_years, plot_data_dataselect, bar_width, bottom=plot_data_seedlink, label='Dataselect', color=colors[1])
            else:
                ax.xaxis.set_label_text('Data send (%s) for %s' % (unit or 'Bytes', network.code))
                if unit:
                    ax.xaxis.set_units(ureg.Unit(unit))
                ax.yaxis.set_major_formatter(mdates.DateFormatter("%Y"))
                ax.yaxis.set_major_locator(mdates.YearLocator())
                ax.barh(plot_data_years, plot_data_seedlink, bar_width, label='Seedlink', color=colors[0])
                ax.barh(plot_data_years, plot_data_dataselect, bar_width, left=plot_data_seedlink, label='Dataselect', color=colors[1])

            # Add title
            #ax.set_title(network.code, loc='left')

            # Add legend
            ax.legend(bbox_to_anchor=(1, 1), loc='lower right', borderaxespad=0, ncol=2, frameon=False)

            # Crop image
            plt.tight_layout()

            # Add footer
            cls._footer_bar(fig)

            # Save file
            output_filename = 'plot_data_send_yearly.png'
            output_filepath = os.path.join(cls.output_dir(network, year), output_filename)
            fig.savefig(output_filepath, transparent=True)
            logger.info("Plot saved as %s" % output_filepath)

            return output_filename

    @classmethod
    def plot_data_send_monthly(cls, network, year=None, width=None, height=None, unit=None, colormap=None):
        """
        Plots the amount of data send monthly for a network
        :param network: The network object
        :param year: The year
        :param width: The width of the output image
        :param height: The height of the output image
        :param unit: The unit in which the values are converted
        :param colormap: The colormap to use
        :return: The plot output file path
        :rtype: str
        """
        logger.debug("ServicePlotterPNG.plot_data_send_monthly(%s, %s, %s, %s, %s, %s)" % (network, year, width, height, unit, colormap))
        year = year or datetime.datetime.now().year
        logger.info("Plotting data send monthly by network %s in %i" % (network.code, year))

        ureg = pint.UnitRegistry()
        ureg.setup_matplotlib(True)

        # Get plot data
        plot_data_months = []
        plot_data_dataselect = []
        plot_data_seedlink = []
        for month in range(1, 13):
            send_dataselect = network.data_send_dataselect(year=year, month=month) or 0
            send_seedlink = network.data_send_seedlink(year=year, month=month) or 0
            plot_data_dataselect.append(send_dataselect * ureg.byte)
            plot_data_seedlink.append(send_seedlink * ureg.byte)
            plot_data_months.append(arrow.get(year, month, 1).datetime)

        # Build image
        bar_width = 0.75 * ureg.month
        fig, ax = plt.subplots()

        # Calculate dimensions of image
        height = height or cls.DEFAULT_PX_HEIGHT
        width = width or cls.DEFAULT_PX_WIDTH
        fig.set_size_inches(width/fig.dpi, height/fig.dpi)

        # Initialize colors
        colors = cls._colors(colormap, 2)

        # Configure axis and plot data
        ax.yaxis.set_label_text('Data send (%s) for %s in %s' % (unit or 'Bytes', network.code, year))
        if unit:
            ax.yaxis.set_units(ureg.Unit(unit))
        ax.xaxis.set_tick_params(rotation=90)
        ax.xaxis.set_major_formatter(mdates.DateFormatter("%b"))
        ax.xaxis.set_major_locator(mdates.MonthLocator())
        ax.bar(plot_data_months, plot_data_seedlink, bar_width, label='Seedlink', color=colors[0])
        ax.bar(plot_data_months, plot_data_dataselect, bar_width, bottom=plot_data_seedlink, label='Dataselect', color=colors[1])

        # Add title
        #ax.set_title("%s %i" % (network.code, year), loc='left')

        # Add legend
        ax.legend(bbox_to_anchor=(1, 1), loc='lower right', borderaxespad=0, ncol=2, frameon=False)

        # Crop image
        plt.tight_layout()

        # Add footer
        cls._footer_bar(fig)

        # Save file
        output_filename = 'plot_data_send_monthly.png'
        output_filepath = os.path.join(cls.output_dir(network, year), output_filename)
        fig.savefig(output_filepath, transparent=True)
        logger.info("Plot saved as %s" % output_filepath)

        return output_filename

    @classmethod
    def plot_data_send_by_other_networks(cls, network, year=None, width=None, height=None, unit=None):
        """
        Plots a pie chart with the amount of data send yearly for a network against the other networks
        :param network: The network object
        :param year: The year
        :param width: The width of the output image
        :param height: The height of the output image
        :param unit: The unit in which the values are converted
        :return: The plot output file path
        :rtype: str
        """
        logger.debug("ServicePlotterPNG.plot_data_send_by_other_networks(%s, %s, %s, %s, %s)" % (network, year, width, height, unit))
        year = year or datetime.datetime.now().year
        logger.info("Plotting ratio of data send for network %s against all networks in %i" % (network.code, year))

        # Get plot data
        send_network = network.data_send(year=year)
        send_all = service_stats.data_send(year=year)
        plot_data_values = [send_network, send_all-send_network]
        plot_data_labels = [network.code, 'Other']
        plot_data_explode = [0.1, 0]

        # Build image
        fig, ax = plt.subplots()

        # Calculate dimensions of image
        height = height or cls.DEFAULT_PX_HEIGHT
        width = width or cls.DEFAULT_PX_WIDTH
        fig.set_size_inches(width/fig.dpi, height/fig.dpi)

        # Configure axis and plot data
        ax.pie(plot_data_values, labels=plot_data_labels, explode=plot_data_explode, startangle=90)
        ax.axis('equal')

        # Crop image
        plt.tight_layout()

        # Add footer
        cls._footer_bar(fig)

        # Save file
        output_filename = 'plot_data_send_by_other_networks.png'
        output_filepath = os.path.join(cls.output_dir(network, year), output_filename)
        fig.savefig(output_filepath, transparent=True)
        logger.info("Plot saved as %s" % output_filepath)

        return output_filename

    @classmethod
    def plot_data_stored_yearly(cls, network, year=None, width=None, height=None, vertical=True, start_year=None, unit=None, colormap=None):
        """
        Plots the amount of data stored yearly for a network
        :param network: The network object
        :param year: The year
        :param width: The width of the output image
        :param height: The height of the output image
        :param vertical: Enables the vertical layout (default=false)
        :param start_year: The starting year
        :param unit: The unit in which the values are converted
        :param colormap: The colormap to use
        :return: The plot output file path
        :rtype: str
        """
        logger.debug("ServicePlotterPNG.plot_data_stored_yearly(%s, %s, %s, %s, %s, %s, %s, %s)" % (network, year, width, height, vertical, start_year, unit, colormap))
        year = year or datetime.datetime.now().year
        logger.info("Plotting data stored yearly by network %s until %i" % (network.code, year))

        ureg = pint.UnitRegistry()
        ureg.setup_matplotlib(True)

        # Calculate starting year
        if start_year:
            start_year = max(network.year_start, start_year)
        else:
            start_year = network.year_start
        logger.debug('Starting year: %s' % start_year)

        # Get plot data
        plot_data_years = []
        plot_data_buffer = []
        plot_data_validated = []
        has_data = False
        for y in range(start_year, year+1):
            stored_buffer = network.data_stored_buffer(year=y)
            stored_validated = network.data_stored_validated(year=y)
            if has_data or stored_buffer or stored_validated:
                has_data = True
                plot_data_years.append(arrow.get(y, 1, 1).datetime)
                plot_data_buffer.append((stored_buffer or 0) * ureg.byte)
                plot_data_validated.append((stored_validated or 0) * ureg.byte)

        # Build image
        if plot_data_years:
            logger.debug("Plotted years: %s" % len(plot_data_years))
            bar_width = 0.75 * ureg.year
            fig, ax = plt.subplots()

            # Calculate dimensions of image
            bar_px = 50
            margin_px_top = 20
            margin_px_right = 10
            margin_px_left_bottom = 100
            if vertical:
                width_px = width or max(cls.MINIMAL_PX_WIDTH, (bar_px * len(plot_data_years) + margin_px_left_bottom + margin_px_right))
                height_px = height or cls.DEFAULT_PX_HEIGHT
            else:
                width_px = width or cls.DEFAULT_PX_WIDTH
                height_px = height or (bar_px * len(plot_data_years) + margin_px_top + margin_px_left_bottom)

            logger.debug("Figure size (Pixels): w=%s, h=%s" % (width_px, height_px))
            fig.set_size_inches(width_px/fig.dpi, height_px/fig.dpi)

            # Initialize colors
            colors = cls._colors(colormap, 2)

            # Configure axis and plot data
            ax.xaxis.set_tick_params(rotation=90)
            if vertical:
                ax.yaxis.set_label_text('Data stored (%s) for %s' % (unit or 'Bytes', network.code))
                if unit:
                    ax.yaxis.set_units(ureg.Unit(unit))
                ax.xaxis.set_major_formatter(mdates.DateFormatter("%Y"))
                ax.xaxis.set_major_locator(mdates.YearLocator())
                ax.bar(plot_data_years, plot_data_validated, bar_width, label='Validated', color=colors[0])
                ax.bar(plot_data_years, plot_data_buffer, bar_width, bottom=plot_data_validated, label='Non-validated', color=colors[1])
            else:
                ax.xaxis.set_label_text('Data stored (%s) for %s' % (unit or 'Bytes', network.code))
                if unit:
                    ax.xaxis.set_units(ureg.Unit(unit))
                ax.yaxis.set_major_formatter(mdates.DateFormatter("%Y"))
                ax.yaxis.set_major_locator(mdates.YearLocator())
                ax.barh(plot_data_years, plot_data_validated, bar_width, label='Validated', color=colors[0])
                ax.barh(plot_data_years, plot_data_buffer, bar_width, left=plot_data_validated, label='Non-validated', color=colors[1])

            # Add title
            #ax.set_title(network.code, loc='left')

            # Add legend
            ax.legend(bbox_to_anchor=(1, 1), loc='lower right', borderaxespad=0, ncol=2, frameon=False)

            # Crop image
            plt.tight_layout()

            # Add footer
            cls._footer_bar(fig)

            # Save file
            output_filename = 'plot_data_stored_yearly.png'
            output_filepath = os.path.join(cls.output_dir(network, year), output_filename)
            fig.savefig(output_filepath, transparent=True)
            logger.info("Plot saved as %s" % output_filepath)

            return output_filename

    @classmethod
    def plot_requests_yearly(cls, network, year=None, width=None, height=None, vertical=True, start_year=None, colormap=None):
        """
        Plots the number of requests querying yearly a network
        :param network: The network object
        :param year: The year
        :param width: The width of the output image
        :param height: The height of the output image
        :param vertical: Enables the vertical layout (default=false)
        :param start_year: The starting year
        :param colormap: The colormap to use
        :return: The plot output file path
        :rtype: str
        """
        logger.debug("ServicePlotterPNG.plot_requests_yearly(%s, %s, %s, %s, %s)" % (network, year, width, height, colormap))
        year = year or datetime.datetime.now().year
        logger.info("Plotting requests yearly by network %s until %i" % (network.code, year))

        ureg = pint.UnitRegistry()
        ureg.setup_matplotlib(True)

        # Calculate starting year
        if start_year:
            start_year = max(network.year_start, start_year)
        else:
            start_year = network.year_start
        logger.debug('Starting year: %s' % start_year)

        # Get plot data
        plot_data_years = []
        plot_data_dataselect = []
        plot_data_station = []
        has_data = False
        for y in range(start_year, year+1):
            requests_dataselect = network.requests_dataselect(year=y)
            requests_station = network.requests_station(year=y)
            if has_data or requests_dataselect or requests_station:
                has_data = True
                plot_data_years.append(arrow.get(y, 1, 1).datetime)
                plot_data_dataselect.append(requests_dataselect or 0)
                plot_data_station.append(requests_station or 0)

        # Build image
        if plot_data_years:
            logger.debug("Plotted years: %s" % len(plot_data_years))
            bar_width = 0.75 * ureg.year
            fig, ax = plt.subplots()

            # Calculate dimensions of image
            bar_px = 50
            margin_px_top = 20
            margin_px_right = 10
            margin_px_left_bottom = 100
            if vertical:
                width_px = width or max(cls.MINIMAL_PX_WIDTH, (bar_px * len(plot_data_years) + margin_px_left_bottom + margin_px_right))
                height_px = height or cls.DEFAULT_PX_HEIGHT
            else:
                width_px = width or cls.DEFAULT_PX_WIDTH
                height_px = height or (bar_px * len(plot_data_years) + margin_px_top + margin_px_left_bottom)

            logger.debug("Figure size (Pixels): w=%s, h=%s" % (width_px, height_px))
            fig.set_size_inches(width_px/fig.dpi, height_px/fig.dpi)

            # Initialize colors
            colors = cls._colors(colormap, 2)

            # Configure axis and plot data
            ax.xaxis.set_tick_params(rotation=90)
            if vertical:
                ax.yaxis.set_label_text('Requests for %s' % network.code)
                ax.xaxis.set_major_formatter(mdates.DateFormatter("%Y"))
                ax.xaxis.set_major_locator(mdates.YearLocator())
                ax.bar(plot_data_years, plot_data_station, bar_width, label='Station', color=colors[0])
                ax.bar(plot_data_years, plot_data_dataselect, bar_width, bottom=plot_data_station, label='Dataselect', color=colors[1])
            else:
                ax.xaxis.set_label_text('Requests for %s' % network.code)
                ax.yaxis.set_major_formatter(mdates.DateFormatter("%Y"))
                ax.yaxis.set_major_locator(mdates.YearLocator())
                ax.barh(plot_data_years, plot_data_station, bar_width, label='Station', color=colors[0])
                ax.barh(plot_data_years, plot_data_dataselect, bar_width, left=plot_data_station, label='Dataselect', color=colors[1])

            # Add title
            #ax.set_title(network.code, loc='left')

            # Add legend
            ax.legend(bbox_to_anchor=(1, 1), loc='lower right', borderaxespad=0, ncol=2, frameon=False)

            # Crop image
            plt.tight_layout()

            # Add footer
            cls._footer_bar(fig)

            # Save file
            output_filename = 'plot_requests_yearly.png'
            output_filepath = os.path.join(cls.output_dir(network, year), output_filename)
            fig.savefig(output_filepath, transparent=True)
            logger.info("Plot saved as %s" % output_filepath)

            return output_filename

    @classmethod
    def plot_requests_monthly(cls, network, year=None, width=None, height=None, colormap=None):
        """
        Plots the number of requests querying monthly a network
        :param network: The network object
        :param year: The year
        :param width: The width of the output image
        :param height: The height of the output image
        :param colormap: The colormap to use
        :return: The plot output file path
        :rtype: str
        """
        logger.debug("ServicePlotterPNG.plot_requests_monthly(%s, %s, %s, %s, %s)" % (network, year, width, height, colormap))
        year = year or datetime.datetime.now().year
        logger.info("Plotting requests monthly by network %s in %i" % (network.code, year))

        ureg = pint.UnitRegistry()
        ureg.setup_matplotlib(True)

        # Get plot data
        plot_data_months = []
        plot_data_dataselect = []
        plot_data_station = []
        for month in range(1, 13):
            requests_dataselect = network.requests_dataselect(year=year, month=month) or 0
            requests_station = network.requests_station(year=year, month=month) or 0

            plot_data_dataselect.append(requests_dataselect)
            plot_data_station.append(requests_station)
            plot_data_months.append(arrow.get(year, month, 1).datetime)

        # Build image
        bar_width = 0.75 * ureg.month
        fig, ax = plt.subplots()

        # Calculate dimensions of image
        height = height or cls.DEFAULT_PX_HEIGHT
        width = width or cls.DEFAULT_PX_WIDTH
        fig.set_size_inches(width/fig.dpi, height/fig.dpi)

        # Initialize colors
        colors = cls._colors(colormap, 2)

        # Configure axis and plot data
        ax.yaxis.set_label_text('Requests for %s in %s' % (network.code, year))
        ax.xaxis.set_tick_params(rotation=90)
        ax.xaxis.set_major_formatter(mdates.DateFormatter("%b"))
        ax.xaxis.set_major_locator(mdates.MonthLocator())
        ax.bar(plot_data_months, plot_data_station, bar_width, label='Station', color=colors[0])
        ax.bar(plot_data_months, plot_data_dataselect, bar_width, bottom=plot_data_station, label='Dataselect', color=colors[1])

        # Add title
        #ax.set_title("%s %i" % (network.code, year), loc='left')

        # Add legend
        ax.legend(bbox_to_anchor=(1, 1), loc='lower right', borderaxespad=0, ncol=2, frameon=False)

        # Crop image
        plt.tight_layout()

        # Add footer
        cls._footer_bar(fig)

        # Save file
        output_filename = 'plot_requests_monthly.png'
        output_filepath = os.path.join(cls.output_dir(network, year), output_filename)
        fig.savefig(output_filepath, transparent=True)
        logger.info("Plot saved as %s" % output_filepath)

        return output_filename

    @classmethod
    def plot_map_requests_by_country(cls, network, year=None, width=None, height=None, colormap='Blues', dpi=100):
        """
        Plots a world map colored by number of requests querying a network
        :param network: The network object
        :param year: The year
        :param width: The width of the output image
        :param height: The height of the output image
        :param colormap: The colormap to use
        :param dpi: The DPI (default=100)
        :return: The plot output file path
        :rtype: str
        """
        logger.debug("ServicePlotterPNG.plot_map_requests_by_country(%s, %s, %s, %s, %s, %s)" % (network, year, width, height, colormap, dpi))
        year = year or datetime.datetime.now().year
        logger.info("Plotting map of requests by country for network %s in %i" % (network.code, year))

        # Build plot data
        plot_data = {}
        with_antartica = False
        for country_code, country_requests in network.requests_by_country(year=year).items():
            try:
                country = countries.get(country_code)
                plot_data[country.alpha3] = int(country_requests)
                if not with_antartica and country.alpha3 == 'ATA':
                    with_antartica = True
            except KeyError:
                logger.debug("Unable to resolve country code '%s'" % country_code)
                continue

        # Build output
        if plot_data:

            # Initialize figure
            height = height or cls.DEFAULT_PX_HEIGHT
            width = width or cls.DEFAULT_PX_WIDTH
            fig = plt.figure(figsize=(width/dpi, height/dpi))
            ax = fig.add_subplot(1, 1, 1)
            ax.tick_params(axis='both', bottom=False, left=False,  labelbottom=False, labelleft=False)

            # Plot data
            df = pd.DataFrame({
                'iso_a3': pd.Series(plot_data.keys()),
                'requests': pd.Series(plot_data.values())
            })
            world = geopandas.read_file(
                geopandas.datasets.get_path("naturalearth_lowres"),
                ignore_fields=["pop_est", "continent", "gdp_md_est"]
            )
            world = world.merge(df, on='iso_a3', how='left')

            if not with_antartica:
                world.drop(world.loc[world['iso_a3']=='ATA'].index, inplace=True)

            world.plot("requests", ax=ax, cmap=colormap, edgecolor="grey",
                       legend=True, legend_kwds={"label":"Requests", 'orientation':"horizontal"},
                       missing_kwds={"color": "lightgrey", "label":"Missing Values", 'edgecolor':None}
            )

            # Add title
            # ax.set_title("Requests for %s in %s" % (network.code, year))

            # Crop image
            #plt.tight_layout()

            # Add footer
            cls._footer_bar(fig)

            # Save file
            output_filename = 'plot_map_requests_by_country.png'
            output_filepath = os.path.join(cls.output_dir(network, year), output_filename)
            fig.savefig(output_filepath, transparent=True)
            logger.info("Plot saved as %s" % output_filepath)

            return output_filename

    @classmethod
    def plot_map_clients_by_country(cls, network, year=None, width=None, height=None, colormap='Blues', dpi=100):
        """
        Plots a world map colored by number of clients querying a network
        :param network: The network object
        :param year: The year
        :param width: The width of the output image
        :param height: The height of the output image
        :param colormap: The colormap to use
        :param dpi: The DPI (default=100)
        :return: The plot output file path
        :rtype: str
        """
        logger.debug("ServicePlotterPNG.plot_map_clients_by_country(%s, %s, %s, %s, %s, %s)" % (network, year, width, height, colormap, dpi))
        year = year or datetime.datetime.now().year
        logger.info("Plotting map of clients by country for network %s in %i" % (network.code, year))

        # Build plot data
        plot_data = {}
        with_antartica = False
        for country_code, country_clients in network.clients_by_country(year=year).items():
            try:
                country = countries.get(country_code)
                plot_data[country.alpha3] = int(country_clients)
                if not with_antartica and country.alpha3 == 'ATA':
                    with_antartica = True
            except KeyError:
                logger.debug("Unable to resolve country code '%s'" % country_code)
                continue

        # Build output
        if plot_data:

            # Initialize figure
            height = height or cls.DEFAULT_PX_HEIGHT
            width = width or cls.DEFAULT_PX_WIDTH
            fig = plt.figure(figsize=(width/dpi, height/dpi))
            ax = fig.add_subplot(1, 1, 1)
            ax.tick_params(axis='both', bottom=False, left=False,  labelbottom=False, labelleft=False)

            # Plot data
            df = pd.DataFrame({
                'iso_a3': pd.Series(plot_data.keys()),
                'clients': pd.Series(plot_data.values())
            })
            world = geopandas.read_file(
                geopandas.datasets.get_path("naturalearth_lowres"),
                ignore_fields=["pop_est", "continent", "gdp_md_est"]
            )
            world = world.merge(df, on='iso_a3', how='left')

            if not with_antartica:
                world.drop(world.loc[world['iso_a3']=='ATA'].index, inplace=True)

            world.plot("clients", ax=ax, cmap=colormap, edgecolor="grey",
                       legend=True, legend_kwds={"label":"Clients", 'orientation':"horizontal"},
                       missing_kwds={"color": "lightgrey", "label":"Missing Values", 'edgecolor':None}
            )

            # Add title
            # ax.set_title("clients for %s in %s" % (network.code, year))

            # Crop image
            #plt.tight_layout()

            # Add footer
            cls._footer_bar(fig)

            # Save file
            output_filename = 'plot_map_clients_by_country.png'
            output_filepath = os.path.join(cls.output_dir(network, year), output_filename)
            fig.savefig(output_filepath, transparent=True)
            logger.info("Plot saved as %s" % output_filepath)

            return output_filename

    @classmethod
    def plot_map_network_stations(cls, network, year=None, width=None, height=None, zoom=None):
        """
        Plots a world map locating the stations of a network
        :param network: The network object
        :param year: The year
        :param width: The width of the output image
        :param height: The height of the output image
        :param zoom: The map zoom
        :return: The plot output file path
        :rtype: str
        """
        logger.debug("ServicePlotterPNG.plot_map_network_stations(%s, %s, %s, %s, %s)" % (network, year, width, height, zoom))
        year = year or datetime.datetime.now().year
        logger.info("Plotting map of stations for network %s in %i" % (network.code, year))

        root_path = pathlib.Path(__file__).resolve().parent.parent.parent
        marker_filepath = os.path.join(root_path, 'assets', 'triangle-10.png')

        width = width or cls.DEFAULT_PX_WIDTH
        height = height or cls.DEFAULT_PX_HEIGHT
        map = StaticMap(width, height)
        for station in network.active_stations(year):
            marker = IconMarker((station.longitude, station.latitude), marker_filepath, 0, 0)
            map.add_marker(marker)

        output_filename = 'plot_map_network_stations.png'
        output_filepath = os.path.join(cls.output_dir(network, year), output_filename)

        image = map.render(zoom=zoom)
        image.save(output_filepath)
        logger.info("Plot saved as %s" % output_filepath)

        return output_filename
