import os
from pathlib import Path
import logging
import contextlib

from ..handler import add_handler
from ..core.windows import get_window_offsets
from ..core.util import parse_filename_dates
from ..config import config
from .rasterio_ import get_ref_image_meta
from .rasterio_ import warp
from .rasterio_ import warp_images
from .rasterio_ import get_file_bounds
from .rasterio_ import window_to_bounds
from .rasterio_ import unpack_bounding_box
from .rasterio_ import unpack_window
from .rasterio_ import transform_crs as rio_transform_crs
from .xarray_rasterio_ import open_rasterio

import numpy as np
from rasterio import open as rio_open
from rasterio.windows import Window
from rasterio.coords import BoundingBox
import dask.array as da
import xarray as xr
from xarray.ufuncs import maximum as xr_maximum
from xarray.ufuncs import minimum as xr_mininum


logger = logging.getLogger(__name__)
logger = add_handler(logger)


def _update_kwarg(ref_obj, ref_kwargs, key):
    """Updates keyword arguments for global config parameters

    Args:
        ref_obj (str or object)
        ref_kwargs (dict)
        key (str)

    Returns:
        ``dict``
    """

    if isinstance(ref_obj, str) and Path(ref_obj).is_file():

        # Get the metadata from the reference image
        ref_meta = get_ref_image_meta(ref_obj)
        ref_kwargs[key] = getattr(ref_meta, key)

    else:

        if ref_obj:
            ref_kwargs[key] = ref_obj

    return ref_kwargs


def _get_raster_coords(filename):
    with open_rasterio(filename) as src:
        x = src.x.values - src.res[0] / 2.0
        y = src.y.values + src.res[1] / 2.0

    return x, y


def _check_config_globals(filenames, bounds_by, ref_kwargs):
    """Checks global configuration parameters

    Args:
        filenames (str or str list)
        bounds_by (str)
        ref_kwargs (dict)
    """

    # Check if there is a reference image
    if config['ref_image']:

        if isinstance(config['ref_image'], str) and os.path.isfile(config['ref_image']):

            # Get the metadata from the reference image
            ref_meta = get_ref_image_meta(config['ref_image'])

            ref_kwargs['bounds'] = ref_meta.bounds
            ref_kwargs['crs'] = ref_meta.crs
            ref_kwargs['res'] = ref_meta.res

        else:

            if not config['ignore_warnings']:
                logger.warning('  The reference image does not exist')

    else:

        if config['ref_bounds']:

            if isinstance(config['ref_bounds'], str) and config['ref_bounds'].startswith('Window'):
                ref_bounds_ = window_to_bounds(filenames, unpack_window(config['ref_bounds']))
            elif isinstance(config['ref_bounds'], str) and config['ref_bounds'].startswith('BoundingBox'):
                ref_bounds_ = unpack_bounding_box(config['ref_bounds'])
            elif isinstance(config['ref_bounds'], Window):
                ref_bounds_ = window_to_bounds(filenames, config['ref_bounds'])
            elif isinstance(config['ref_bounds'], BoundingBox):

                ref_bounds_ = (config['ref_bounds'].left,
                               config['ref_bounds'].bottom,
                               config['ref_bounds'].right,
                               config['ref_bounds'].top)

            else:
                ref_bounds_ = config['ref_bounds']

            ref_kwargs = _update_kwarg(ref_bounds_, ref_kwargs, 'bounds')

        else:

            if isinstance(filenames, str) or isinstance(filenames, Path):

                # Use the bounds of the image
                ref_kwargs['bounds'] = get_file_bounds([filenames],
                                                       bounds_by='reference',
                                                       crs=ref_kwargs['crs'],
                                                       res=ref_kwargs['res'],
                                                       return_bounds=True)

            else:

                # Replace the bounds keyword, if needed
                if bounds_by.lower() == 'intersection':

                    # Get the intersecting bounds of all images
                    ref_kwargs['bounds'] = get_file_bounds(filenames,
                                                           bounds_by='intersection',
                                                           crs=ref_kwargs['crs'],
                                                           res=ref_kwargs['res'],
                                                           return_bounds=True)

                elif bounds_by.lower() == 'union':

                    # Get the union bounds of all images
                    ref_kwargs['bounds'] = get_file_bounds(filenames,
                                                           bounds_by='union',
                                                           crs=ref_kwargs['crs'],
                                                           res=ref_kwargs['res'],
                                                           return_bounds=True)

                elif bounds_by.lower() == 'reference':

                    # Use the bounds of the first image
                    ref_kwargs['bounds'] = get_file_bounds(filenames,
                                                           bounds_by='reference',
                                                           crs=ref_kwargs['crs'],
                                                           res=ref_kwargs['res'],
                                                           return_bounds=True)

                else:
                    logger.exception("  Choose from 'intersection', 'union', or 'reference'.")

                config['ref_bounds'] = ref_kwargs['bounds']

        if config['ref_crs']:
            ref_kwargs = _update_kwarg(config['ref_crs'], ref_kwargs, 'crs')

        if config['ref_res']:
            ref_kwargs = _update_kwarg(config['ref_res'], ref_kwargs, 'res')

        if config['ref_tar']:

            if isinstance(config['ref_tar'], str):

                if os.path.isfile(config['ref_tar']):
                    ref_kwargs = _update_kwarg(_get_raster_coords(config['ref_tar']), ref_kwargs, 'tac')
                else:

                    if not config['ignore_warnings']:
                        logger.warning('  The target aligned raster does not exist.')

            else:

                if not config['ignore_warnings']:
                    logger.warning('  The target aligned raster must be an image.')

    return ref_kwargs


