/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

#include "os/mynewt.h"

#if MYNEWT_VAL(SENSOR_OIC)

#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <assert.h>

#include "sensor/sensor.h"
#include "sensor/accel.h"
#include "sensor/mag.h"
#include "sensor/light.h"
#include "sensor/quat.h"
#include "sensor/euler.h"
#include "sensor/color.h"
#include "sensor/temperature.h"
#include "sensor/pressure.h"
#include "sensor/humidity.h"
#include "sensor/gyro.h"

/* OIC */
#include <oic/oc_rep.h>
#include <oic/oc_ri.h>
#include <oic/oc_ri_const.h>
#include <oic/oc_api.h>
#include <oic/messaging/coap/observe.h>

static const char g_s_oic_dn[] = "x.mynewt.snsr.";

static int
sensor_oic_encode(struct sensor* sensor, void *arg, void *databuf,
                  sensor_type_t type)
{

    if (!databuf) {
        return SYS_EINVAL;
    }

    switch(type) {
        /* Gyroscope supported */
        case SENSOR_TYPE_GYROSCOPE:

            if (((struct sensor_gyro_data *)(databuf))->sgd_x_is_valid) {
                oc_rep_set_double(root, x,
                    ((struct sensor_gyro_data *)(databuf))->sgd_x);
            } else {
                goto err;
            }
            if (((struct sensor_gyro_data *)(databuf))->sgd_y_is_valid) {
                oc_rep_set_double(root, y,
                    ((struct sensor_gyro_data *)(databuf))->sgd_y);
            } else {
                goto err;
            }
            if (((struct sensor_gyro_data *)(databuf))->sgd_z_is_valid) {
                oc_rep_set_double(root, z,
                    ((struct sensor_gyro_data *)(databuf))->sgd_z);
            } else {
                goto err;
            }
            break;

        /* Accelerometer functionality supported */
        case SENSOR_TYPE_ACCELEROMETER:
        /* Linear Accelerometer (Without Gravity) */
        case SENSOR_TYPE_LINEAR_ACCEL:
        /* Gravity Sensor */
        case SENSOR_TYPE_GRAVITY:

            if (((struct sensor_accel_data *)(databuf))->sad_x_is_valid) {
                oc_rep_set_double(root, x,
                    ((struct sensor_accel_data *)(databuf))->sad_x);
            } else {
                goto err;
            }
            if (((struct sensor_accel_data *)(databuf))->sad_y_is_valid) {
                oc_rep_set_double(root, y,
                    ((struct sensor_accel_data *)(databuf))->sad_y);
            } else {
                goto err;
            }
            if (((struct sensor_accel_data *)(databuf))->sad_z_is_valid) {
                oc_rep_set_double(root, z,
                    ((struct sensor_accel_data *)(databuf))->sad_z);
            } else {
                goto err;
            }
            break;

        /* Magnetic field supported */
        case SENSOR_TYPE_MAGNETIC_FIELD:
            if (((struct sensor_mag_data *)(databuf))->smd_x_is_valid) {
                oc_rep_set_double(root, x,
                    ((struct sensor_mag_data *)(databuf))->smd_x);
            } else {
                goto err;
            }
            if (((struct sensor_mag_data *)(databuf))->smd_y_is_valid) {
                oc_rep_set_double(root, y,
                    ((struct sensor_mag_data *)(databuf))->smd_y);
            } else {
                goto err;
            }
            if (((struct sensor_mag_data *)(databuf))->smd_z_is_valid) {
                oc_rep_set_double(root, z,
                    ((struct sensor_mag_data *)(databuf))->smd_z);
            } else {
                goto err;
            }
            break;
        /* Light supported */
        case SENSOR_TYPE_LIGHT:
            if (((struct sensor_light_data *)(databuf))->sld_ir_is_valid) {
                oc_rep_set_double(root, ir,
                    ((struct sensor_light_data *)(databuf))->sld_ir);
            } else {
                goto err;
            }
            if (((struct sensor_light_data *)(databuf))->sld_full_is_valid) {
                oc_rep_set_double(root, full,
                    ((struct sensor_light_data *)(databuf))->sld_full);
            } else {
                goto err;
            }
            if (((struct sensor_light_data *)(databuf))->sld_lux_is_valid) {
                oc_rep_set_double(root, lux,
                    ((struct sensor_light_data *)(databuf))->sld_lux);
            } else {
                goto err;
            }
            break;

        /* Temperature supported */
        case SENSOR_TYPE_TEMPERATURE:
            if (((struct sensor_temp_data *)(databuf))->std_temp_is_valid) {
                oc_rep_set_double(root, temp,
                    ((struct sensor_temp_data *)(databuf))->std_temp);
            }
            break;

        /* Ambient temperature supported */
        case SENSOR_TYPE_AMBIENT_TEMPERATURE:
            if (((struct sensor_temp_data *)(databuf))->std_temp_is_valid) {
                oc_rep_set_double(root, temp,
                    ((struct sensor_temp_data *)(databuf))->std_temp);
            }
            break;

        /* Pressure sensor supported */
        case SENSOR_TYPE_PRESSURE:
            if (((struct sensor_press_data *)(databuf))->spd_press_is_valid) {
                oc_rep_set_double(root, press,
                    ((struct sensor_press_data *)(databuf))->spd_press);
            }
            break;

        /* Relative humidity supported */
        case SENSOR_TYPE_RELATIVE_HUMIDITY:
            if (((struct sensor_humid_data *)(databuf))->shd_humid_is_valid) {
                oc_rep_set_double(root, humid,
                    ((struct sensor_humid_data *)(databuf))->shd_humid);
            }
            break;

        /* Rotation vector (quaternion) supported */
        case SENSOR_TYPE_ROTATION_VECTOR:
            if (((struct sensor_quat_data *)(databuf))->sqd_x_is_valid) {
                oc_rep_set_double(root, x,
                    ((struct sensor_quat_data *)(databuf))->sqd_x);
            } else {
                goto err;
            }
            if (((struct sensor_quat_data *)(databuf))->sqd_y_is_valid) {
                oc_rep_set_double(root, y,
                    ((struct sensor_quat_data *)(databuf))->sqd_y);
            } else {
                goto err;
            }
            if (((struct sensor_quat_data *)(databuf))->sqd_z_is_valid) {
                oc_rep_set_double(root, z,
                    ((struct sensor_quat_data *)(databuf))->sqd_z);
            } else {
                goto err;
            }
            if (((struct sensor_quat_data *)(databuf))->sqd_w_is_valid) {
                oc_rep_set_double(root, w,
                    ((struct sensor_quat_data *)(databuf))->sqd_w);
            } else {
                goto err;
            }
            break;

        /* Euler Orientation Sensor */
        case SENSOR_TYPE_EULER:
            if (((struct sensor_euler_data *)(databuf))->sed_h_is_valid) {
                oc_rep_set_double(root, h,
                    ((struct sensor_euler_data *)(databuf))->sed_h);
            } else {
                goto err;
            }
            if (((struct sensor_euler_data *)(databuf))->sed_r_is_valid) {
                oc_rep_set_double(root, r,
                    ((struct sensor_euler_data *)(databuf))->sed_r);
            } else {
                goto err;
            }
            if (((struct sensor_euler_data *)(databuf))->sed_p_is_valid) {
                oc_rep_set_double(root, p,
                    ((struct sensor_euler_data *)(databuf))->sed_p);
            } else {
                goto err;
            }
            break;

        /* Color Sensor */
        case SENSOR_TYPE_COLOR:
            if (((struct sensor_color_data *)(databuf))->scd_r_is_valid) {
                oc_rep_set_uint(root, r,
                    ((struct sensor_color_data *)(databuf))->scd_r);
            } else {
                goto err;
            }
            if (((struct sensor_color_data *)(databuf))->scd_g_is_valid) {
                oc_rep_set_uint(root, g,
                    ((struct sensor_color_data *)(databuf))->scd_g);
            } else {
                goto err;
            }
            if (((struct sensor_color_data *)(databuf))->scd_b_is_valid) {
                oc_rep_set_uint(root, b,
                    ((struct sensor_color_data *)(databuf))->scd_b);
            } else {
                goto err;
            }
            if (((struct sensor_color_data *)(databuf))->scd_lux_is_valid) {
                oc_rep_set_uint(root, lux,
                    ((struct sensor_color_data *)(databuf))->scd_lux);
            } else {
                goto err;
            }
            if (((struct sensor_color_data *)(databuf))->scd_colortemp_is_valid) {
                oc_rep_set_uint(root, colortemp,
                    ((struct sensor_color_data *)(databuf))->scd_colortemp);
            } else {
                goto err;
            }
            if (((struct sensor_color_data *)(databuf))->scd_saturation_is_valid) {
                oc_rep_set_uint(root, saturation,
                    ((struct sensor_color_data *)(databuf))->scd_saturation);
            } else {
                goto err;
            }
            if (((struct sensor_color_data *)(databuf))->scd_saturation75_is_valid) {
                oc_rep_set_uint(root, saturation75,
                    ((struct sensor_color_data *)(databuf))->scd_saturation75);
            } else {
                goto err;
            }
            if (((struct sensor_color_data *)(databuf))->scd_is_sat_is_valid) {
                oc_rep_set_uint(root, is_sat,
                    ((struct sensor_color_data *)(databuf))->scd_is_sat);
            } else {
                goto err;
            }
            if (((struct sensor_color_data *)(databuf))->scd_cratio_is_valid) {
                oc_rep_set_double(root, cratio,
                    ((struct sensor_color_data *)(databuf))->scd_cratio);
            } else {
                goto err;
            }
            if (((struct sensor_color_data *)(databuf))->scd_maxlux_is_valid) {
                oc_rep_set_uint(root, maxlux,
                    ((struct sensor_color_data *)(databuf))->scd_maxlux);
            } else {
                goto err;
            }
            if (((struct sensor_color_data *)(databuf))->scd_ir_is_valid) {
                oc_rep_set_uint(root, ir,
                    ((struct sensor_color_data *)(databuf))->scd_ir);
            } else {
                goto err;
            }
            break;

        /* Support for these sensors is currently not there, hence
         * encoding would fail for them
         */

        /* Proximity sensor supported */
        case SENSOR_TYPE_PROXIMITY:
        /* Altitude Supported */
        case SENSOR_TYPE_ALTITUDE:
        /* Weight Supported */
        case SENSOR_TYPE_WEIGHT:
        /* User defined sensor type 1 */
        case SENSOR_TYPE_USER_DEFINED_1:
        /* User defined sensor type 2 */
        case SENSOR_TYPE_USER_DEFINED_2:
        /* User defined sensor type 3 */
        case SENSOR_TYPE_USER_DEFINED_3:
        /* User defined sensor type 4 */
        case SENSOR_TYPE_USER_DEFINED_4:
        /* User defined sensor type 5 */
        case SENSOR_TYPE_USER_DEFINED_5:
        /* User defined sensor type 6 */
        case SENSOR_TYPE_USER_DEFINED_6:

        /* None */
        case SENSOR_TYPE_NONE:
        default:
            goto err;
    }

    oc_rep_set_uint(root, ts_secs, (long int)sensor->s_sts.st_ostv.tv_sec);
    oc_rep_set_int(root, ts_usecs, (int)sensor->s_sts.st_ostv.tv_usec);
    oc_rep_set_uint(root, ts_cputime, (unsigned int)sensor->s_sts.st_cputime);

    return 0;
err:
    return SYS_EINVAL;
}

