/*
 * This file is part of the weatherdaemon project.
 * Copyright 2026 Edward V. Emelianov <edward.emelianoff@gmail.com>.
 *
 * 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 3 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, see <http://www.gnu.org/licenses/>.
 */

// collect here weather from all weatherstations sorted by importance

#include <usefull_macros.h>

#include "mainweather.h"
#include "sensors.h"
#include "weathlib.h"

// wind speed history array size (not less than for one hour)
#define MAX_HISTORY     3600
// throw out data older than 24 hours
#define TOO_OLD_DATA    86400
// one hour
#define T_ONE_HOUR      3600

static pthread_mutex_t datamutex = PTHREAD_MUTEX_INITIALIZER;
static int Forbidden = 0;

// index of meteodata in array
enum{
    NWIND,
    NWINDMAX,
    NWINDMAX1,
    NWINDDIR,
    NWINDDIR1,
    NWINDDIR2,
    NHUMIDITY,
    NAMB_TEMP,
    NPRESSURE,
    NPRECIP,
    NPRECIP_LEVEL,
    NMIST,
    NCLOUDS,
    NSKYTEMP,
    NCOMMWEATH,
    NLASTAHTUNG,
    NAHTUNGRSN,
//    NLIGHTDIST,
    NBADWEATH,
    NTERRWEATH,
    NFORCEDSHTDN,
    NAMOUNT_OF_DATA
};

// starting sense values are VAL_BROKEN except of calculated values
// they would be changed later in `fix_new_data` to lowest level
static val_t collected_data[NAMOUNT_OF_DATA] = {
    [NWIND] = {.sense = VAL_BROKEN, .type = VALT_FLOAT, .meaning = IS_WIND},
    [NWINDMAX] = {.sense = VAL_BROKEN, .type = VALT_FLOAT, .meaning = IS_OTHER, .name = "WINDMAX", .comment = "Maximal wind speed for last 24 hours"},
    [NWINDMAX1] = {.sense = VAL_BROKEN, .type = VALT_FLOAT, .meaning = IS_OTHER, .name = "WINDMAX1", .comment = "Maximal wind speed for last hour"},
    [NWINDDIR] = {.sense = VAL_BROKEN, .type = VALT_FLOAT, .meaning = IS_WINDDIR},
    [NWINDDIR1] = {.sense = VAL_BROKEN, .type = VALT_FLOAT, .meaning = IS_OTHER, .name = "WINDDIR1", .comment = "Mean wind speed direction for last hour"},
    [NWINDDIR2] = {.sense = VAL_BROKEN, .type = VALT_FLOAT, .meaning = IS_OTHER, .name = "WINDDIR2", .comment = "Mean wind speed^2 direction for last hour"},
    [NHUMIDITY] = {.sense = VAL_BROKEN, .type = VALT_FLOAT, .meaning = IS_HUMIDITY},
    [NAMB_TEMP] = {.sense = VAL_BROKEN, .type = VALT_FLOAT, .meaning = IS_AMB_TEMP},
    [NPRESSURE] = {.sense = VAL_BROKEN, .type = VALT_FLOAT, .meaning = IS_PRESSURE},
    [NPRECIP] = {.sense = VAL_BROKEN, .type = VALT_UINT, .meaning = IS_PRECIP},
    [NPRECIP_LEVEL] = {.sense = VAL_BROKEN, .type = VALT_FLOAT,   .meaning = IS_PRECIP_LEVEL},
    [NMIST] = {.sense = VAL_BROKEN, .type = VALT_UINT,   .meaning = IS_MIST},
    [NCLOUDS] = {.sense = VAL_BROKEN, .type = VALT_FLOAT,   .meaning = IS_CLOUDS},
    [NSKYTEMP] = {.sense = VAL_BROKEN, .type = VALT_FLOAT, .meaning = IS_SKYTEMP},
 //   [NLIGHTDIST] = {.sense = VAL_FORCEDSHTDN, .type = VALT_FLOAT, .meaning = IS_LIGTDIST},
    // these are calculated values
    [NCOMMWEATH] = {.sense = VAL_OBLIGATORY, .type = VALT_UINT,   .meaning = IS_OTHER, .name = "WEATHER", .comment = "Weather (0..3: good/bad/terrible/prohibited)"},
    [NLASTAHTUNG] = {.sense = VAL_BROKEN, .type = VALT_UINT, .meaning = IS_OTHER, .name = "EVTTIME", .comment = "UNIX-time of last weather level changing"},
    [NAHTUNGRSN] = {.sense = VAL_BROKEN, .type = VALT_STRING, .meaning = IS_OTHER, .name = "EVTRSN", .comment = "Last weather level increasing reason"},
    // virtual values for weather level / flags changing
    [NBADWEATH] = {.sense = VAL_BROKEN, .type = VALT_UINT, .meaning = IS_BADWEATH, .name = "BADWEATH", .comment = "Flag changing weather level to 'BAD'"},
    [NTERRWEATH] = {.sense = VAL_BROKEN, .type = VALT_UINT, .meaning = IS_TERRIBLEWEATH, .name = "TERWEATH", .comment = "Flag changing weather level to 'TERRIBLE'"},
    [NFORCEDSHTDN] = {.sense = VAL_FORCEDSHTDN, .type = VALT_UINT, .meaning = IS_FORCEDSHTDN, .name = "FORCEOFF", .comment = "All should be powered off NOW"},
//    {.sense = VAL_OBLIGATORY, .type = VALT_FLOAT, .meaning = IS_OTHER},
};

