# Copyright (c) 2011-2014, B.I.Stepanov Institute of Physics, National Academy
# of Sciences of Belarus.
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation; either version 2 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc., 51
# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

import math
import numpy

from common.utils import txt

from common.DataRecord import *

__all__ = ['RamanOutput']

# *****************************************************************************
class RamanOutput(DataRecord):
    """Output data for aerosol backscatter and lidar ratio retrieval algorithm
    utilizing Raman lidar signals.

    Attributes to be read from a database:
      - 'latitude', 'longitude', 'altitude', 'startDate', 'stopDate',
        'startTime', 'stopTime': same as in 'PreparedLidarInput'.
      - 'retrievalDate', 'retrievalTime': date and time of the retrieval;
      - 'gridStep', 'gridZenithAngle': parameters of the data grid used for
        output data profiles (similar to those of 'PreparedLidarInput').

      - 'wavelength<channel>', where '<channel>' is either 'Source' or 'Raman':
        wavelengths of the lidar channels used in the retrieval, in nanometers.
        'Source' wavelength may be either 355.0 or 532.0, whereas the
        corresponding 'Raman' wavelength has to be 387.0 and 607.0
        respectively.
      - 'polarizationSource': polarization ID of the source lidar channel,
        (either 0 meaning "unpolarized" or 3 meaning "parallel-polarized").

      - 'profile<characteristic>', where '<characteristic>' is either
        'Backscatter' or 'Lidar': retrieved profiles for aerosol backscatter
        ratio (aerosol backscatter divided by molecular backscatter) and
        aerosol lidar ratio (aerosol extinction divided by aerosol
        backscatter).

      - 'measuredSignal<channel>', 'calculatedSignal<channel>': measured and
        calculated lidar signals for different lidar channels, divided by their
        average values around the reference points, with molecular components
        removed.
      - 'signalId<channel>': 'localId' fields of the lidar inputs.

      - 'atmoModel': textual identifier for the molecular atmosphere (copied
        from the lidar database).
      - 'dispersion<channel>': Numpy array holding weighting coefficients for
        the lidar signal equations.

      - 'molBackscatter': molecular backscatter profile (copied from the lidar
        input).
      - 'aerBackscatter', 'aerExtinction': aerosol backscatter and extinction
        profiles (calculated from 'molBackscatter' and the retrieved data).

      - 'weighting<channel>': weighting coefficients for lidar signals (copied
        from the algorithm params).
      - 'weightSmooth<characteristic>': weighting coefficients for smoothness
        terms of the retrieval equations (copied from the algorithm params).

      - 'molDepolarization', 'attenuationRatio': molecular depolarization ratio
        and lidar channel attenuation ratio (copied from the algorithm params).

      - 'molThicknessError<channel>', 'aerosolThicknessError<channel>',
        'totalBackscatterError<channel>': estimations of the atmospheric
        relative errors (copied from the corresponding 'PreparedLidarInput'
        instances).

      - 'ratio<channel>': retrieved correction coefficients for the lidar
        equations.
      - 'residual<channel>': square root of the squared lidar equation
        residual, integrated over the retrieval interval.
      - 'resSmooth<characteristic>': square root of the squared approximated
        second order derivative of the retrieved aerosol characteristic
        profile, integrated over the retrieval interval.

      - 'profileDispersion<...>', 'ratioDispersion<...>': dispersions of the
        corresponding 'profile<...>' and 'ratio<...>' retrieved parameters that
        had been obtained while applying a predefined set of perturbances to
        the input data.

    Attributes that are calculated manually in 'onDataLoaded':
      - 'gridHeightStep': height step of the data grid (similar to
        'PreparedLidarInput')."""

    # ---- Class attributes ---------------------------------------------------
    # Name of the aerosol profile output database table to read the data from.
    tableName = 'AerosolProfiles'

    fields = [
        DataField('latitude',  'Latitude',  'SINGLE'),
        DataField('longitude', 'Longitude', 'SINGLE'),
        DataField('altitude',  'Altitude',  'SINGLE'),

        DataField('startDate',      'StartDate',      'DATE'),
        DataField('stopDate',       'StopDate',       'DATE'),
        DataField('startTime',      'StartTime',      'TIME'),
        DataField('stopTime',       'StopTime',       'TIME'),
        DataField('retrievalDate',  'RetrievalDate',  'DATE'),
        DataField('retrievalTime',  'RetrievalTime',  'TIME'),

        DataField('gridStep',        'Step',      'SINGLE'),
        DataField('gridZenithAngle', 'Zenith',    'SINGLE'),

        DataField('firstInputIndexSource', 'LeftSource', 'SMALLINT'),
        DataField('firstInputIndexRaman',  'LeftRaman',  'SMALLINT'),

        DataField('wavelengthSource', 'Wavelength_Source', 'SINGLE'),
        DataField('wavelengthRaman',  'Wavelength_Raman',  'SINGLE'),

        DataField('polarizationSource', 'Polarization_Source', 'SMALLINT'),

        DataField('profileBackscatter', 'BackscatterMin1Profile', 'LONGBINARY',
            numpy.float32),
        DataField('profileLidar', 'LidarRatioProfile', 'LONGBINARY',
            numpy.float32),

        DataField('measuredSignalSource', 'Measured_Source', 'LONGBINARY',
            numpy.float32),
        DataField('measuredSignalRaman',  'Measured_Raman',  'LONGBINARY',
            numpy.float32),

        DataField('calculatedSignalSource', 'Calculated_Source', 'LONGBINARY',
            numpy.float32),
        DataField('calculatedSignalRaman',  'Calculated_Raman',  'LONGBINARY',
            numpy.float32),

        DataField('signalIdSource', 'IDLocal_Source', 'VARCHAR'),
        DataField('signalIdRaman',  'IDLocal_Raman',  'VARCHAR'),

        DataField('atmosphereModel',  'AtmoModel',  'VARCHAR'),

        DataField('dispersionSource', 'StdDeviation_Source', 'LONGBINARY',
            numpy.float32),
        DataField('dispersionRaman',  'StdDeviation_Raman',  'LONGBINARY',
            numpy.float32),

        DataField('molBackscatter', 'MolBackscatter', 'LONGBINARY',
            numpy.float32, required = False),
        DataField('aerBackscatter', 'AerBackscatter', 'LONGBINARY',
            numpy.float32, required = False),
        DataField('aerExtinction',  'AerExtinction', 'LONGBINARY',
            numpy.float32, required = False),

        DataField('weightingSource', 'Weight_Source', 'SINGLE'),
        DataField('weightingRaman',  'Weight_Raman',  'SINGLE'),

        DataField('weightSmoothBackscatter', 'WeightSmooth_Backscatter',
            'SINGLE'),
        DataField('weightSmoothLidar', 'WeightSmooth_Lidar', 'SINGLE'),

        DataField('molDepolarization', 'MolDepolar',       'SINGLE'),
        DataField('attenuationRatio',  'AttenuationRatio', 'SINGLE'),

        DataField('molThicknessErrorSource', 'RE OMT_Source', 'SINGLE'),
        DataField('molThicknessErrorRaman',  'RE OMT_Raman',  'SINGLE'),

        DataField('aerosolThicknessErrorSource', 'RE OAT_Source', 'SINGLE'),
        DataField('aerosolThicknessErrorRaman',  'RE OAT_Raman',  'SINGLE'),

        DataField('totalBackscatterErrorSource', 'RE GBS_Source', 'SINGLE'),
        DataField('totalBackscatterErrorRaman',  'RE GBS_Raman',  'SINGLE'),

        DataField('ratioSource', 'Ratio_Source', 'SINGLE'),
        DataField('ratioRaman',  'Ratio_Raman',  'SINGLE'),

        DataField('residualSource', 'Residual_Source', 'SINGLE'),
        DataField('residualRaman',  'Residual_Raman',  'SINGLE'),

        DataField('resSmoothBackscatter', 'ResSmooth_Backscatter', 'SINGLE'),
        DataField('resSmoothLidar',       'ResSmooth_Lidar',       'SINGLE'),
        DataField('resDeviateLidar',      'ResDeviate_Lidar',      'SINGLE'),

        DataField('profileDispersionBackscatter', 'BackscatterMin1Dispersion',
            'LONGBINARY', numpy.float32, required = False),
        DataField('profileDispersionLidar', 'LidarRatioDispersion',
            'LONGBINARY', numpy.float32, required = False),

        DataField('ratioDispersionSource', 'RatioDispersion_Source', 'SINGLE',
            required = False),
        DataField('ratioDispersionRaman', 'RatioDispersion_Raman', 'SINGLE',
            required = False),

        DataField('perturbanceEvaluations', 'PerturbanceEvaluations',
            'SMALLINT', required = False),

        DataField('whiteNoiseSource', 'WhiteNoise_Source', 'SINGLE',
            required = False),
        DataField('whiteNoiseRaman', 'WhiteNoise_Raman', 'SINGLE',
            required = False),

        DataField('linearNoiseSource', 'LinearNoise_Source', 'SINGLE',
            required = False),
        DataField('linearNoiseRaman', 'LinearNoise_Raman', 'SINGLE',
            required = False),
    ]

    createDatabaseErrorMessage = (
        'Raman retrieval output database file could not be created')
    openDatabaseErrorMessage = (
        'Raman retrieval output file is not a valid Access database or may '
        'not be opened for reading')
    queryFailureErrorMessage = (
        'Ramam retrieval output: failed to query data from the database file')
    invalidFormatErrorPrefix = (
        'Raman retrieval output file format is invalid: ')
    writeDataErrorMessage = (
        'Failed to write the data to the Raman retrieval output file')

    importDataFromExcelErrorPrefix = (
        'Failed to read the data from the Excel file')
    exportDataToExcelErrorPrefix = (
        'Failed to write the data to the Excel file')

    # ---- Public methods -----------------------------------------------------
    def onDataLoaded(self):
        # Calculate the vertical height step of the data grid.
        if self.gridStep is None or self.gridZenithAngle is None:
            self.gridHeigthStep = None
        else:
            self.gridHeightStep = (self.gridStep *
                math.cos(self.gridZenithAngle * math.pi / 180.0))

    def getErrorMessage(self):
        """Return an error message describing the malformed data or 'None' if
        all the attribute values are plausible and consistent."""

        # Check if some of the values were missing in the database.
        for name in self.getRequiredAttributeNames():
            if getattr(self, name) is None:
                return ('%s database field contains no data' %
                    txt.quote(self.getFieldName(name)))