static int
sensor_typename_to_type(char *typename, sensor_type_t *type,
                        struct sensor *sensor)
{
    if (!strcmp(typename, "acc")) {
        /* Accelerometer functionality supported */
        *type = SENSOR_TYPE_ACCELEROMETER;
    } else if (!strcmp(typename, "mag")) {
        /* Magnetic field supported */
        *type = SENSOR_TYPE_MAGNETIC_FIELD;
    } else if (!strcmp(typename, "gyr")) {
        /* Gyroscope supported */
        *type = SENSOR_TYPE_GYROSCOPE;
    } else if (!strcmp(typename, "lt")) {
        /* Light supported */
        *type = SENSOR_TYPE_LIGHT;
    } else if (!strcmp(typename, "tmp")) {
        /* Temperature supported */
        *type = SENSOR_TYPE_TEMPERATURE;
    } else if (!strcmp(typename, "ambtmp")) {
        /* Ambient temperature supported */
        *type = SENSOR_TYPE_AMBIENT_TEMPERATURE;
    } else if (!strcmp(typename, "psr")) {
        /* Pressure sensor supported */
        *type = SENSOR_TYPE_PRESSURE;
    } else if (!strcmp(typename, "prox")) {
        /* Proximity sensor supported */
        *type = SENSOR_TYPE_PROXIMITY;
    } else if (!strcmp(typename, "rhmty")) {
        /* Relative humidity supported */
        *type = SENSOR_TYPE_RELATIVE_HUMIDITY;
    } else if (!strcmp(typename, "quat")) {
        /* Rotation vector (quaternion)) supported */
        *type = SENSOR_TYPE_ROTATION_VECTOR;
    } else if (!strcmp(typename, "alt")) {
        /* Altitude Supported */
        *type = SENSOR_TYPE_ALTITUDE;
    } else if (!strcmp(typename, "wt")) {
        /* Weight Supported */
        *type = SENSOR_TYPE_WEIGHT;
    } else if (!strcmp(typename, "lacc")) {
        /* Linear Accelerometer (Without Gravity)) */
        *type = SENSOR_TYPE_LINEAR_ACCEL;
    } else if (!strcmp(typename, "grav")) {
        /* Gravity Sensor */
        *type = SENSOR_TYPE_GRAVITY;
    } else if (!strcmp(typename, "eul")) {
        /* Euler Orientation Sensor */
        *type = SENSOR_TYPE_EULER;
    } else if (!strcmp(typename, "col")) {
        /* Color Sensor */
        *type = SENSOR_TYPE_COLOR;
    } else if (!strcmp(typename, "udef1")) {
        /* User defined sensor type 1 */
        *type = SENSOR_TYPE_USER_DEFINED_1;
    } else if (!strcmp(typename, "udef2")) {
        /* User defined sensor type 2 */
        *type = SENSOR_TYPE_USER_DEFINED_2;
    } else if (!strcmp(typename, "udef3")) {
        /* User defined sensor type 3 */
        *type = SENSOR_TYPE_USER_DEFINED_3;
    } else if (!strcmp(typename, "udef4")) {
        /* User defined sensor type 4 */
        *type = SENSOR_TYPE_USER_DEFINED_4;
    } else if (!strcmp(typename, "udef5")) {
        /* User defined sensor type 5 */
        *type = SENSOR_TYPE_USER_DEFINED_5;
    } else if (!strcmp(typename, "udef6")) {
        /* User defined sensor type 6 */
        *type = SENSOR_TYPE_USER_DEFINED_6;
    } else {
        goto err;
    }

