from bokeh.plotting import figure, output_file, show
from bokeh.models import ColumnDataSource, TapTool, OpenURL
from bokeh.models.tools import HoverTool
from bokeh.tile_providers import get_provider, Vendors
import pandas as pd
import piexif
from pyproj import Transformer

# Own modules:
from scripts.tagging_functions import GeotaggingFunctions, Logging


class PlotImages:

    def __init__(self, settings):
        pd.set_option('display.max_columns', None)  # show all columns for pandas
        self.input_location = settings.input_location
        # Initialize logging
        self.logger = Logging('Plot_Images', 'DEBUG')
        self.gf = GeotaggingFunctions(self.logger)
        # Setup coordinate conversion parameters:
        self.transformer = Transformer.from_crs("EPSG:4326", "EPSG:3857", always_xy=True)

    def apply(self):
        df = self.exif_coordinates_to_dataframe(self.input_location)
        df = self.add_3857_to_df(df)
        self.create_bokeh_plot(df)

    def exif_coordinates_to_dataframe(self, input_location):
        """
        This function takes all the images in the input location and extracts the exif data from them.
        It retrieves the datetime the image was taken and the latitude and longitude where the image were taken.
        It then creates a pandas dataframe with the filename, datetime, latitude, longitude and it returns this
        dataframe.

        :param input_location: the directory containing the images to extract the exif data from.
        :return: a pandas dataframe containing: path, filename, datetime, latitude, longitude
        """
        image_list = self.gf.retrieve_image_filelist(input_location)
        paths, filenames, dates, lats, lons = [], [], [], [], []
        for file in image_list:
            exif_dict = piexif.load(file)
            # Extract the datetime from the exif data, transform byte to string and create a datetime object from it:
            img_datetime = self.gf.string_to_datetime(
                self.gf.decode_byte_object(exif_dict["0th"][piexif.ImageIFD.DateTime]))
            latitude, longitude = self.gf.dms_to_decimal_degrees((exif_dict["GPS"][piexif.GPSIFD.GPSLatitude],
                                                                  exif_dict["GPS"][piexif.GPSIFD.GPSLongitude]))
            dates.append(img_datetime)
            lats.append(latitude)
            lons.append(longitude)
            filenames.append(file.split('/')[-1])
            paths.append(file)
        df = pd.DataFrame()
        df['path'] = paths
        df['filename'] = filenames
        df['datetime'] = dates
        df['latitude'] = lats
        df['longitude'] = lons

        return df

    def add_3857_to_df(self, df):
        """
        This function adds a epsg:3857 column to the dataframe for both the latitude and longitude.

        :param df: pandas dataframe as generated by exif_coordinates_to_dataframe().
        :return: dataframe with epsg:3857 coordinates added.
        """
        lat3857, lon3857 = [], []
        for index, row in df.iterrows():
            lat, lon = self.convert_wgs84_to_3857(row['latitude'], row['longitude'])
            lat3857.append(lat)
            lon3857.append(lon)
        df['latitude3857'] = lat3857
        df['longitude3857'] = lon3857
        return df

    def convert_wgs84_to_3857(self, latitude, longitude):
        """
        This function transforms a epsg:4326 (WGS84) to a epsg:3857 coordinate.
        :param latitude: the latitude in 4326
        :param longitude: the longitude in 4326
        :return: latitude, longitude in 3857
        """
        return self.transformer.transform(longitude, latitude)

    @staticmethod
    def create_bokeh_plot(df):
        """
        This function creates a bokeh plot which shows where images were taken. It adds a link to the image path so that
        if the location is clicked, the image is shown. The generated html bokeh plot is saved in the same directory
        as the images.

        :param df: A pandas dataframe containing the columns:
         path, filename, datetime, latitude, longitude, latitude3857, longitude3857
        """
        # Extract the image directory by selecting the first image in the dataframe and taking the path from that:
        image_path = df['path'].iloc[0]
        output_path = '/'.join(image_path.split('/')[:-1]) + '/output.html'
        output_file(output_path)

        # Create a data source of the type ColumnDataSource:
        source = ColumnDataSource(df)

        # Get map tile provider (in EPSG:3857):
        tile_provider = get_provider(Vendors.CARTODBPOSITRON)

        # range bounds supplied in web mercator coordinates
        p = figure(x_axis_type="mercator", y_axis_type="mercator", plot_width=1200, plot_height=960,
                   tools=['pan', 'tap',
                          'wheel_zoom',
                          'save', 'reset'])
        p.title.text = 'Images taken this time'
        p.xaxis.axis_label = 'Longitude'
        p.yaxis.axis_label = 'Latitude'
        p.add_tile(tile_provider)
        # Add species data (points)
        p.circle(x="latitude3857", y="longitude3857", size=5, fill_color="green", fill_alpha=0.8, source=source)
        # Add a hovertool:
        hover = HoverTool()
        hover.tooltips = [
            ('Filename', '@filename'),
            ('Date and time taken', '@datetime'),
            ('Latitude (WGS84)', '@latitude'),
            ('Longitude (WGS84)', '@longitude')
        ]
        p.add_tools(hover)

        url = "@path"
        taptool = p.select(type=TapTool)
        taptool.callback = OpenURL(url=url)

        show(p)