// additional fields marked as `IS_OTHER` gathered from different stations
static int Nadditional = 0;
static val_t *additional_data = NULL;

/**
 * @brief weather_level - set/clear weather level
 * @param newlvl - -1 for getter or 0..3 for setter
 * @return current weather level
 */
int weather_level(int newlvl){
    if(newlvl > -1 && newlvl <= WEATHER_PROHIBITED){
        pthread_mutex_lock(&datamutex);
        uint32_t curt = time(NULL);
        int oldlvl = collected_data[NCOMMWEATH].value.u;
        collected_data[NCOMMWEATH].value.u = newlvl;
        collected_data[NCOMMWEATH].time = curt;
        sprintf(collected_data[NAHTUNGRSN].value.str, "MANUAL");
        collected_data[NAHTUNGRSN].time = curt;
        collected_data[NLASTAHTUNG].value.u = curt;
        collected_data[NLASTAHTUNG].time = curt;
        pthread_mutex_unlock(&datamutex);
        LOGWARN("Manual changing of weather level from %d to %d", oldlvl, newlvl);
    }
    pthread_mutex_lock(&datamutex);
    int curlevel = collected_data[NCOMMWEATH].value.u;
    pthread_mutex_unlock(&datamutex);
    return curlevel;
}

/**
 * @brief force_off - set/clear `force off` flag
 * @param flag - 1 to set, 0 to clear or -1 to get
 * @return current value
 */
int force_off(int flag){
    DBG("Force OFF to %d", flag);
    if(flag > -1 && flag < 2){
        pthread_mutex_lock(&datamutex);
        uint32_t curt = time(NULL);
        int oldval = collected_data[NFORCEDSHTDN].value.u;
        collected_data[NFORCEDSHTDN].value.u = (uint32_t)flag;
        if(flag) collected_data[NFORCEDSHTDN].time = curt;
        sprintf(collected_data[NAHTUNGRSN].value.str, "MANUAL");
        collected_data[NAHTUNGRSN].time = curt;
        collected_data[NLASTAHTUNG].value.u = curt;
        collected_data[NLASTAHTUNG].time = curt;
        pthread_mutex_unlock(&datamutex);
        LOGWARN("Manual changing of FORCED SHUTDOWN from %d to %d", oldval, flag);
    }
    pthread_mutex_lock(&datamutex);
    flag = collected_data[NFORCEDSHTDN].value.u;
    pthread_mutex_unlock(&datamutex);
    return flag;
}

typedef struct{
    double array[MAX_HISTORY];
    double sum;
} sumval_t;