    return SYS_EOK;
err:
    return SYS_EINVAL;
}

static int
sensor_type_to_typename(char **typename, sensor_type_t type,
                        struct sensor *sensor)
{

    if (!sensor_mgr_match_bytype(sensor, (void *)&type)) {
        goto err;
    }

    switch(type) {
        /* Accelerometer functionality supported */
        case SENSOR_TYPE_ACCELEROMETER:
            *typename =  "acc";
            break;
        /* Magnetic field supported */
        case SENSOR_TYPE_MAGNETIC_FIELD:
            *typename = "mag";
            break;
        /* Gyroscope supported */
        case SENSOR_TYPE_GYROSCOPE:
            *typename = "gyr";
            break;
        /* Light supported */
        case SENSOR_TYPE_LIGHT:
            *typename = "lt";
            break;
        /* Temperature supported */
        case SENSOR_TYPE_TEMPERATURE:
            *typename = "tmp";
            break;
        /* Ambient temperature supported */
        case SENSOR_TYPE_AMBIENT_TEMPERATURE:
            *typename = "ambtmp";
            break;
        /* Pressure sensor supported */
        case SENSOR_TYPE_PRESSURE:
            *typename = "psr";
            break;
        /* Proximity sensor supported */
        case SENSOR_TYPE_PROXIMITY:
            *typename = "prox";
            break;
        /* Relative humidity supported */
        case SENSOR_TYPE_RELATIVE_HUMIDITY:
            *typename = "rhmty";
            break;
        /* Rotation vector (quaternion)) supported */
        case SENSOR_TYPE_ROTATION_VECTOR:
            *typename = "quat";
            break;
        /* Altitude Supported */
        case SENSOR_TYPE_ALTITUDE:
            *typename = "alt";
            break;
        /* Weight Supported */
        case SENSOR_TYPE_WEIGHT:
            *typename = "wt";
            break;
        /* Linear Accelerometer (Without Gravity)) */
        case SENSOR_TYPE_LINEAR_ACCEL:
            *typename = "lacc";
            break;
        /* Gravity Sensor */
        case SENSOR_TYPE_GRAVITY:
            *typename = "grav";
            break;
        /* Euler Orientation Sensor */
        case SENSOR_TYPE_EULER:
            *typename = "eul";
            break;
        /* Color Sensor */
        case SENSOR_TYPE_COLOR:
            *typename = "col";
            break;
        /* User defined sensor type 1 */
        case SENSOR_TYPE_USER_DEFINED_1:
            *typename = "udef1";
            break;
        /* User defined sensor type 2 */
        case SENSOR_TYPE_USER_DEFINED_2:
            *typename = "udef2";
            break;
        /* User defined sensor type 3 */
        case SENSOR_TYPE_USER_DEFINED_3:
            *typename = "udef3";
            break;
        /* User defined sensor type 4 */
        case SENSOR_TYPE_USER_DEFINED_4:
            *typename = "udef4";
            break;
        /* User defined sensor type 5 */
        case SENSOR_TYPE_USER_DEFINED_5:
            *typename = "udef5";
            break;
        /* User defined sensor type 6 */
        case SENSOR_TYPE_USER_DEFINED_6:
            *typename = "udef6";
            break;
        default:
            goto err;
    }

