"""
<NAME> 01/2022
Utility Functions:
1. create_dir - create directory at the selected absolute path.
2. save_raster - save a numpy raster in GeoTiff format.
3. load_tiff - read GeoTiff raster ad a numpy array.
4. load_netcdf - read raster saved in NetCDF format.

Python Dependencies:
    numpy: package for scientific computing with Python
           https://numpy.org
    rasterio: access to geospatial raster data
       https://rasterio.readthedocs.io
    xarray: xarray: N-D labeled arrays and datasets in Python
           https://xarray.pydata.org/en/stable
"""
# - python dependencies
from __future__ import print_function
import os
import numpy as np
import rasterio
from rasterio.transform import Affine
import xarray as xr


def create_dir(abs_path: str, dir_name: str) -> str:
    """
    Create directory
    :param abs_path: absolute path to the output directory
    :param dir_name: new directory name
    :return: absolute path to the new directory
    """
    dir_to_create = os.path.join(abs_path, dir_name)
    if not os.path.exists(dir_to_create):
        os.mkdir(dir_to_create)
    return dir_to_create


def save_raster(raster: np.ndarray, res: float, x: np.ndarray,
                y: np.ndarray, out_path: str, crs: int,
                nodata: int = -9999) -> None:
    """
    Save the Provided Raster in GeoTiff format
    :param raster: input raster - np.ndarray
    :param res: raster resolution - integer
    :param x: x-axis - np.ndarray
    :param y: y-axis - np.ndarray
    :param crs: - coordinates reference system
    :param out_path: absolute path to output file
    :param nodata: no-data value
    :return: None
    """
    # - Calculate Affine Transformation of the output raster
    y = np.flipud(y)
    shift = res/2
    transform = (Affine.translation(x[0]-shift, y[0]+shift)
                 * Affine.scale(res, -res))
    print(transform)
    with rasterio.open(out_path, 'w', driver='GTiff',
                       height=raster.shape[0],
                       width=raster.shape[1], count=1,
                       dtype=raster.dtype, crs=crs,
                       transform=transform,
                       nodata=nodata) as dst:
        dst.write(raster, 1)


def load_tiff(in_path: str) -> dict:
    """
    Load raster saved in GeoTiff format
    :param in_path: absolute path to input GeoTiff
    :return: python dictionary containing raster data as numpy array
             input raster + ancillary data (X- and Y-axes info + Geo-Transform).
    """
    with rasterio.open(in_path, mode="r+") as src:
        # - read band #1 - DEM elevation in meters
        raster_input = src.read(1).astype(src.dtypes[0])
        # - raster upper-left and lower-right corners
        ul_corner = src.transform * (0, 0)
        lr_corner = src.transform * (src.width, src.height)
        grid_res = src.res
        # -
        shift_x = src.res[0] / 2
        shift_y = src.res[1] / 2
        x_coords = np.arange(ul_corner[0]+shift_x, lr_corner[0]+shift_x,
                             grid_res[0])
        y_coords = np.arange(lr_corner[1]+shift_y, ul_corner[1]+shift_y,
                             grid_res[1])
        if src.transform.e < 0:
            raster_input = np.flipud(raster_input)
        # - Compute New Affine Transform
        transform = (Affine.translation(x_coords[0]+shift_x,
                                        y_coords[0]-shift_y)
                     * Affine.scale(src.res[0], src.res[1]))

        return{'data': raster_input, 'crs': src.crs, 'res': src.res,
               'y_coords': y_coords, 'x_coords': x_coords,
               'transform': transform, 'src_transform': src.transform,
               'width': src.width, 'height': src.height,
               'ul_corner': ul_corner, 'lr_corner': lr_corner,
               'nodata': src.nodata, 'dtype': src.dtypes[0]}


def load_netcdf(in_path: str, data_name='mask', x='x', y='y') -> dict:
    """
    Load raster saved in NetCDF format
    :param in_path: absolute path to input NetCDF archive
    :param data_name:input raster var name
    :param x:x-axis name
    :param y:y-axis name
    :return: python dictionary containing raster data as numpy array
             input raster + ancillary data (X- and Y-axes info + Geo-Transform).
    """
    src = xr.open_dataset(in_path)
    raster_input = src[data_name].values
    x_coords = src[x].values[0, :]
    y_coords = src[y].values[:, 1]
    grid_res_x = np.abs(x_coords[0] - x_coords[1])
    grid_res_y = np.abs(y_coords[0] - y_coords[1])
    width = raster_input.shape[1]
    height = raster_input.shape[0]
    # -
    shift_x = grid_res_x / 2
    shift_y = grid_res_y / 2

    if y_coords[1] < y_coords[0]:
        raster_input = np.flipud(raster_input)
        y_coords = np.flipud(y_coords)

    ul_corner = (y_coords[-1], x_coords[0])
    lr_corner = (y_coords[0], x_coords[-1])

    # - Compute New Affine Transform
    transform = (Affine.translation(x_coords[0]-shift_x, y_coords[0]-shift_y)
                 * Affine.scale(grid_res_x, grid_res_y))

    return {'data': raster_input, 'res': (grid_res_x, grid_res_y),
            'y_coords': y_coords, 'x_coords': x_coords,
            'transform': transform, 'width': width, 'height': height,
            'ul_corner': ul_corner, 'lr_corner': lr_corner,
            'dtype': raster_input.dtype}