def warp_open(
    filename,
    band_names=None,
    resampling='nearest',
    dtype=None,
    netcdf_vars=None,
    nodata=0,
    return_windows=False,
    warp_mem_limit=512,
    num_threads=1,
    tap=False,
    **kwargs
):
    """Warps and opens a file

    Args:
        filename (str): The file to open.
        band_names (Optional[int, str, or list]): The band names.
        resampling (Optional[str]): The resampling method.
        dtype (Optional[str]): A data type to force the output to. If not given, the data type is extracted
            from the file.
        netcdf_vars (Optional[list]): NetCDF variables to open as a band stack.
        nodata (Optional[float | int]): A 'no data' value to set. Default is 0.
        return_windows (Optional[bool]): Whether to return block windows.
        warp_mem_limit (Optional[int]): The memory limit (in MB) for the ``rasterio.vrt.WarpedVRT`` function.
        num_threads (Optional[int]): The number of warp worker threads.
        tap (Optional[bool]): Whether to target align pixels.
        kwargs (Optional[dict]): Keyword arguments passed to ``open_rasterio``.

    Returns:
        ``xarray.DataArray``
    """
    ref_kwargs = {
        'bounds': None,
        'crs': None,
        'res': None,
        'nodata': nodata if config['nodata'] is None else config['nodata'],
        'warp_mem_limit': warp_mem_limit,
        'num_threads': num_threads,
        'tap': tap,
        'tac': None
    }

    ref_kwargs_netcdf_stack = ref_kwargs.copy()
    ref_kwargs_netcdf_stack['bounds_by'] = 'union'
    del ref_kwargs_netcdf_stack['tap']

    ref_kwargs = _check_config_globals(filename, 'reference', ref_kwargs)

    filenames = None

    # Create a list of variables to open
    if filename.lower().startswith('netcdf:') and netcdf_vars:
        filenames = (f'{filename}:' + f',{filename}:'.join(netcdf_vars)).split(',')

    if filenames:

        ref_kwargs_netcdf_stack = _check_config_globals(filenames[0], 'reference', ref_kwargs_netcdf_stack)

        with rio_open(filenames[0]) as src:
            tags = src.tags()

    else:

        ref_kwargs_netcdf_stack = _check_config_globals(filename, 'reference', ref_kwargs_netcdf_stack)

        with rio_open(filename) as src:
            tags = src.tags()

    @contextlib.contextmanager
    def warp_netcdf_vars():
        # Warp all images to the same grid.
        warped_objects = warp_images(filenames,
                                     resampling=resampling,
                                     **ref_kwargs_netcdf_stack)

        yield xr.concat(
            (
                open_rasterio(wobj, **kwargs).assign_coords(band=[band_names[wi]]
                if band_names else [netcdf_vars[wi]])
                for wi, wobj in enumerate(warped_objects)
             ), dim='band'
        )

    with open_rasterio(
            warp(
                filename,
                resampling=resampling,
                **ref_kwargs
            ), **kwargs
    ) if not filenames else warp_netcdf_vars() as src:
        if band_names:
            if len(band_names) > src.gw.nbands:
                src.coords['band'] = band_names[:src.gw.nbands]
            else:
                src.coords['band'] = band_names

        else:
            if src.gw.sensor:
                if src.gw.sensor not in src.gw.avail_sensors:
                    if not src.gw.config['ignore_warnings']:
                        logger.warning('  The {} sensor is not currently supported.\nChoose from [{}].'.format(src.gw.sensor,
                                                                                                               ', '.join(src.gw.avail_sensors)))

                else:
                    new_band_names = list(src.gw.wavelengths[src.gw.sensor]._fields)
                    # Avoid nested opens within a `config` context
                    if len(new_band_names) != len(src.band.values.tolist()):
                        if not src.gw.config['ignore_warnings']:
                            logger.warning('  The new bands, {}, do not match the sensor bands, {}.'.format(new_band_names,
                                                                                                            src.band.values.tolist()))

                    else:
                        src.coords['band'] = new_band_names
                        src.attrs['sensor'] = src.gw.sensor_names[src.gw.sensor]

        if return_windows:
            if isinstance(kwargs['chunks'], tuple):
                chunksize = kwargs['chunks'][-1]
            else:
                chunksize = kwargs['chunks']

            src.attrs['block_windows'] = get_window_offsets(src.shape[-2],
                                                            src.shape[-1],
                                                            chunksize,
                                                            chunksize,
                                                            return_as='list')

        src.attrs['filename'] = filename
        src.attrs['resampling'] = resampling

        if tags:
            attrs = src.attrs.copy()
            attrs.update(tags)
            src = src.assign_attrs(**attrs)

        if dtype:

            attrs = src.attrs.copy()
            return src.astype(dtype).assign_attrs(**attrs)

        else:
            return src