    return SYS_EOK;
err:
    return SYS_EINVAL;
}

static void
sensor_oic_get_data(oc_request_t *request, oc_interface_mask_t interface)
{
    int rc;
    struct sensor *sensor;
    char *devname;
    char *typename;
    sensor_type_t type;
    char tmpstr[COAP_MAX_URI] = {0};
    const char s[2] = "/";
    char *tok_ptr;

    memcpy(tmpstr, (char *)&(request->resource->uri.os_str[1]),
           request->resource->uri.os_sz - 1);

    /* Parse the sensor device name from the uri  */
    devname = strtok_r(tmpstr, s, &tok_ptr);

    (void)strtok_r(NULL, s, &tok_ptr);

    /* Look up sensor by name */
    sensor = sensor_mgr_find_next_bydevname(devname, NULL);
    if (!sensor) {
        rc = SYS_EINVAL;
        goto err;
    }

    if (memcmp(g_s_oic_dn, request->resource->types.oa_arr.s,
               sizeof(g_s_oic_dn) - 1)) {
        goto err;
    }

    oc_rep_start_root_object();

    switch (interface) {
    case OC_IF_BASELINE:
        oc_process_baseline_interface(request->resource);
    case OC_IF_R:

        typename =
            &(request->resource->types.oa_arr.s[sizeof(g_s_oic_dn) - 1]);
        rc = sensor_typename_to_type(typename, &type, sensor);
        if (rc) {
            /* Type either not supported by sensor or not found */
            goto err;
        }

        rc = sensor_read(sensor, type, sensor_oic_encode,
                         (uintptr_t *)SENSOR_IGN_LISTENER,
                         OS_TIMEOUT_NEVER);
        if (rc) {
            goto err;
        }

        break;
    default:
        break;
    }

    oc_rep_end_root_object();
    oc_send_response(request, OC_STATUS_OK);
    return;
err:
    oc_send_response(request, OC_STATUS_NOT_FOUND);
}

