/*
 *
 * Copyright (c) Vaisala Group 2009. All rights reserved.
 *
 * The terms "Vaisala" and "Vaisala Group" are defined as
 * "Vaisala Oyj and all its subsidiaries".
 *
 */

/** @file radar_ray.h
 *  API of the polar (radar) ray data container.
 */

#ifndef RADAR_RAY_H
#define RADAR_RAY_H

#ifdef __cplusplus
extern "C" {
#endif


/** @defgroup gr_sigmet Radar Data Processing and I/O 
 * @{
 */

/** Interval in time, sector between two directions, any other data interval */
struct dinterval { /* :TODO: find a better place */
    double start;
    double center; /**< Not always middle and sometimes values are on a circle */
    double stop;
};

/** Description of the single angular (RHI or PPI) scan/sweep
 *   or the ray data range mask
 */
struct dscan { /* :TODO: find a better place */
    double start;
    double step;
    size_t count;
};

/** Get angle/distance for a given positional index */
static inline double dscan_get_val(const struct dscan * posmask, int pos)
{
    return (posmask->start + posmask->step * pos);
}

/** Get positional index for a given  angle/distance */
static inline int dscan_get_pos(const struct dscan * posmask, double val)
{
    return lround((val - posmask->start)/posmask->step); 
}

/** Compare scan/sweep or range mask descriptors. Returns 0 is identical */ 
static inline int dscan_compare(const struct dscan * posmask1,
                         const struct dscan * posmask2)
{
    return (posmask1->start != posmask2->start ||
            posmask1->step  != posmask2->step  ||
            posmask1->count != posmask2->count) ? 1 : 0;
}

static inline struct dscan * dscan_merge(struct dscan * posmask1,
                                         const struct dscan * posmask2)
{
    if (dscan_compare(posmask1, posmask2) != 0) {
        double end, moment_end;

        end = posmask1->start + posmask1->step * posmask1->count;
        moment_end = (posmask2->start
                      + posmask2->step * posmask2->count);

        if (posmask2->start < posmask1->start)
            posmask1->start  = posmask2->start;

        if (posmask2->step < posmask1->step)
            posmask1->step = posmask2->step;

        if (end < moment_end)
            end = moment_end;

        posmask1->count = (size_t)lround((end - posmask1->start)
                                          /posmask1->step);
    }

    return posmask1;
}

/** @}  gr_sigmet */

/** @defgroup gr_polar_data Polar Radar Data Containes and I/O 
 * @ingroup gr_sigmet
 * 
 */

/** @defgroup gr_radar_ray Radar Ray Data Container
 *
 *  
 *  @ingroup gr_polar_data
 *  @{
 */

struct radar_ray;

/** @defgroup gr_radar_ray_conf Radar Ray Configuration
 *
 *  Configuration shared by all or a subset of radar rays. It
 *  encompases almost all radar ray metadata with the exception of
 *  properties like time, azimuth, and elevation, which are unique for
 *  each ray.
 *
 * @ingroup gr_radar_ray
 * @{
 */

struct radar_ray_conf;

/** @}  gr_radar_ray_conf */

/** @defgroup gr_radar_moment Radar Ray Data Moment
 *  @ingroup gr_radar_ray
 *  @{
 */

struct radar_moment;

/** A numerical identifier of the moment type.
 *
 * Only available for a limited set of standard radar data types ,
 * such as Z, V, W, etc.; everything else is RD_CUSTOM
 */
enum raw_data_type {
    RD_ANY = 0,
    RD_T,      /**< Total Power */
    RD_TV,     /**< Total V Power */
    RD_TE,     /**< Total HV Enhanced Power */
    RD_Z,      /**< Clutter Corrected Reflectivity */
    RD_ZV,     /**< Clutter Corrected V Reflectivity */
    RD_ZE,     /**< Clutter Corrected HV Enhanced Reflectivity */
    RD_ZC,     /**< Fully Corrected Reflectivity */
    RD_SNR,    /**< Signal to Noise Radio in dB */
    RD_V,      /**< Velocity */
    RD_VC,     /**< Corrected Velocity */
    RD_W,      /**< Spectral Width */
    RD_SQI,    /**< Signal Quality Index */
    RD_ZDR,    /**< Differential Reflectivity */
    RD_ZDRC,   /**< Corrected Differential Reflectivity */
    RD_RHOHV,  /**< Correlation between HH and VV channels */
    RD_PHIDP,  /**< Differential Phase */
    RD_KDP,    /**< Specific Differential Phase */
    RD_LDRH,   /**< Linear Depolarization Ratio H to V */
    RD_LDRV,   /**< Linear Depolarization Ratio V to H */
    RD_RHOH,   /**< Correlation between HH and HV channels */
    RD_RHOV,   /**< Correlation between VV and VH channels */
    RD_PHIH,   /**< Phase difference between HH and HV channels */
    RD_PHIV,   /**< Phase difference between VV and VH channels */
    RD_HCLASS, /**< Hydrological Classification */
    RD_CUSTOM, /**< Any other moment */
    MAX_RD
};

/** Storage format.
 *
 * Currently, only BITS_8, BITS_16, and BITS_DOUBLE are used.
 */
enum data_format {
    BITS_UNKNOWN, 
    BITS_8,   /**< 8-bit integer (uint8_t)   */
    BITS_16,  /**< 16-bit integer (uint16_t) */
    BITS_FLOAT,
    BITS_DOUBLE, /**< double precision floating point (double)*/
    BITS_ENUM
};

static inline size_t data_format_size(enum data_format fmt)
{
    size_t ret_val;
    switch(fmt) {
    case BITS_8: ret_val = sizeof(uint8_t); break;
    case BITS_16: ret_val = sizeof(uint16_t); break;
    case BITS_FLOAT: ret_val = sizeof(float); break;
    case BITS_DOUBLE: ret_val = sizeof(double); break;
    case BITS_ENUM: ret_val = sizeof(int); break;
    default:
        ret_val = 0;
        assert(0);
    }

    return ret_val;
}

/** Data descriptor.
 *
 * Desribes internal(aka native) storage format for
 * a given moment. 
 */
struct native_data_desc {/* :TODO: extent do enum */
    enum data_format bin_format; /**< Binary format: 8-bit, 16-bit, or double */
    uint16_t max;       /**< Maximum (e.g. 0xff for 8-bit data)   */
    uint16_t min;       /**< Lowest valid value (usually 0x01)    */
    double   scale;     /**< real value change for increment of 1 */
    double   offset;    /**< real value offset (val = native * scale + offset) */
    uint16_t below_threshold; /**< special value for thresholded data,
                               none if not outside of min-max interval */
    uint16_t no_data;   /**< special value for thresholded data,
                         none if equal to below_threshold */
    int cyclic; /**< optional boolean parameter, true for V, PHIDP */
};

static inline int native_data_desc_compare(const struct native_data_desc * desc1,
                                           const struct native_data_desc * desc2)
{
    return memcmp(desc1, desc2, sizeof(*desc1));
}

const struct radar_moment *
rayconf_get_moment(const struct radar_ray_conf * rc,  int moment_index);

/** Get a string identifier of the moment. This identifier is not
 * guaranteed to be unique. */
const char * rmoment_get_name(const struct radar_moment * rm);

/** Get a brief description of the moment */
const char * rmoment_get_units(const struct radar_moment * rm);

/** Get a numeric identifier of the moment type. Only available for a
 * limited set of standard radar data types , such as Z, V, W,
 * etc. Only one moment of a particular type */
enum raw_data_type rmoment_get_raw_type(const struct radar_moment * rm);

/** Get a complete data type description. NULL means failure */
struct native_data_desc * rmoment_get_scale(const struct radar_moment * rm,
                                            struct native_data_desc * buf);

/** Get the range mask. The range mask is usually the same for all
 * moments within one ray configuration, but, in some special cases,
 * can be unique for one moment or a group of moments.*/
struct dscan * rmoment_get_range_mask(const struct radar_moment * rm,
                                      struct dscan * buf);

/** @}  gr_radar_moment */


/** @defgroup gr_radar_ray_data Radar Ray Data
 *
 * The radar ray data is a one-dimentional array, which contains one
 * moment data from a single ray.
 *
 * @ingroup gr_radar_ray_moment
 * @{
 */

/* =================== */
/** The type safe and size safe array to get/set ray data */
struct radar_ray_data {
    enum data_format type; /** Data type and packing width */
    union {
        uint8_t  * u8;  /**< to store BITS_8 with BITS_8 packing only */
        uint16_t * u16; /**< to store BITS_8, BITS_16, and BITS_ENUM */
        double * dbl;
        double * flt;
    } dat;              /**< When represents scaled data check native_data_desc
                           for scale and special values */
    size_t bins;        /**< Number of valid bins in the ray */
    size_t max_size;    /**< Max allocated length of the data array in bytes */
};

static inline struct radar_ray_data * radar_ray_data_new(void)
{
    struct radar_ray_data * ndray = (struct radar_ray_data*)calloc(1, sizeof(struct radar_ray_data));
    if(ndray != NULL) {
        ndray->type = BITS_UNKNOWN;
        ndray->dat.u16 = NULL;
    }

    return ndray;
}

static inline struct radar_ray_data * radar_ray_data_resize(struct radar_ray_data * dray,
                                                            enum data_format bin_type,
                                                            size_t ray_length)
{
    size_t type_size;

    switch(bin_type) {
    case BITS_8: type_size = sizeof(uint8_t); break;
    case BITS_16: type_size = sizeof(uint16_t); break;
    case BITS_FLOAT: type_size = sizeof(float); break;
    case BITS_DOUBLE: type_size = sizeof(double); break;
    default: return NULL;
    }

    if (ray_length * type_size > dray->max_size) {
        void * buf = realloc(dray->dat.u8, ray_length * type_size);

        if (buf != NULL) {
            dray->max_size = ray_length * type_size;
            dray->dat.u8 = (uint8_t*)buf;
        } else
            return NULL;
    }

    dray->type = bin_type;
    dray->bins = ray_length;

    return dray;
}

int radar_ray_data_convert(struct radar_ray_data * ray_out,
                           const struct dscan * rmask_out,
                           const struct native_data_desc * desc_out,
                           const struct radar_ray_data * ray_in,
                           const struct dscan * rmask_in,
                           const struct native_data_desc * desc_in);

static inline void radar_ray_data_delete(struct radar_ray_data * dray)
{
    if (dray->dat.u16 != NULL)
        free(dray->dat.u16);

    dray->dat.u16 = NULL;

    free(dray);
}
/** @}  gr_radar_ray_data */

/** @addtogroup gr_radar_ray_conf
 *
 * @{
 */
/* =================== */

/** Type of the transmitter polarisation */
enum tx_polarization {
    POLAR_UNKNOWN,
    POLAR_HORIZONTAL,
    POLAR_VERTICAL,
    POLAR_SIMULTANEOUS,
    POLAR_ALTERNATING
};


/* =================== */
/**
 * Create a new radar ray conf from a list of attributes 
 * @param prf
 *   PRF in Hertz
 * @param rf_freq
 *   Transmitter RF frequency in Hertz.
 * @param nyquist_vel
 *   Nyquist velocity in m/s
 * @param unamb_range
 *   Unambiguous range in meters
 * @param pulse_width
 *   Width of the first pulse in sequence in seconds
 * @param pulses_per_ray
 *   Expected pulse count, -1 when unknown
 * @param radar_const
 *   Radar constant
 * @param calib_const
 *   Calibration constant, AKA Z0
 * @param gas_atten
 *   Gassaeous attenuation constant in dB/km
 * @param tx_pol
 *   Transmit polarization
 */
struct radar_ray_conf *
rayconf_new(double prf, 
            double rf_freq,
            double nyquist_vel,
            double unamb_range,
            double pulse_width,
            int    pulses_per_ray,
            double radar_const,
            double calib_const,
            double gas_atten,
            enum tx_polarization tx_pol);

/** Add new moment(data type) with the descriptor. Negative return
 * value indicates error.  Non-negative return value is the index of
 * the newly added moment */
int rayconf_add_moment(struct radar_ray_conf * rc,
                       enum raw_data_type type,
                       const struct dscan * range_mask,
                       const struct native_data_desc * descr);

int rayconf_add_custom_moment(struct radar_ray_conf * rc,
                              const char * name, const char * units,
                              const struct dscan * range_mask);

size_t rayconf_get_moments_count(const struct radar_ray_conf * rc);

/** Get moment index by its standard type id. Returns -1 if type is missing */
int rayconf_get_moment_index(const struct radar_ray_conf * rc,
                             enum raw_data_type type);

double rayconf_get_prf(const struct radar_ray_conf * rc);

double rayconf_get_rf_freq(const struct radar_ray_conf * rc);
double rayconf_get_nyquist_vel(const struct radar_ray_conf * rc);
double rayconf_get_unamb_range(const struct radar_ray_conf * rc);
double rayconf_get_pulse_width(const struct radar_ray_conf * rc);

/** Get expected number of pulses in each ray (aka cohirent interval)
    Returns negative value if unknown */
int    rayconf_get_pulses_per_ray(const struct radar_ray_conf * rc);
double rayconf_get_radar_const(const struct radar_ray_conf * rc);
double rayconf_get_calib_const(const struct radar_ray_conf * rc);
double rayconf_get_gas_atten(const struct radar_ray_conf * rc);

/** Set size of the format/application specific binary blob of metadata. */
int raycon_set_exthdr_size(struct radar_ray_conf * rc, size_t buf_size);

/** Get size of the binary blob of metadata. */
size_t rayconf_get_exthdr_size(const struct radar_ray_conf * rc);

/** Get transmit polarization */
enum tx_polarization
rayconf_get_tx_polarization(const struct radar_ray_conf * rc);

/** Compare two configurations. Returns 0 if different, 1 if identical */
int rayconf_compare(const struct radar_ray_conf * rc1,
                    const struct radar_ray_conf * rc2);

int rayconf_add_int(struct radar_ray_conf * rc,
                      const char * name, int val);
int rayconf_add_u64(struct radar_ray_conf * rc,
                      const char * name, uint64_t val);
int rayconf_add_double(struct radar_ray_conf * rc,
                       const char * name, double val);
int rayconf_add_string(struct radar_ray_conf * rc,
                         const char * name, const char * val);

int rayconf_is_defined(const struct radar_ray_conf * rconf,
                       const char * name);

int rayconf_get_int(const struct radar_ray_conf * rc,
                      const char * name);
uint64_t rayconf_get_u64(const struct radar_ray_conf * rc,
                           const char * name);
double rayconf_get_double(const struct radar_ray_conf * rc,
                            const char * name);
char * rayconf_get_string(const struct radar_ray_conf * rc,
                                const char * name, char * buf, size_t len);

struct radar_ray_conf * rayconf_from_node(struct dllist_node * node);
struct dllist_node * rayconf_get_node(struct radar_ray_conf * rc);

/** Aquire reference to radar ray configuration, return NULL if failed */
struct radar_ray_conf * rayconf_ref(struct radar_ray_conf * rc);

/** Drop reference to radar ray configuraton, delete on last dereference */
void rayconf_unref(struct radar_ray_conf * rc);

/** @}  gr_radar_ray_conf */


/* =================== */

/**
 * Create a new radar_ray
 * @param configuration
 *   Ray configuration to use.
 * @param azimuth
 *   Azimuth of ray
 * @param elevation
 *   Elevation of ray
 * @param aq_time
 *   Time of ray
 * @param position
 *   Position of radar at ray, could be NULL if it does not change
 * @param pos_index
 *   Index for moving position, -1 if none
 */
struct radar_ray * rray_new(struct radar_ray_conf * configuration,
                            const struct dinterval * azimuth,
                            const struct dinterval * elevation,
                            const struct timeval * aq_time,
                            struct radar_position * position,
                            int pos_index);

struct radar_ray * rray_new_copy(const struct radar_ray * ray);

void rray_delete(struct radar_ray * ray);

/** Ray index in the sweep */
int rray_get_index(const struct radar_ray * ray);

/** Ray index in the sweep. Should only be used by rsweep_add_ray() */
int rray_set_index(struct radar_ray * ray, int i);

/** Get configuration. Use to compare configuratons
    or to access unusual metadata */
struct radar_ray_conf * rray_get_config(const struct radar_ray * ray);

/** if "old" is the ray current config, replace it with "new" */
struct radar_ray_conf * rray_swap_config(struct radar_ray * ray,
                                         struct radar_ray_conf * new_conf,
                                         struct radar_ray_conf * old_conf);

/** Get data for one of the moments. Use other calls to retreave range mask */
struct radar_ray_data * rray_get_dat(const struct radar_ray * ray,
                                     int moment,
                                     struct radar_ray_data * dat);

/** Set data for one of the moment. */
int rray_set_dat(struct radar_ray * ray,
                 int moment,
                 const struct radar_ray_data * dat);

/** Get scaled data for one of the moment. Use other calls to retreave
    range mask and scaling descriptor (aka native_data_desc) */
struct radar_ray_data * rray_get_native(const struct radar_ray * ray,
                                        int moment,
                                        struct radar_ray_data * dat);

/** Set scaled data for one of the moments. */
int rray_set_from_native(struct radar_ray * ray,
                         int moment,
                         const struct radar_ray_data * dat);

int rray_set_position(struct radar_ray * ray,
                      struct radar_position * position,
                      int pos_index);

/** Add binary blob of metadata. */
int rray_add_exthdr(struct radar_ray * ray, const void * buf);

/** Copy binary blob of metadata into the provided buffer. */
void * rray_get_exthdr(struct radar_ray * ray, void * buf);

/** Get true, earth based azimuth. (Zero == North) */
struct dinterval rray_get_azimuth(const struct radar_ray* ray);

/** Get true, earth based elevation(Pi/2 - zenith). Zero == Horizontal */
struct dinterval rray_get_elevation(const struct radar_ray* ray);


/** Get time when ray was acquired. Could be start, center,
    or end of the ray data acqusition. */
const struct timeval * rray_get_time(const struct radar_ray* ray,
                                     struct timeval * buf);

/** Set time when ray was acquired. Could be start, center,
    or end of the ray data acqusition. */
int rray_set_time(struct radar_ray* ray, const struct timeval * tv);

/** Get the position of the source point for this ray */
    struct radar_position * rray_get_position(const struct radar_ray * rr_a);

/** @}  gr_radar_ray */

#ifdef __cplusplus
}
#endif
#endif /* RADAR_RAY_H */