typedef struct{
    int write_idx;
    sumval_t C;        // (speed * cos(dir))
    sumval_t C2;       // (speed^2 * cos(dir))
    sumval_t S;        // (speed * sin(dir))
    sumval_t S2;       // (speed^2 * sin(dir))
} meanwinddir_t;

static meanwinddir_t winddirs = {0};

static double update_sum(sumval_t *sv, double newval, int oldidx){
    double old = sv->array[oldidx];
    sv->array[oldidx] = newval;
    return (sv->sum += newval - old);
}

// add current value into floating array and recalculate mean speeds
// data mutex should be locked outside this function
static void wind_dir_add(double curspeed, double curdir, double *dir, double *dir2){
    double C, S;
    sincos(curdir * M_PI / 180., &S, &C);
    double vS = curspeed * S, vC = curspeed * C, v2S = curspeed * vS, v2C = curspeed * vC;
    int idx = winddirs.write_idx;
    winddirs.write_idx = (idx + 1) % MAX_HISTORY;
    // calculate sums
    vS  = update_sum(&winddirs.S, vS, idx);
    vC  = update_sum(&winddirs.C, vC, idx);
    v2S = update_sum(&winddirs.S2, v2S, idx);
    v2C = update_sum(&winddirs.C2, v2C, idx);
    *dir = atan2(vS, vC) * 180. / M_PI;
    *dir2 = atan2(v2S, v2C) * 180. / M_PI;
    if(*dir < 0.) *dir += 360.;
    if(*dir2 < 0.) *dir2 += 360.;

}

typedef struct{
    double speeds[MAX_HISTORY];
    time_t timestamps[MAX_HISTORY];
    int write_idx;          // index in `speeds` and `timestamps` for new value

    int deq[MAX_HISTORY];   // array of indexes in queue
    int deq_head, deq_tail; // queue's head and tail
} sliding_max_t;

static sliding_max_t windspeeds = {0};

static void add_windspeed(sliding_max_t *sm, double speed, time_t now) {
    // Write new data portion into queue
    int idx = sm->write_idx;
    sm->speeds[idx] = speed;
    sm->timestamps[idx] = now;
    sm->write_idx = (idx + 1) % MAX_HISTORY;

    // Remove values older than `TOO_OLD_DATA`
    time_t cutoff = now - TOO_OLD_DATA;
    while(sm->deq_head != sm->deq_tail && sm->timestamps[sm->deq[sm->deq_head]] < cutoff){
        sm->deq_head = (sm->deq_head + 1) % MAX_HISTORY;
    }

    // Remove small values less than current
    while(sm->deq_head != sm->deq_tail && sm->speeds[sm->deq[(sm->deq_tail - 1 + MAX_HISTORY) % MAX_HISTORY]] <= speed){
        sm->deq_tail = (sm->deq_tail - 1 + MAX_HISTORY) % MAX_HISTORY;
    }

    // Add new index into queue
    sm->deq[sm->deq_tail] = idx;
    sm->deq_tail = (sm->deq_tail + 1) % MAX_HISTORY;
}

static double get_current_max(sliding_max_t *sm){
    if(sm->deq_head == sm->deq_tail) return 0.0; // No data
    return sm->speeds[sm->deq[sm->deq_head]];
}

static double get_max_forT(sliding_max_t *sm, time_t tcutoff){
    if(sm->deq_head == sm->deq_tail) return 0.0; // No data
    int idx = sm->deq_head;
    while(idx != sm->deq_tail && sm->timestamps[sm->deq[sm->deq_head]] < tcutoff){
        idx = (idx + 1) % MAX_HISTORY;
    }
    if(idx == sm->deq_tail) return 0.0; // No fresh data
    return sm->speeds[sm->deq[idx]];
}

int collected_amount(){
    return NAMOUNT_OF_DATA + Nadditional;
}