/**
 * Transmit OIC trigger
 *
 * @param ptr to the sensor
 * @param ptr to sensor data
 * @param The sensor type
 *
 * @return 0 on sucess, non-zero on failure
 */
int
sensor_oic_tx_trigger(struct sensor *sensor, void *arg, sensor_type_t type)
{
    oc_request_t request = {};
    oc_response_t response = {};
    oc_response_buffer_t response_buffer;
    struct os_mbuf *m = NULL;
    struct sensor_type_traits *stt;
    int rc;

    (void)request;

    memset(&response_buffer, 0, sizeof(response_buffer));
    stt = sensor_get_type_traits_bytype(type, sensor);
    if (!stt->stt_oic_res->num_observers) {
        return 0;
    }

    m = os_msys_get_pkthdr(0, 0);
    if (!m) {
        rc = SYS_EINVAL;
        goto err;
    }

    response_buffer.buffer = m;
    response_buffer.block_offset = NULL;
    response.response_buffer = &response_buffer;
    request.resource = stt->stt_oic_res;
    request.response = &response;
    oc_rep_new(m);
    oc_rep_start_root_object();
    rc = sensor_oic_encode(sensor, NULL, arg, type);
    if (rc) {
        goto err;
    }
    oc_rep_end_root_object();
    oc_send_response(&request, OC_STATUS_OK);
    coap_notify_observers(stt->stt_oic_res, &response_buffer, NULL);
    os_mbuf_free_chain(m);

    return 0;
err:
    return rc;
}