def mosaic(
    filenames,
    overlap='max',
    bounds_by='reference',
    resampling='nearest',
    band_names=None,
    nodata=0,
    dtype=None,
    warp_mem_limit=512,
    num_threads=1,
    **kwargs
):
    """Mosaics a list of images

    Args:
        filenames (list): A list of file names to mosaic.
        overlap (Optional[str]): The keyword that determines how to handle overlapping data.
            Choices are ['min', 'max', 'mean'].
        bounds_by (Optional[str]): How to concatenate the output extent. Choices are ['intersection', 'union', 'reference'].

            * reference: Use the bounds of the reference image
            * intersection: Use the intersection (i.e., minimum extent) of all the image bounds
            * union: Use the union (i.e., maximum extent) of all the image bounds

        resampling (Optional[str]): The resampling method.
        band_names (Optional[1d array-like]): A list of names to give the band dimension.
        nodata (Optional[float | int]): A 'no data' value to set. Default is 0.
        dtype (Optional[str]): A data type to force the output to. If not given, the data type is extracted
            from the file.
        warp_mem_limit (Optional[int]): The memory limit (in MB) for the ``rasterio.vrt.WarpedVRT`` function.
        num_threads (Optional[int]): The number of warp worker threads.
        kwargs (Optional[dict]): Keyword arguments passed to ``open_rasterio``.

    Returns:
        ``xarray.DataArray``
    """
    if overlap not in ['min', 'max', 'mean']:
        logger.exception("  The overlap argument must be one of ['min', 'max', 'mean'].")

    ref_kwargs = {'bounds': None,
                  'crs': None,
                  'res': None,
                  'nodata': nodata,
                  'warp_mem_limit': warp_mem_limit,
                  'num_threads': num_threads,
                  'tac': None}

    ref_kwargs = _check_config_globals(filenames, bounds_by, ref_kwargs)

    # Warp all images to the same grid.
    warped_objects = warp_images(filenames,
                                 bounds_by=bounds_by,
                                 resampling=resampling,
                                 **ref_kwargs)

    footprints = []

    with rio_open(filenames[0]) as src_:
        tags = src_.tags()

    # Combine the data
    with open_rasterio(warped_objects[0], **kwargs) as darray:
        attrs = darray.attrs.copy()

        # Get the original bounds, unsampled
        with open_rasterio(filenames[0], **kwargs) as src_:
            footprints.append(src_.gw.geometry)

        for fidx, fn in enumerate(warped_objects[1:]):
            with open_rasterio(fn, **kwargs) as darrayb:
                with open_rasterio(filenames[fidx+1], **kwargs) as src_:
                    footprints.append(src_.gw.geometry)
                src_ = None

                if overlap == 'min':

                    if isinstance(nodata, float) or isinstance(nodata, int):

                        darray = xr.where((darray.mean(dim='band') == nodata) & (darrayb.mean(dim='band') != nodata), darrayb,
                                      xr.where((darray.mean(dim='band') != nodata) & (darrayb.mean(dim='band') == nodata), darray,
                                               xr_mininum(darray, darrayb)))

                    else:
                        darray = xr_mininum(darray, darrayb)

                elif overlap == 'max':

                    if isinstance(nodata, float) or isinstance(nodata, int):

                        darray = xr.where((darray.mean(dim='band') == nodata) & (darrayb.mean(dim='band') != nodata), darrayb,
                                      xr.where((darray.mean(dim='band') != nodata) & (darrayb.mean(dim='band') == nodata), darray,
                                               xr_maximum(darray, darrayb)))

                    else:
                        darray = xr_maximum(darray, darrayb)

                elif overlap == 'mean':

                    if isinstance(nodata, float) or isinstance(nodata, int):

                        darray = xr.where((darray.mean(dim='band') == nodata) & (darrayb.mean(dim='band') != nodata), darrayb,
                                      xr.where((darray.mean(dim='band') != nodata) & (darrayb.mean(dim='band') == nodata), darray,
                                               (darray + darrayb) / 2.0))

                    else:
                        darray = (darray + darrayb) / 2.0

                # darray = darray.combine_first(darrayb)

        darray = darray.assign_attrs(**attrs)

        if band_names:
            darray.coords['band'] = band_names
        else:

            if darray.gw.sensor:

                if darray.gw.sensor not in darray.gw.avail_sensors:

                    if not darray.gw.config['ignore_warnings']:

                        logger.warning('  The {} sensor is not currently supported.\nChoose from [{}].'.format(darray.gw.sensor,
                                                                                                               ', '.join(darray.gw.avail_sensors)))

                else:

                    new_band_names = list(darray.gw.wavelengths[darray.gw.sensor]._fields)

                    if len(new_band_names) != len(darray.band.values.tolist()):

                        if not darray.gw.config['ignore_warnings']:
                            logger.warning('  The band list length does not match the sensor bands.')

                    else:

                        darray.coords['band'] = new_band_names
                        darray.attrs['sensor'] = darray.gw.sensor_names[darray.gw.sensor]

        darray.attrs['filename'] = [Path(fn).name for fn in filenames]
        darray.attrs['resampling'] = resampling

        if tags:
            attrs = darray.attrs.copy()
            attrs.update(tags)
            darray = darray.assign_attrs(**attrs)

        darray.gw.footprint_grid = footprints

        if dtype:

            attrs = darray.attrs.copy()

            return darray\
                    .astype(dtype)\
                    .assign_attrs(**attrs)

        else:
            return darray