int get_collected(val_t *val, int N){
    if(!val || N < 0 || N >= NAMOUNT_OF_DATA + Nadditional){
        LOGWARN("get_collected(): wrong number (%d) requested or no place for data", N);
        return FALSE;
    }
    pthread_mutex_lock(&datamutex);
    val_t *dptr = (N < NAMOUNT_OF_DATA) ? &collected_data[N] : &additional_data[N-NAMOUNT_OF_DATA];
    //LOGDBG("tm: %zd; cl: %zd", dptr->time, collected_data[N].time);
//#ifdef EBUG
    //char buf[KEY_LEN+1];
    //get_fieldname(dptr, buf);
    //LOGDBG("Copied data of %d (u=%d, nm=%s, t=%zd)", N, dptr->value.u, buf, dptr->time);
//#endif
    *val = *dptr;
    pthread_mutex_unlock(&datamutex);
    return TRUE;
}

/**
 * @brief fix_new_data - take only data with `sense value` less than collected have and more recent
 * @param collected - pointer to collected data
 * @param fresh - pointer to fresh data
 * @param force - ==1 to force changing even if this data is older
 */
static void fix_new_data(val_t *collected, const val_t *fresh, int force){
    if(!collected || !fresh) return;
    //LOGDBG("fix_new_data(): collt: %zd, fresht: %zd", collected->time, fresh->time);
    if(collected->time >= fresh->time){
        if(!force) return;
        //DBG("Forced, collected=%g, fresh=%g", val2d(collected), val2d(fresh));
        if(collected->time - fresh->time > 60) return;
        //DBG("Not too old");
    }
    // lower `collected` level if data is too old
    if(fresh->time - collected->time > WeatherConf.ahtung_delay){
        LOGDBG("collected val is too old -> mark as unnecessary");
        collected->sense = VAL_UNNECESSARY;
    }
    //LOGDBGADD("sense coll: %d, fresh: %d", collected->sense, fresh->sense);
    if(collected->sense < fresh->sense) return;
    if(collected->sense != fresh->sense) collected->sense = fresh->sense; // take new level
    //DBG("Refresh collected value");
    //LOGDBGADD("Refresh time");
    collected->time = fresh->time;

    // check windmax/winddir
    double curvalue = val2d(fresh), dir1 = -100., dir2 = -100.;
    switch(fresh->meaning){ // fill winddir/windmax
        case IS_WIND:
            collected_data[NWINDMAX].sense = fresh->sense;
            collected_data[NWINDMAX1].sense = fresh->sense;
            add_windspeed(&windspeeds, curvalue, fresh->time);
            collected_data[NWINDMAX].value.f = (float) get_current_max(&windspeeds);
            collected_data[NWINDMAX].time = fresh->time;
            collected_data[NWINDMAX1].value.f = (float) get_max_forT(&windspeeds, fresh->time - T_ONE_HOUR);
            collected_data[NWINDMAX1].time = fresh->time;
            break;
        case IS_WINDDIR:
            collected_data[NWINDDIR].sense = fresh->sense;
            collected_data[NWINDDIR1].sense = fresh->sense;
            collected_data[NWINDDIR2].sense = fresh->sense;
            wind_dir_add(collected_data[NWIND].value.f, fresh->value.f, &dir1, &dir2);
            // refresh max
            if(dir1 >= 0.){
                collected_data[NWINDDIR1].value.f = (float) dir1;
                collected_data[NWINDDIR1].time = fresh->time;
            }
            if(dir2 >= 0.){
                collected_data[NWINDDIR2].value.f = (float) dir2;
                collected_data[NWINDDIR2].time = fresh->time;
            }
            break;
        default:
            break;
    }

    if(collected->type == fresh->type){ // good case
        memcpy(&collected->value, &fresh->value, sizeof(num_t));
        //DBG("Types are the same");
        return;
    }
    // bad case: have different types
    // DON'T convert between string and number types!
    switch(collected->type){
    case VALT_UINT:
        switch(fresh->type){
        case VALT_INT:
            collected->value.u = (uint32_t) fresh->value.i;
            //DBG("i->u");
            break;
        case VALT_FLOAT:
            collected->value.u = (uint32_t) fresh->value.f;
            //DBG("f->u");
        default: break;
        }
        break;
    case VALT_INT:
        switch(fresh->type){
        case VALT_UINT:
            collected->value.i = (int32_t) fresh->value.u;
            //DBG("u->i");
            break;
        case VALT_FLOAT:
            collected->value.i = (int32_t) fresh->value.f;
            //DBG("f->i");
        default: break;
        }
        break;
    case VALT_FLOAT:
        switch(fresh->type){
        case VALT_UINT:
            collected->value.f = (float) fresh->value.u;
            //DBG("u->f");
            break;
        case VALT_INT:
            collected->value.f = (float) fresh->value.i;
            //DBG("i->f");
        default: break;
        }
        break;
    default: break;
    }
}