static int
sensor_oic_add_resource(struct sensor *sensor, sensor_type_t type)
{
    char *typename;
    char tmpstr[COAP_MAX_URI];
    struct sensor_type_traits *stt;
    int rc;

    stt = sensor_get_type_traits_bytype(type, sensor);
    if (!stt) {
        stt = malloc(sizeof(struct sensor_type_traits));
        memset(stt, 0, sizeof(struct sensor_type_traits));
        sensor_lock(sensor);
        SLIST_INSERT_HEAD(&sensor->s_type_traits_list, stt, stt_next);
        sensor_unlock(sensor);
    }

    typename = NULL;

    rc = sensor_type_to_typename(&typename, type, sensor);
    if (rc) {
        return rc;
    }

    memset(tmpstr, 0, sizeof(tmpstr));
    snprintf(tmpstr, sizeof(tmpstr), "/%s/%s",
             sensor->s_dev->od_name, typename);

    sensor_lock(sensor);

    stt->stt_sensor_type = type;

    stt->stt_oic_res = oc_new_resource(tmpstr, 1, 0);

    memset(tmpstr, 0, sizeof(tmpstr));
    snprintf(tmpstr, sizeof(tmpstr), "%s%s", g_s_oic_dn, typename);
    oc_resource_bind_resource_type(stt->stt_oic_res, tmpstr);
    oc_resource_bind_resource_interface(stt->stt_oic_res, OC_IF_R);
    oc_resource_set_default_interface(stt->stt_oic_res, OC_IF_R);

    oc_resource_set_discoverable(stt->stt_oic_res);

#if MYNEWT_VAL(SENSOR_OIC_PERIODIC)
    oc_resource_set_periodic_observable_ms(stt->stt_oic_res,
                                           MYNEWT_VAL(SENSOR_OIC_OBS_RATE));
#else
    oc_resource_set_observable(stt->stt_oic_res);
#endif
    oc_resource_set_request_handler(stt->stt_oic_res, OC_GET,
                                    sensor_oic_get_data);

    oc_add_resource(stt->stt_oic_res);

    sensor_unlock(sensor);

    return 0;
}

/*
 * Iterates through the sensor list and initializes OIC resources
 * based on each sensor type
 */
void
sensor_oic_init(void)
{
    struct sensor *sensor;
    sensor_type_t type;
    int i;
    int rc;

    sensor = NULL;

    while (1) {

        sensor_mgr_lock();

        sensor = sensor_mgr_find_next_bytype(SENSOR_TYPE_ALL, sensor);

        sensor_mgr_unlock();

        if (sensor == NULL) {
            /* Sensor not found */
            break;
        }

        i = 0;

        /* Iterate through N types of sensors */
        while (i < 32) {
            type = (1 << i);
            if (sensor_mgr_match_bytype(sensor, &type)) {
                rc = sensor_oic_add_resource(sensor, type);
                if (rc) {
                    break;
                }
                sensor_trigger_init(sensor, type, sensor_oic_tx_trigger);
            }
            i++;
        }
    }
}

#endif