def concat(
    filenames,
    stack_dim='time',
    bounds_by='reference',
    resampling='nearest',
    time_names=None,
    band_names=None,
    nodata=0,
    dtype=None,
    netcdf_vars=None,
    overlap='max',
    warp_mem_limit=512,
    num_threads=1,
    tap=False,
    **kwargs
):
    """Concatenates a list of images

    Args:
        filenames (list): A list of file names to concatenate.
        stack_dim (Optional[str]): The stack dimension. Choices are ['time', 'band'].
        bounds_by (Optional[str]): How to concatenate the output extent. Choices are ['intersection', 'union', 'reference'].

            * reference: Use the bounds of the reference image
            * intersection: Use the intersection (i.e., minimum extent) of all the image bounds
            * union: Use the union (i.e., maximum extent) of all the image bounds

        resampling (Optional[str]): The resampling method.
        time_names (Optional[1d array-like]): A list of names to give the time dimension.
        band_names (Optional[1d array-like]): A list of names to give the band dimension.
        nodata (Optional[float | int]): A 'no data' value to set. Default is 0.
        dtype (Optional[str]): A data type to force the output to. If not given, the data type is extracted
            from the file.
        netcdf_vars (Optional[list]): NetCDF variables to open as a band stack.
        overlap (Optional[str]): The keyword that determines how to handle overlapping data.
            Choices are ['min', 'max', 'mean']. Only used when mosaicking arrays from the same timeframe.
        warp_mem_limit (Optional[int]): The memory limit (in MB) for the ``rasterio.vrt.WarpedVRT`` function.
        num_threads (Optional[int]): The number of warp worker threads.
        tap (Optional[bool]): Whether to target align pixels.
        kwargs (Optional[dict]): Keyword arguments passed to ``open_rasterio``.

    Returns:
        ``xarray.DataArray``
    """
    if stack_dim.lower() not in ['band', 'time']:
        logger.exception("  The stack dimension should be 'band' or 'time'.")

    # ref_kwargs = {'bounds': None,
    #               'crs': None,
    #               'res': None,
    #               'nodata': nodata,
    #               'warp_mem_limit': warp_mem_limit,
    #               'num_threads': num_threads,
    #               'tap': tap,
    #               'tac': None}

    # ref_kwargs = _check_config_globals(f'{filenames[0]}:{netcdf_vars[0]}' if netcdf_vars else filenames,
    #                                    bounds_by,
    #                                    ref_kwargs)

    with rio_open(filenames[0]) as src_:
        tags = src_.tags()

    src_ = warp_open(f'{filenames[0]}:{netcdf_vars[0]}' if netcdf_vars else filenames[0],
                     resampling=resampling,
                     band_names=[netcdf_vars[0]] if netcdf_vars else band_names,
                     nodata=nodata,
                     warp_mem_limit=warp_mem_limit,
                     num_threads=num_threads,
                     **kwargs)

    attrs = src_.attrs.copy()

    src_.close()
    src_ = None

    # Keep a copy of the transformed attributes.
    # with xr.open_rasterio(warp(filenames[0],
    #                            resampling=resampling,
    #                            **ref_kwargs), **kwargs) as src_:
    #
    #     attrs = src_.attrs.copy()

    if time_names and not (str(filenames[0]).lower().startswith('netcdf:')):

        concat_list = []
        new_time_names = []

        # Check the time names for duplicates
        for tidx in range(0, len(time_names)):

            if list(time_names).count(time_names[tidx]) > 1:

                if time_names[tidx] not in new_time_names:

                    # Get the file names to mosaic
                    filenames_mosaic = [filenames[i] for i in range(0, len(time_names))
                                        if time_names[i] == time_names[tidx]]

                    # Mosaic the images into a single-date array
                    concat_list.append(mosaic(filenames_mosaic,
                                              overlap=overlap,
                                              bounds_by=bounds_by,
                                              resampling=resampling,
                                              band_names=band_names,
                                              nodata=nodata,
                                              warp_mem_limit=warp_mem_limit,
                                              num_threads=num_threads,
                                              **kwargs))

                    new_time_names.append(time_names[tidx])

            else:

                new_time_names.append(time_names[tidx])

                # Warp the date
                concat_list.append(warp_open(filenames[tidx],
                                             resampling=resampling,
                                             band_names=band_names,
                                             nodata=nodata,
                                             warp_mem_limit=warp_mem_limit,
                                             num_threads=num_threads,
                                             **kwargs))

        # Warp all images and concatenate along the 'time' axis into a DataArray
        src = xr.concat(concat_list, dim=stack_dim.lower())\
                    .assign_coords(time=new_time_names)

    else:

        # Warp all images and concatenate along
        #   the 'time' axis into a DataArray.
        # src = xr.concat([xr.open_rasterio(warp(fn,
        #                                        resampling=resampling,
        #                                        **ref_kwargs), **kwargs)
        #                  for fn in filenames], dim=stack_dim.lower())

        src = xr.concat([warp_open(fn,
                                   resampling=resampling,
                                   band_names=band_names,
                                   netcdf_vars=netcdf_vars,
                                   nodata=nodata,
                                   warp_mem_limit=warp_mem_limit,
                                   num_threads=num_threads,
                                   **kwargs) for fn in filenames], dim=stack_dim.lower())

    src.attrs['filename'] = [Path(fn).name for fn in filenames]

    if tags:
        attrs = src.attrs.copy()
        attrs.update(tags)

    src = src.assign_attrs(**attrs)

    if stack_dim == 'time':

        if str(filenames[0]).lower().startswith('netcdf:'):

            if time_names:
                src.coords['time'] = time_names
            else:
                src.coords['time'] = parse_filename_dates(filenames)

            try:
                src = src.groupby('time').max().assign_attrs(**attrs)
            except:
                pass

        else:

            if not time_names:
                src.coords['time'] = parse_filename_dates(filenames)

    if band_names:
        src.coords['band'] = band_names
    else:

        if src.gw.sensor:

            if src.gw.sensor not in src.gw.avail_sensors:

                if not src.gw.config['ignore_warnings']:

                    logger.warning('  The {} sensor is not currently supported.\nChoose from [{}].'.format(src.gw.sensor,
                                                                                                           ', '.join(src.gw.avail_sensors)))

            else:

                new_band_names = list(src.gw.wavelengths[src.gw.sensor]._fields)

                if len(new_band_names) != len(src.band.values.tolist()):

                    if not src.gw.config['ignore_warnings']:

                        logger.warning('  The new bands, {}, do not match the sensor bands, {}.'.format(new_band_names,
                                                                                                        src.band.values.tolist()))

                else:

                    src.coords['band'] = new_band_names
                    src.attrs['sensor'] = src.gw.sensor_names[src.gw.sensor]

    if dtype:

        attrs = src.attrs.copy()
        return src.astype(dtype).assign_attrs(**attrs)

    else:
        return src