// find value.name in `additional_data`, if need - allocate new memory and update
static void update_additional(val_t *value){
    if(!value) return;
    int idx = 0;
    for(; idx < Nadditional; ++idx){
        if(0 == strcmp(additional_data[idx].name, value->name)) break;
    }
    if(idx == Nadditional){ // not found -> allocate
        additional_data = realloc(additional_data, sizeof(val_t) * (++Nadditional));
        if(!additional_data){
            LOGERR("update_additional() can't realloc()");
            ERR("realloc()");
        }
        memcpy(&additional_data[idx], value, sizeof(val_t));
        LOGDBG("Allocated new field: %s", value->name);
    }else fix_new_data(&additional_data[idx], value, 0);
}

/**
 * @brief chkweatherlevel - increase weather level if need, also check force shutdown flags
 * @param curlevel - current max weather level
 * @param curvalue - current value of sensor data
 * @param curcond - conditions for given value
 * @return 0 if level wasn't changed, or +1 if was increased
 */
static int chkweatherlevel(uint32_t *curlevel, double curvalue, weather_cond_t const *curcond){
    int rtn = 0;
    double good = curcond->good, bad = curcond->bad, terrible = curcond->terrible, prohibited = curcond->prohibited;
    int haveproh = (prohibited > terrible) ? 1 : 0; // value have `prohibited` field
    if(curcond->negflag){ // negate
        curvalue = -curvalue;
        good = -good;
        bad = -bad;
        terrible = -terrible;
        prohibited = -prohibited;
    }
    int newlevel = -1;
    if(haveproh && curvalue > prohibited){
        newlevel = WEATHER_PROHIBITED;
        DBG("---> new level is PROHIBITED, val=%g", (curcond->negflag) ? -curvalue : curvalue);
    }else if(curvalue > terrible){
        newlevel = WEATHER_TERRIBLE;
        DBG("---> new level is TERRIBLE, val=%g", (curcond->negflag) ? -curvalue : curvalue);
    }else if(curvalue > bad) newlevel = WEATHER_BAD;
    else if(curvalue < good) newlevel = WEATHER_GOOD;
    if(newlevel == -1) return 0;
    time_t curt = time(NULL);
    if(curcond->shtdnflag && newlevel >= WEATHER_TERRIBLE){
        DBG("Forced shutdown flag is set, curvalue: %g", (curcond->negflag) ? -curvalue : curvalue);
        // set to one collected data flag and its time
        val_t *f = &collected_data[NFORCEDSHTDN];
        f->value.u = 1;
        f->time = (int) curt;
        DBG("forced = %u", collected_data[NFORCEDSHTDN].value.u);
        // and set current weather level to prohibited
        newlevel = WEATHER_PROHIBITED;
        rtn = 1;
    }
    if((uint32_t)newlevel > *curlevel){
        // TODO: add logging
        DBG("local level increased to %d", newlevel);
        *curlevel = (uint32_t)newlevel;
        rtn = 1;
    }
    return rtn;
}

// conditions for "bad weather" flag (if it ==1 set BAD WEATH)
static weather_cond_t const badweathflag = {.good = 0.1, .bad = 0.5, .terrible = 2.};
// conditions for "terrible weather" flag
static weather_cond_t const terrweathflag = {.good = 0.1, .bad = 0.5, .terrible = 0.7};
// conditions for "prohibited weather" flag
static weather_cond_t const prohibweathflag = {.good = 0.1, .bad = 0.5, .terrible = 0.6, .prohibited = 0.7};
// conditions for "force shutdown" flag
static weather_cond_t const shtdnflag = {.good = 0.1, .bad = 0.5, .terrible = 0.7, .shtdnflag = 1, .prohibited = 0.8};

