#!/usr/bin/env python

import sys
import warnings
import numpy as np
import os
import nes
from netCDF4 import Dataset
from mpi4py import MPI
from copy import copy


def to_netcdf_cams_ra(self, path):
    """
    Horizontal methods from one grid to another one.

    Parameters
    ----------
    self : nes.Nes
        Source projection Nes Object.
    path : str
            Path to the output netCDF file.
    """
    
    if not isinstance(self, nes.LatLonNes):
        raise TypeError("CAMS Re-Analysis format must have Regular Lat-Lon projection")
    if '<level>' not in path:
        raise ValueError("AMS Re-Analysis path must contain '<level>' as pattern; current: '{0}'".format(path))
    
    orig_path = copy(path)
    
    for i_lev, level in enumerate(self.lev['data']):
        path = orig_path.replace('<level>', 'l{0}'.format(i_lev))
        # Open NetCDF
        if self.info:
            print("Rank {0:03d}: Creating {1}".format(self.rank, path))
        if self.size > 1:
            netcdf = Dataset(path, format="NETCDF4", mode='w', parallel=True, comm=self.comm, info=MPI.Info())
        else:
            netcdf = Dataset(path, format="NETCDF4", mode='w', parallel=False)
        if self.info:
            print("Rank {0:03d}: NetCDF ready to write".format(self.rank))
        self.to_dtype(data_type=np.float32)

        # Create dimensions
        create_dimensions(self, netcdf)

        # Create variables
        create_variables(self, netcdf, i_lev)

        # Create dimension variables
        create_dimension_variables(self, netcdf)
        if self.info:
            print("Rank {0:03d}: Dimensions done".format(self.rank))

        # Close NetCDF
        if self.global_attrs is not None:
            for att_name, att_value in self.global_attrs.items():
                netcdf.setncattr(att_name, att_value)

        netcdf.close()

    return None


def create_dimensions(self, netcdf):
    """
    Create 'time', 'time_bnds', 'lev', 'lon' and 'lat' dimensions.

    Parameters
    ----------
    self : nes.Nes
        Source projection Nes Object.
    netcdf : Dataset
        netcdf4-python open dataset.
    """

    # Create time dimension
    netcdf.createDimension('time', None)

    # Create lev, lon and lat dimensions
    netcdf.createDimension('lat', len(self._lat['data']))
    netcdf.createDimension('lon', len(self._lon['data']))

    return None


def create_dimension_variables(self, netcdf):
    """
    Create the 'time', 'time_bnds', 'lev', 'lat', 'lat_bnds', 'lon' and 'lon_bnds' variables.

    Parameters
    ----------
    self : nes.Nes
        Source projection Nes Object.
    netcdf : Dataset
        netcdf4-python open dataset.
    """

    # LATITUDES
    lat = netcdf.createVariable('lat', np.float64, ('lat',))
    lat.standard_name = 'latitude'
    lat.long_name = 'latitude'
    lat.units = 'degrees_north'
    lat.axis = 'Y'

    if self.size > 1:
        lat.set_collective(True)
    lat[:] = self._lat['data']

    # LONGITUDES
    lon = netcdf.createVariable('lon', np.float64, ('lon',))
    lon.long_name = 'longitude'
    lon.standard_name = 'longitude'
    lon.units = 'degrees_east'
    lon.axis = 'X'
    if self.size > 1:
        lon.set_collective(True)
    lon[:] = self._lon['data']

    # TIMES
    time_var = netcdf.createVariable('time', np.float64, ('time',))
    time_var.standard_name = 'time'
    time_var.units = 'day as %Y%m%d.%f'
    time_var.calendar = 'proleptic_gregorian'
    time_var.axis = 'T'
    if self.size > 1:
        time_var.set_collective(True)
    time_var[:] = date2num(self._time[self.get_time_id(self.hours_start, first=True):
                                      self.get_time_id(self.hours_end, first=False)],
                           time_var.units, time_var.calendar)

    return None


def create_variables(self, netcdf, i_lev):
    """
    Create the netCDF file variables.

    Parameters
    ----------
    self : nes.Nes
        Source projection Nes Object.
    netcdf : Dataset
        netcdf4-python open dataset.
    """

    for i, (var_name, var_dict) in enumerate(self.variables.items()):
        if var_dict['data'] is not None:
            if self.info:
                print("Rank {0:03d}: Writing {1} var ({2}/{3})".format(self.rank, var_name, i + 1, len(self.variables)))
            try:
                var = netcdf.createVariable(var_name, np.float32, ('time', 'lat', 'lon',),
                                            zlib=True, complevel=7, least_significant_digit=3)

                if self.info:
                    print("Rank {0:03d}: Var {1} created ({2}/{3})".format(
                        self.rank, var_name, i + 1, len(self.variables)))
                if self.size > 1:
                    var.set_collective(True)
                    if self.info:
                        print("Rank {0:03d}: Var {1} collective ({2}/{3})".format(
                            self.rank, var_name, i + 1, len(self.variables)))

                if self.info:
                    print("Rank {0:03d}: Filling {1})".format(self.rank, var_name))
                var[self.write_axis_limits['t_min']:self.write_axis_limits['t_max'],
                    self.write_axis_limits['y_min']:self.write_axis_limits['y_max'],
                    self.write_axis_limits['x_min']:self.write_axis_limits['x_max']] = var_dict['data'][:, i_lev, :, :]

                if self.info:
                    print("Rank {0:03d}: Var {1} data ({2}/{3})".format(
                        self.rank, var_name, i + 1, len(self.variables)))
                var.long_name = var_dict['long_name']
                var.units = var_dict['units']
                var.number_of_significant_digits = np.int32(3)

                if self.info:
                    print("Rank {0:03d}: Var {1} completed ({2}/{3})".format(self.rank, var_name, i + 1,
                                                                             len(self.variables)))
            except Exception as e:
                print("**ERROR** an error has occurred while writing the '{0}' variable".format(var_name))
                # print("**ERROR** an error has occurredred while writing the '{0}' variable".format(var_name),
                #       file=sys.stderr)
                raise e
        else:
            msg = 'WARNING!!! '
            msg += 'Variable {0} was not loaded. It will not be written.'.format(var_name)
            warnings.warn(msg)
            sys.stderr.flush()

    return None


def date2num(time_array, time_units=None, time_calendar=None):
    
    time_res = []
    for aux_time in time_array:
        time_res.append(float(aux_time.strftime("%Y%m%d")) + (float(aux_time.strftime("%H")) / 24))
    time_res = np.array(time_res, dtype=np.float64)

    return time_res