def transform_crs(
    data_src,
    dst_crs=None,
    dst_res=None,
    dst_width=None,
    dst_height=None,
    dst_bounds=None,
    src_nodata=None,
    dst_nodata=None,
    coords_only=False,
    resampling='nearest',
    warp_mem_limit=512,
    num_threads=1
):
    """Transforms a DataArray to a new coordinate reference system

    Args:
        data_src (DataArray): The data to transform.
        dst_crs (Optional[CRS | int | dict | str]): The destination CRS.
        dst_res (Optional[tuple]): The destination resolution.
        dst_width (Optional[int]): The destination width. Cannot be used with ``dst_res``.
        dst_height (Optional[int]): The destination height. Cannot be used with ``dst_res``.
        dst_bounds (Optional[BoundingBox | tuple]): The destination bounds, as a ``rasterio.coords.BoundingBox``
            or as a tuple of (left, bottom, right, top).
        src_nodata (Optional[int | float]): The source nodata value. Pixels with this value will not be used for
            interpolation. If not set, it will default to the nodata value of the source image if a masked ndarray
            or rasterio band, if available.
        dst_nodata (Optional[int | float]): The nodata value used to initialize the destination; it will remain in
            all areas not covered by the reprojected source. Defaults to the nodata value of the destination
            image (if set), the value of src_nodata, or 0 (GDAL default).
        coords_only (Optional[bool]): Whether to return transformed coordinates. If ``coords_only`` = ``True`` then
            the array is not warped and the size is unchanged. It also avoids in-memory computations.
        resampling (Optional[str]): The resampling method if ``filename`` is a ``list``.
            Choices are ['average', 'bilinear', 'cubic', 'cubic_spline', 'gauss', 'lanczos', 'max', 'med', 'min', 'mode', 'nearest'].
        warp_mem_limit (Optional[int]): The warp memory limit.
        num_threads (Optional[int]): The number of parallel threads.

    Returns:
        ``xarray.DataArray``
    """

    data_dst, dst_transform, dst_crs = rio_transform_crs(data_src,
                                                         dst_crs=dst_crs,
                                                         dst_res=dst_res,
                                                         dst_width=dst_width,
                                                         dst_height=dst_height,
                                                         dst_bounds=dst_bounds,
                                                         src_nodata=src_nodata,
                                                         dst_nodata=dst_nodata,
                                                         coords_only=coords_only,
                                                         resampling=resampling,
                                                         warp_mem_limit=warp_mem_limit,
                                                         num_threads=num_threads)

    if coords_only:

        cellx = abs(dst_transform[0])
        celly = abs(dst_transform[4])

        attrs = data_src.attrs.copy()

        attrs['crs'] = dst_crs
        attrs['transform'] = dst_transform[:6]
        attrs['res'] = (cellx, celly)

        return data_src.assign_coords(x=data_dst.xs, y=data_dst.ys).assign_attrs(**attrs)

    else:

        nrows, ncols = data_dst.shape[-2], data_dst.shape[-1]

        left = dst_transform[2]
        cellx = abs(dst_transform[0])
        x = np.arange(left + cellx / 2.0, left + cellx / 2.0 + (cellx * ncols), cellx)[:ncols]

        top = dst_transform[5]
        celly = abs(dst_transform[4])
        y = np.arange(top - celly / 2.0, top - celly / 2.0 - (celly * nrows), -celly)[:nrows]

        if not dst_res:
            dst_res = (abs(x[1] - x[0]), abs(y[0] - y[1]))

        data_dst = xr.DataArray(data=da.from_array(data_dst,
                                                   chunks=data_src.data.chunksize),
                                coords={'band': data_src.band.values.tolist(),
                                        'y': y,
                                        'x': x},
                                dims=('band', 'y', 'x'),
                                attrs=data_src.attrs)

        data_dst.attrs['transform'] = tuple(dst_transform)[:6]
        data_dst.attrs['crs'] = dst_crs
        data_dst.attrs['res'] = dst_res
        data_dst.attrs['resampling'] = resampling

        if 'sensor' in data_src.attrs:
            data_dst.attrs['sensor'] = data_src.attrs['sensor']

        if 'filename' in data_src.attrs:
            data_dst.attrs['filename'] = data_src.attrs['filename']

        return data_dst