void refresh_sensval(sensordata_t *s){
    //FNAME();
    //static time_t poll_time = 0;
    //LOGMSG("Refresh sensval for %d", s->PluginNo);
    static char reason[VAL_LEN+1] = {0}; // reason of weather level increasing
    val_t value;
    if(!s || !sensor_alive(s) || !s->get_value) return;
    //if(poll_time == 0) poll_time = get_pollT();
    static uint32_t curlevel = 0; // this is worse weather leavel, start from best (collect by all sensors through 3*tpoll)
    static time_t lasttupdate = 0; // last update time of weather level
    time_t curtime = time(NULL);
    time_t tpoll = get_pollT(), _3tpoll = 3*tpoll;
    //DBG("%d meteo values", s->Nvalues);
    for(int i = 0; i < s->Nvalues; ++i){
        //DBG("\nTry to get %dth value", i);
        if(!s->get_value(s, &value, i) || value.sense > VAL_UNNECESSARY) continue;
        //DBG("got value");
        int idx = -1;
        const weather_cond_t *curcond = NULL;
        switch(value.meaning){
            case IS_WIND:
                idx = NWIND;
                curcond = &WeatherConf.wind;
                break;
            case IS_WINDDIR:
                idx = NWINDDIR;
                break;
            case IS_HUMIDITY:
                idx = NHUMIDITY;
                curcond = &WeatherConf.humidity;
                break;
            case IS_AMB_TEMP:
                idx = NAMB_TEMP;
                break;
            case IS_PRESSURE:
                idx = NPRESSURE;
                break;
            case IS_PRECIP:
                idx = NPRECIP;
                curcond = &prohibweathflag;
                break;
            case IS_PRECIP_LEVEL:
                idx = NPRECIP_LEVEL;
                break;
            case IS_MIST:
                idx = NMIST;
                curcond = &terrweathflag;
                break;
            case IS_CLOUDS:
                idx = NCLOUDS;
                curcond = &WeatherConf.clouds;
                break;
            case IS_SKYTEMP:
                idx = NSKYTEMP;
                curcond = &WeatherConf.sky;
                break;
            /*case IS_LIGTDIST:
                idx = NLIGHTDIST;
                curcond = &WeatherConf.ligtdist;
                break;*/
            case IS_BADWEATH:
                idx = NBADWEATH;
                curcond = &badweathflag;
                break;
            case IS_TERRIBLEWEATH:
                idx = NTERRWEATH;
                curcond = &terrweathflag;
                break;
            case IS_FORCEDSHTDN:
                idx = NFORCEDSHTDN;
                curcond = &shtdnflag;
                //DBG("%s have shtdn flag", value.name);
                break;
            default : break;
        }
        if(value.meaning == IS_OTHER){ // check for new or existant field in `additional_data`
            update_additional(&value);
            continue;
        }
        if(idx < 0 || idx >= NAMOUNT_OF_DATA) continue; // wrong index?
        if(value.sense > collected_data[idx].sense &&
            curtime - collected_data[idx].time < WeatherConf.ahtung_delay) continue; // bad level
        //DBG("IDX=%d", idx);
        pthread_mutex_lock(&datamutex);
        int force = 0;
        if(curcond && value.sense <= collected_data[idx].sense){
            int oldshtdn = collected_data[NFORCEDSHTDN].value.u;
            if(1 == chkweatherlevel(&curlevel, val2d(&value), curcond)){
                get_fieldnameval(&value, reason); // copy to `reason` reason of last level increasing
                force = 1;
                DBG("reason: %s; forceflag=%u, old=%d", reason, collected_data[NFORCEDSHTDN].value.u, oldshtdn);
                if(collected_data[NFORCEDSHTDN].value.u - oldshtdn == 1){ // got shutdown
                    LOGWARN("Forced shutdown flag is set by '%s' of '%s'", reason, s->name);
                }
            }
        }
        if(idx == NFORCEDSHTDN && value.value.u == 0){
            //DBG("Don't clear forced flag by other station");
        }else{
            //LOGDBG("Call fix_new_data for idx=%d (tcoll=%zd)", idx, collected_data[idx].time);
            fix_new_data(&collected_data[idx], &value, force);
        }
        pthread_mutex_unlock(&datamutex);
    }
    pthread_mutex_lock(&datamutex);
    //DBG("check ahtung");
    time_t _2update = lasttupdate + _3tpoll;
    //LOGDBG("curtime: %ld, _2update: %ld", curtime, lasttupdate);
    if(Forbidden){
        collected_data[NCOMMWEATH].value.u = WEATHER_PROHIBITED;
        collected_data[NCOMMWEATH].time = curtime;
    }else if(curtime >= _2update){ // need to update weather level
        if(collected_data[NCOMMWEATH].value.u > curlevel){ // check timeout to make level lower
            // DBG("curtime: %zd, curahtt: %d, diff: %zd, delay: %d", curtime, collected_data[NLASTAHTUNG].value.u, curtime - collected_data[NLASTAHTUNG].value.u, WeatherConf.ahtung_delay);
            if(curtime - collected_data[NLASTAHTUNG].value.u > WeatherConf.ahtung_delay){
                DBG("newlevel: %d, current: %d  DECREASED", curlevel, collected_data[NCOMMWEATH].value.u);
                if(curlevel < WEATHER_TERRIBLE){ // clear forced shutdown flag
                    if(collected_data[NFORCEDSHTDN].value.u){
                        LOGMSG("Clear forced shutdown flag by '%s' and set weather level to %d", s->name, WEATHER_TERRIBLE);
                        DBG("Clear FORCED SHUTDOWN flag");
                        collected_data[NCOMMWEATH].value.u = WEATHER_TERRIBLE;
                        collected_data[NFORCEDSHTDN].value.u = 0;
                    }else collected_data[NCOMMWEATH].value.u = curlevel;
                }else --collected_data[NCOMMWEATH].value.u;
                collected_data[NLASTAHTUNG].value.u = curtime;
                LOGMSG("Station '%s', decrease weather level to %d", s->name, collected_data[NCOMMWEATH].value.u);
            }
        }else{
            if(collected_data[NCOMMWEATH].value.u < curlevel){ // set to worse
                DBG("newlevel: %d, current: %d  INCREASED", curlevel, collected_data[NCOMMWEATH].value.u);
                LOGWARN("Station '%s', sensor '%s', increase weather level to %d", s->name, reason, curlevel);
                collected_data[NCOMMWEATH].value.u = curlevel;
                if(1 < snprintf(collected_data[NAHTUNGRSN].value.str, STRT_LEN+1, "%s", reason))
                    collected_data[NAHTUNGRSN].time = curtime;
            }
            if(curlevel){
                collected_data[NLASTAHTUNG].value.u = curtime; // refresh last ahtung time only for level > good
                collected_data[NLASTAHTUNG].time = curtime;
                collected_data[NAHTUNGRSN].time = curtime;
            }
        }
        lasttupdate = curtime;
        curlevel = 0; // wait for next collected max level
        collected_data[NCOMMWEATH].time = curtime; // refresh `common weather` updating time
    }
    pthread_mutex_unlock(&datamutex);
    //DBG("Refreshed");
}

// set/clear `forbid` flag (by signals USR1 and USR2)
void forbid_observations(int f){
    pthread_mutex_lock(&datamutex);
    if(f) Forbidden = 1;
    else Forbidden = 0;
    int curt = (int) time(NULL);
    // don't use mutexes here as this function called from signal handler
    collected_data[NLASTAHTUNG].value.u = curt;
    collected_data[NLASTAHTUNG].time = curt;
    sprintf(collected_data[NAHTUNGRSN].value.str, "FORBID");
    collected_data[NAHTUNGRSN].time = curt;
    pthread_mutex_unlock(&datamutex);
    DBG("Change FORBID status to %d", f);
}

// `forbid` flag getter
int is_forbidden(){ return Forbidden; }
