/*
 * Copyright (C) 2014-2020 Catalin Toda <catalinii@yahoo.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 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 * USA
 *
 */

#include <fcntl.h>
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
// #include <linux/ioctl.h>

#include <sys/ioctl.h>

#include "adapter.h"
#include "api/symbols.h"
#include "api/variables.h"
#include "dvb.h"
#include "dvbapi.h"
#include "pmt.h"
#include "socketworks.h"
#include "stream.h"
#include "utils.h"
#include "utils/ticks.h"

#ifndef DISABLE_SATIPCLIENT
#include "satipc.h"
#endif

#ifndef DISABLE_NETCVCLIENT
#include "netceiver.h"
#endif

#ifndef DISABLE_DDCI
#include "ddci.h"
#endif

#define DEFAULT_LOG LOG_ADAPTER

const int DEFAULT_PIDS[] = {0, 1, 16, 18};
adapter *a[MAX_ADAPTERS];
int a_count;
char disabled[MAX_ADAPTERS]; // disabled adapters
int sock_signal;
char do_dump_pids = 1;
char absolute_switch;
SMutex a_mutex;

int tuner_s2, tuner_t, tuner_c, tuner_t2, tuner_c2, tuner_at, tuner_ac;
int16_t fe_map[2 * MAX_ADAPTERS];
void find_dvb_adapter(adapter **a);

adapter *adapter_alloc() {
    adapter *ad = new adapter();

    /* diseqc setup */
    ad->diseqc_param.fast = opts.diseqc_fast;
    ad->diseqc_param.addr = opts.diseqc_addr;
    ad->diseqc_param.committed_no = opts.diseqc_committed_no;
    ad->diseqc_param.uncommitted_no = opts.diseqc_uncommitted_no;

    /* diseqc default timing */
    ad->diseqc_param.before_cmd = opts.diseqc_before_cmd;
    ad->diseqc_param.after_cmd = opts.diseqc_after_cmd;
    ad->diseqc_param.after_repeated_cmd = opts.diseqc_after_repeated_cmd;
    ad->diseqc_param.after_switch = opts.diseqc_after_switch;
    ad->diseqc_param.after_burst = opts.diseqc_after_burst;
    ad->diseqc_param.after_tone = opts.diseqc_after_tone;

    /* diseqc state control */
    ad->old_diseqc = -1;
    ad->old_hiband = -1;
    ad->old_pol = -1;
    ad->dmx_source = -1;
    ad->master_source = -1;
    ad->diseqc_multi = opts.diseqc_multi;

    /* LOF setup */
    ad->diseqc_param.lnb_low = opts.lnb_low;
    ad->diseqc_param.lnb_high = opts.lnb_high;
    ad->diseqc_param.lnb_circular = opts.lnb_circular;
    ad->diseqc_param.lnb_switch = opts.lnb_switch;

    ad->adapter_timeout = opts.adapter_timeout;

    ad->strength_multiplier = opts.strength_multiplier;
    ad->snr_multiplier = opts.snr_multiplier;
    ad->force_tuner_signal = TUNER_FORCE_NO;
    ad->db_snr_map = 1.0;

    ad->drop_encrypted = opts.drop_encrypted;

    ad->lbuf = opts.adapter_buffer;
    ad->buf = (unsigned char *)malloc(ad->lbuf + 10);

#ifndef DISABLE_PMT
    // filter for pid 0
    ad->pat_filter = -1;
    ad->sdt_filter = -1;
#endif
    return ad;
}

void find_adapters() {
    static int init_find_adapter;
    if (init_find_adapter == 1)
        return;
    init_find_adapter = 1;
    a_count = 0;
#ifndef DISABLE_LINUXDVB
    find_dvb_adapter(a);
#endif
#ifndef DISABLE_SATIPCLIENT
    find_satip_adapter(a);
#endif
#ifndef DISABLE_NETCVCLIENT
    find_netcv_adapter(a);
#endif
#ifndef DISABLE_DDCI
    find_ddci_adapter(a);
#endif
}

// avoid adapter close unless all the adapters can be closed
int adapter_timeout(sockets *s) {
    int do_close = 1;
    adapter *ad = get_adapter(s->sid);
    if (!ad)
        return 1;

    if (ad->force_close)
        return 1;

    if (ad->adapter_timeout == 0) {
        LOG("Keeping adapter %d open as the initialization is slow", ad->id);
        s->rtime = getTick();
        return 0;
    }

    if (ad->type == ADAPTER_CI) {
        LOG("Keeping adapter %d open as it's a CI device", ad->id);
        s->rtime = getTick();
        return 0;
    }

    if (get_streams_for_adapter(ad->id) > 0) {
        ad->rtime = getTick();
        s->rtime = ad->rtime;
        LOG("Keeping adapter %d open as there are active streams", ad->id);
        return 0;
    }

    int64_t rtime = getTick(), max_close = rtime - ad->adapter_timeout + 2000;
    int i;
    for (i = 0; i < MAX_ADAPTERS; i++)
        if (a[i] && (a[i]->master_source == ad->id) && ad->enabled &&
            a[i]->enabled) {
            LOG("adapter %d is already used by a slave adapters %d", ad->id,
                a[i]->id);
            do_close = 0;
            break;
        }

    LOG("Requested adapter %d close due to timeout %d sec, result %d max_rtime "
        "%jd",
        s->sid, ad->adapter_timeout / 1000, do_close, max_close);
    if (!do_close)
        s->rtime = max_close;
    return do_close;
}

void request_adapter_close(adapter *ad) {
    ad->force_close = 1;
    sockets_force_close(ad->sock);
}

int close_adapter_for_socket(sockets *s) {
    int aid = s->sid;
    adapter *ad = get_adapter(aid);
    LOG("closing DVR socket %d pos %d aid %d", s->sock, s->id, aid);
    if (ad && (s->sid == ad->sock)) {
        ad->sock = -1;
        s->sid = -1;
    }
    if (!ad)
        return 1;

    ad->rtime = getTick();
    return close_adapter(aid);
}

void adapter_set_dvr(adapter *ad) {
    ad->sock =
        sockets_add(ad->dvr, NULL, ad->id, TYPE_DVR, (socket_action)read_dmx,
                    (socket_action)close_adapter_for_socket,
                    (socket_action)adapter_timeout);
    memset(ad->buf, 0, opts.adapter_buffer + 1);
    set_socket_buffer(ad->sock, (unsigned char *)ad->buf, opts.adapter_buffer);
    if (ad->adapter_timeout > 0)
        sockets_timeout(ad->sock, ad->adapter_timeout);
    if (opts.th_priority > 0)
        set_thread_prio(get_socket_thread(ad->sock), opts.th_priority);
}

int init_complete = 0;
int num_adapters = 0;

int init_hw(int i) {
    int64_t st, et;
    adapter *ad;
    int rv = 0;
    if (i < 0 || i >= MAX_ADAPTERS)
        return 3;

    if (a[i] && a[i]->enabled)
        return 0;

    if (!a[i])
        return 2;

    ad = a[i];
    std::lock_guard<SMutex> lock(ad->mutex);
    if (is_adapter_disabled(i)) {
        LOG_AND_RETURN(
            1, "opening adapter %i failed because adapter is disabled", ad->id);
    }
    if (ad->enabled) {
        LOG_AND_RETURN(
            1, "opening adapter %i failed because adapter is already enabled",
            ad->id);
    }

    ad->id = i;
    ad->fe_sock = -1;
    ad->sock = -1;
    ad->force_close = 0;
    ad->err = 0;

    ad->db = MAX_DB;

    if (opts.max_pids)
        ad->max_pids = opts.max_pids;

    st = getTick();
    if (!ad->open) {
        LOG_AND_RETURN(1,
                       "opening adapter %i failed because adapter does not "
                       "have an open method",
                       ad->id);
    }

    if (opts.enigma && ad->dmx_source == -1)
        ad->dmx_source = ad->fn;

    if ((rv = ad->open(ad))) {
        init_complete = 0;
        LOG_AND_RETURN(
            1, "opening adapter %i failed to open the adapter with code %d",
            ad->id, rv);
    }
    ad->enabled = 1;

    if (!ad->buf) {
        LOG_AND_RETURN(1,
                       "opening adapter %i failed because adapter the adapter "
                       "buffer was not initialized",
                       ad->id);
    }
    memset(ad->buf, 0, opts.adapter_buffer + 1);
    init_dvb_parameters(&ad->tp);
    mark_pids_deleted(i, PID_STREAM_ID_UNDEFINED, NULL);
    update_pids(i);

    if (!ad->sys[0])
        ad->delsys(i, ad->fe, ad->sys);
    ad->adapter_name = ad->name(i, ad->fe);
    ad->master_sid = -1;
    ad->sid_cnt = 0;
    ad->pid_err = ad->dec_err = 0;
    ad->new_gs = 0;
    ad->force_close = 0;
    ad->ca_mask = 0;
    ad->tune_time = 0;
    ad->threshold = opts.udp_threshold;
    ad->updating_pids = 0;
    ad->wait_new_stream = 0;
    ad->pids_updates = 0;
    ad->rtime = getTick();
    adapter_set_dvr(ad);
    snprintf(ad->thread_name, sizeof(ad->thread_name), "AD%d", i);
    ad->thread = start_new_thread(ad->thread_name);
    set_socket_thread(ad->sock, ad->thread);
#ifndef DISABLE_PMT
    pmt_init_device(ad);
#endif
    if (ad->post_init)
        ad->post_init(ad);

    et = getTick();

    if (et - st > 1000000) {
        LOG("Slow adapter %d detected", ad->id);
        ad->adapter_timeout = 0;
    }

    //	set_sock_lock(ad->sock, &ad->mutex); // locks automatically the adapter
    // on reading from the DVR

    LOG("done opening adapter %i delivery systems: %s %s %s %s", i,
        get_delsys(ad->sys[0]), get_delsys(ad->sys[1]), get_delsys(ad->sys[2]),
        get_delsys(ad->sys[3]));
    getAdaptersCount();

    if ((ad->master_source >= 0) && (ad->master_source < MAX_ADAPTERS)) {
        return init_hw(ad->master_source);
    }

    return 0;
}

int init_all_hw() {
    int i, rv;
    std::lock_guard<SMutex> lock(a_mutex);
    LOG("starting init_all_hw %d", init_complete);
    if (init_complete)
        return num_adapters;
    find_adapters();
    num_adapters = 0;
    init_complete = 1;
    for (i = 0; i < MAX_ADAPTERS; i++)
        if (!a[i] ||
            ((!a[i]->enabled || a[i]->fe <= 0) &&
             ((a[i]->pa >= 0 && a[i]->fn >= 0) ||
              a[i]->type > ADAPTER_DVB))) // ADAPTER is intialized and not DVB
        {
            if (!(rv = init_hw(i)))
                num_adapters++;
            else if (rv != 2)
                LOG("Failed to init device %d with return value %d", i, rv);
        } else if (a[i]->enabled)
            num_adapters++;
    if (num_adapters == 0)
        init_complete = 0;
    LOG("done init_hw %d", init_complete);
    return num_adapters;
}

// this method needs to close the adapter otherwise the adapter will be in a
// half closed state

int close_adapter(int na) {
    adapter *ad;
    init_complete = 0;
    int sock, i;

    ad = get_adapter_nw(na);
    if (!ad)
        return 1;

    std::lock_guard<SMutex> lock(ad->mutex);
    if (!ad->enabled) {
        return 1;
    }

    LOG("closing adapter %d  -> fe:%d dvr:%d, sock:%d, fe_sock:%d", na, ad->fe,
        ad->dvr, ad->sock, ad->fe_sock);
    // close all streams attached to this adapter
    //	close_streams_for_adapter (na, -1);
    mark_pids_deleted(na, PID_STREAM_ID_UNDEFINED, NULL);
    update_pids(na);
    ad->enabled = 0;
    if (ad->close)
        ad->close(ad);
    //      if(ad->dmx>0)close(ad->dmx);
    if (ad->fe > 0)
        close(ad->fe);
#ifndef DISABLE_PMT
    if (ad->ca_mask > 0)
        pmt_close_device(ad);
#endif
    ad->ca_mask = 0;
    ad->fe = -1;
    ad->dvr = 0;
    ad->strength = 0;
    ad->snr = 0;
    ad->db = MAX_DB;
    ad->db_snr_map = 1.0;
    ad->old_diseqc = -1;
    ad->old_hiband = -1;
    ad->old_pol = -1;
    sock = ad->sock;
    ad->sock = -1;
    if (sock >= 0) // avoid locking the socket after the adapter
    {
        sockets_force_close(sock);
        set_sockets_sid(sock, -1);
    }
    LOG("done closing adapter %d", na);
    for (i = 0; i < MAX_ADAPTERS; i++)
        if (a[i] && (a[i]->master_source == ad->id) && ad->enabled &&
            a[i]->enabled) {
            LOG("Slave adapter %d of adapter %d will be closed", i, ad->id);
            request_adapter_close(a[i]);
        }
    return 1;
}

int getAdaptersCount() {
    int j, k;
    adapter *ad;
    uint32_t i;

    int ts2 = 0, tc2 = 0, tt2 = 0, tc = 0, tt = 0, tac = 0, tat = 0;
    int fes[MAX_DVBAPI_SYSTEMS][MAX_ADAPTERS];
    int ifes[MAX_DVBAPI_SYSTEMS];
    char order[] = {SYS_DVBS2, SYS_DVBT, SYS_DVBC_ANNEX_A, SYS_DVBT2,
                    SYS_DVBC2, SYS_ATSC, SYS_DVBC_ANNEX_B};

    memset(&ifes, 0, sizeof(ifes));
    memset(&fes, 0, sizeof(fes));

    if (opts.force_sadapter)
        tuner_s2 = opts.force_sadapter;
    if (opts.force_tadapter)
        tuner_t = opts.force_tadapter;
    if (opts.force_cadapter)
        tuner_c = opts.force_cadapter;
    for (i = 0; i < MAX_ADAPTERS; i++)
        if ((ad = a[i])) {
            if (!opts.force_sadapter &&
                (delsys_match(ad, SYS_DVBS) || delsys_match(ad, SYS_DVBS2))) {
                ts2++;
                fes[SYS_DVBS2][ifes[SYS_DVBS2]++] = i;
            }

            if (!opts.force_tadapter && delsys_match(ad, SYS_DVBT) &&
                !delsys_match(ad, SYS_DVBT2)) {
                tt++;
                fes[SYS_DVBT][ifes[SYS_DVBT]++] = i;
            }

            if (!opts.force_cadapter && delsys_match(ad, SYS_DVBC_ANNEX_A) &&
                !delsys_match(ad, SYS_DVBC2)) {
                tc++;
                fes[SYS_DVBC_ANNEX_A][ifes[SYS_DVBC_ANNEX_A]++] = i;
            }

            if (delsys_match(ad, SYS_DVBT2)) {
                tt2++;
                fes[SYS_DVBT2][ifes[SYS_DVBT2]++] = i;
            }

            if (delsys_match(ad, SYS_DVBC2)) {
                tc2++;
                fes[SYS_DVBC2][ifes[SYS_DVBC2]++] = i;
            }
            if (delsys_match(ad, SYS_DVBC_ANNEX_B)) {
                tac++;
                fes[SYS_DVBC_ANNEX_B][ifes[SYS_DVBC_ANNEX_B]++] = i;
            }
            if (delsys_match(ad, SYS_ATSC)) {
                tat++;
                fes[SYS_ATSC][ifes[SYS_ATSC]++] = i;
            }
        }
    if (tuner_s2 != ts2 || tuner_c2 != tc2 || tt2 != tuner_t2 ||
        tc != tuner_c || tt != tuner_t || tac != tuner_ac || tat != tuner_at) {
        if (!opts.force_sadapter)
            tuner_s2 = ts2;
        tuner_c2 = tc2;
        tuner_t2 = tt2;
        if (!opts.force_cadapter)
            tuner_c = tc;
        if (!opts.force_tadapter)
            tuner_t = tt;
        tuner_at = tat;
        tuner_ac = tac;

        memset(&fe_map, -1, sizeof(fe_map));

        k = 0;
        for (i = 0; i < ARRAY_SIZE(order); i++) {
            int sys = order[i];
            for (j = 0; j < ifes[sys]; j++) {
                fe_map[k++] = fes[sys][j];
                LOG("FE %d mapped to Adapter %d, sys %s", k, fes[sys][j],
                    get_delsys(sys));
            }
        }
    }
    return tuner_s2 + tuner_c2 + tuner_t2 + tuner_c + tuner_t;
}

char *dump_absolute_table(adapter *ad, char *buf, int lb) {
    int i, len = 0;

    if (!absolute_switch)
        return (char *)"";
    strlcatf(buf, lb, len, " Sources Table: ");
    for (i = 0; i < MAX_SOURCES; i++)
        if (ad->absolute_table[i] > 0) {
            strlcatf(buf, lb, len, "%d->%d, ", i, ad->absolute_table[i]);
        }
    return buf;
}

void dump_adapters() {
    int i;
    adapter *ad;
    char buf[1024];
    if (!opts.log)
        return;
    memset(buf, 0, sizeof(buf));
    LOG("Dumping adapters:");
    for (i = 0; i < MAX_ADAPTERS; i++)
        if ((ad = get_adapter_nw(i)))
            LOG("%d|f: %d sid_cnt:%d master_sid:%d master_source:%d del_sys: "
                "%s,%s,%s %s",
                i, ad->tp.freq, ad->sid_cnt, ad->master_sid, ad->master_source,
                get_delsys(ad->sys[0]), get_delsys(ad->sys[1]),
                get_delsys(ad->sys[2]),
                dump_absolute_table(ad, buf, sizeof(buf)));
    dump_streams();
}

void dump_pids(int aid) {
    int i;
    bool dh = true;

    if (!opts.log)
        return;

    if (!do_dump_pids)
        return;
    adapter *p = get_adapter(aid);
    if (!p)
        return;
    for (i = 0; i < MAX_PIDS; i++)
        if (p->pids[i].flags > PID_STATE_INACTIVE) {
            if (dh)
                LOG("Dumping pids table for adapter %d, number of unknown "
                    "pids: %d",
                    aid, p->pid_err);
            dh = false;

            LOG("pid %d, fd %d, packets %d, d/c/c2 errs %d/%d/%d, flags %d, "
                "pmt %d, filter %d, sock %d, p2 %d, sids: <%s>",
                p->pids[i].pid, p->pids[i].fd, p->pids[i].packets,
                p->pids[i].dec_err, p->pids[i].cc_err, p->pids[i].cc_err2,
                p->pids[i].flags, p->pids[i].pmt, p->pids[i].filter,
                p->pids[i].sock, p->pids[i].packets2,
                iterable_to_string(p->pids[i].sid).c_str());
        }
}

// makes sure the slave and the master adapter have the same polarity, band and
// diseqc
int compare_slave_parameters(adapter *ad, transponder *tp) {
    adapter *master = NULL;

    if (!ad)
        return 0;
    // is not a slave and does not have a slave adapter
    if ((ad->master_source < 0) &&
        is_byte_array_empty(ad->used, sizeof(ad->used)))
        return 0;
    // is slave and the switch is UNICABLE/JESS - we do not care about pol and
    // band
    if ((ad->diseqc_param.switch_type == SWITCH_JESS) ||
        (ad->diseqc_param.switch_type == SWITCH_UNICABLE))
        return 0;

    // master adapter is used by other
    int diseqc = (tp->diseqc > 0) ? tp->diseqc - 1 : 0;
    int pol = (tp->pol - 1) & 1;
    int hiband = get_lnb_hiband(tp, &tp->diseqc_param);
    int freq = tp->freq;

    if (ad->master_source >= 0 && ad->master_source < MAX_ADAPTERS)
        master = a[ad->master_source];

    // master adapter used by slave adapters, check slave parameters if they
    // match
    if (ad && !is_byte_array_empty(ad->used, sizeof(ad->used))) {
        int i;
        for (i = 0; i < MAX_ADAPTERS; i++)
            if (ad->used[i]) {
                adapter *ad2 = get_adapter(i);
                if (!ad2) {
                    LOG("adapter %d used is set for adapter %d but it is "
                        "disabled, "
                        "clearing",
                        ad->id, i);
                    ad->used[i] = 0;
                    continue;
                }
                if (ad2->old_pol != pol || ad2->old_hiband != hiband ||
                    ad2->old_diseqc != diseqc)
                    return 1; // slave parameters matches with the required
                              // parameters
            }
    }

    // master adapter used by slave adapters
    if (master) {
        if (master->old_pol != pol || master->old_hiband != hiband ||
            master->old_diseqc != diseqc)
            return 1; // master parameters matches with the required parameters
    }
    LOGM("%s: adapter %d master %d (pol %d, band %d, diseqc "
         "%d) not compatible with freq %d, pol %d band %d diseqc %d",
         __FUNCTION__, ad->id, master ? master->id : ad->master_source,
         ad->old_pol, ad->old_hiband, ad->old_diseqc, freq, pol, hiband,
         diseqc);
    return 0;
}

int source_enabled_for_adapter(adapter *ad, transponder *tp) {
    if (!absolute_switch)
        return 1;
    if (tp->sys != SYS_DVBS && tp->sys != SYS_DVBS2)
        return 1;
    return ad->absolute_table[tp->diseqc - 1];
}

int get_absolute_source_for_adapter(int aid, int src, int sys) {
    if (!a[aid])
        return -1;
    if (!absolute_switch)
        return src;
    if (sys != SYS_DVBS && sys != SYS_DVBS2)
        return src;
    if (src <= 0)
        return src;
    LOG("Mapping adapter %d src %d to src %d", aid, src,
        a[aid]->absolute_table[src - 1]);
    return a[aid]->absolute_table[src - 1];
}

int get_free_adapter(transponder *tp) {
    int i;
    int match = 0;
    int msys = tp->sys;
    int fe = tp->fe;

    adapter *ad = a[0]; // If none is found then the first is the default!
    for (i = 0; i < MAX_ADAPTERS; i++)
        if (a[i] && a[i]->enabled) {
            ad = a[i];
            break;
        }

    if ((fe > 0) && (fe <= (int)ARRAY_SIZE(fe_map)) && (fe_map[fe - 1] >= 0) &&
        (fe_map[fe - 1] < MAX_ADAPTERS)) {
        fe = fe_map[fe - 1];
        ad = a[fe];
    } else
        fe = -1;

    if (ad)
        LOG("get free adapter %d - a[%d] => e:%d m:%d sid_cnt:%d src:%d f:%d "
            "pol=%d sys: %s %s",
            tp->fe, ad->id, ad->enabled, ad->master_sid, ad->sid_cnt,
            ad->tp.diseqc, ad->tp.freq, ad->tp.pol, get_delsys(ad->sys[0]),
            get_delsys(ad->sys[1]))
    else
        LOG("get free adapter %d msys %s requested %s", fe, get_delsys(fe),
            get_delsys(msys));

    dump_adapters();

    if (fe >= 0) {
        if (ad && delsys_match(ad, msys)) {
            match = 0;
            if (ad->sid_cnt == 0)
                match = 1;
            if (!ad->enabled || !compare_tunning_parameters(ad->id, tp))
                match = 1;
            if (match && !init_hw(fe))
                return fe;
        }
        goto noadapter;
    }
    // provide an already existing adapter
    for (i = 0; i < MAX_ADAPTERS; i++)
        if ((ad = get_adapter_nw(i)) && delsys_match(ad, msys) &&
            source_enabled_for_adapter(ad, tp))
            if (!compare_tunning_parameters(ad->id, tp))
                return i;

    for (i = 0; i < MAX_ADAPTERS; i++) {
        // first free adapter that has the same msys
        if ((ad = get_adapter_nw(i)) && ad->sid_cnt == 0 &&
            delsys_match(ad, msys) && !compare_slave_parameters(ad, tp) &&
            source_enabled_for_adapter(ad, tp))
            return i;
        if (!ad && delsys_match(a[i], msys) &&
            !compare_slave_parameters(a[i], tp)) // device is not initialized
        {
            ad = a[i];
            if (source_enabled_for_adapter(ad, tp) && !init_hw(i))
                return i;
        }
    }

    // No regular tuners available, check for slave tuners
    for (i = 0; i < MAX_ADAPTERS; i++) {
        // first free slave adapter that has the same msys
        if ((ad = get_adapter_nw(i)) && ad->sid_cnt == 0 &&
            delsys_match(ad, msys) && compare_slave_parameters(ad, tp) &&
            source_enabled_for_adapter(ad, tp)) {
            LOGM("get free adapter found slave adapter %d", i);
            return i;
        }
    }

noadapter:
    LOG("no adapter found for src:%d f:%d pol:%d msys:%d", tp->diseqc, tp->freq,
        tp->pol, tp->sys);
    dump_adapters();
    return -1;
}

void adapter_update_threshold(adapter *ad) {
    streams *sid = NULL;
    int i, threshold = opts.udp_threshold;
    if (ad->sid_cnt == 1 && ad->master_sid >= 0) {
        sid = get_stream(ad->master_sid);
        if (sid && (sid->type == STREAM_RTSP_TCP || sid->type == STREAM_HTTP))
            threshold = opts.tcp_threshold;
    } else if (ad->sid_cnt > 0) {
        for (i = 0; i < MAX_STREAMS; i++)
            if ((sid = get_stream(i)))
                if (sid->type == STREAM_RTSP_UDP)
                    break;
        if (i >= MAX_STREAMS)
            threshold = opts.tcp_threshold;
    }

    if (threshold <= 0 || threshold >= 200)
        threshold = 200;

    ad->threshold = threshold;
}

int set_adapter_for_stream(int sid, int aid) {
    adapter *ad;
    if (!(ad = get_adapter(aid)))
        return -1;
    std::lock_guard<SMutex> lock(ad->mutex);

    if (ad->master_sid == -1)
        ad->master_sid = sid;
    if (ad->sid_cnt++ == 0) // tune always first time for a stream
        ad->tp.freq = 0;

    if (ad->master_source >= 0 && ad->master_source < MAX_ADAPTERS) {
        adapter *ad2 = a[ad->master_source];
        ad2->used[ad->id] = 1;
    }
    LOG("set adapter %d for sid %d m:%d s:%d", aid, sid, ad->master_sid,
        ad->sid_cnt);
    adapter_update_threshold(ad);
    return 0;
}

void adapter_commit(adapter *ad) {
    if (!ad->commit)
        return;
    if (!ad->pids_updates) {
        DEBUGM("skipping committing adapter %d as no pids were updated",
               ad->id);
        return;
    }
    LOGM("adapter_commit for adapter %d", ad->id);
    ad->pids_updates = 0;
    ad->commit(ad);
}

void close_adapter_for_stream(int sid, int aid, int close_stream) {
    adapter *ad;
    streams *s = get_sid(sid);
    if (!(ad = get_adapter(aid)))
        return;

    std::lock_guard<SMutex> lock(ad->mutex);

    if (s && s->adapter == aid)
        s->adapter = -1;
    else if (s && s->adapter != aid)
        LOG("%s adapter mismatch: got %d expected %d", __FUNCTION__, s->adapter,
            aid);

    if (ad->master_sid == sid) {
        ad->master_sid = -1;
        fix_master_sid(aid);
    }
    if (ad->sid_cnt > 0)
        ad->sid_cnt--;
    LOG("closed adapter %d for sid %d m:%d sid_cnt:%d", aid, sid,
        ad->master_sid, ad->sid_cnt);
    // delete the attached PIDs as well
    if (ad->sid_cnt == 0) {
        ad->master_sid = -1;
        mark_pids_deleted(aid, PID_STREAM_ID_UNDEFINED, NULL);
        if (ad->standby && close_stream)
            ad->standby(ad);
        init_dvb_parameters(&ad->tp);

        if ((ad->master_source >= 0) && (ad->master_source < MAX_ADAPTERS)) {
            adapter *ad2 = a[ad->master_source];
            if (ad2)
                ad2->used[ad->id] = 0;
            LOGM("adapter %d freed from slave adapter %d", ad2 ? ad2->id : -1,
                 ad->id);
        }
    } else
        mark_pids_deleted(aid, sid, NULL);
    update_pids(aid);
    adapter_update_threshold(ad);
    adapter_commit(ad);
}

int update_pids(int aid) {
    int i;
    bool dp = true;
    adapter *ad;
    ad = get_adapter(aid);
    if (!ad || ad->updating_pids) {
        LOG("ad %d [%d], another update_pids is in progress skipping", aid,
            ad ? ad->id : -1);
        return 1;
    }

    if (ad->err) {
        LOG("adapter %d in error state %d", aid, ad->err);
        return 1;
    }
    ad->updating_pids = 1;
    LOGM("Updating pids for adapter %d", ad->id);
#ifndef DISABLE_PMT
    emulate_add_all_pids(ad);
    for (i = 0; i < MAX_PIDS; i++)
        if (ad->pids[i].flags == PID_STATE_DELETED)
            pmt_pid_del(ad, ad->pids[i].pid);

    for (i = 0; i < MAX_PIDS; i++)
        if (ad->pids[i].flags == PID_STATE_NEW)
            pmt_pid_add(ad, ad->pids[i].pid, 0);
#endif

    for (i = MAX_PIDS - 1; i >= 0; i--)
        if (ad->pids[i].flags == PID_STATE_DELETED) {
            if (dp)
                dump_pids(ad->id);
            dp = false;

            if (ad->pids[i].fd > 0) {
                if (ad->active_pids > 0)
                    ad->active_pids--;
                if (ad->del_filters)
                    ad->del_filters(ad, ad->pids[i].fd, ad->pids[i].pid);
            }
            ad->pids[i].fd = 0;
            ad->pids[i].filter = -1;
            ad->pids[i].pmt = -1;
            ad->pids[i].flags = 0;
            ad->pids[i].packets2 = 0;
            ad->pids[i].packets = 0;
        }

    for (i = 0; i < MAX_PIDS; i++)
        if (ad->pids[i].flags == PID_STATE_NEW) {
            if (dp)
                dump_pids(ad->id);
            dp = false;
            if (ad->max_pids && (ad->max_pids < ad->active_pids)) {
                LOG("maximum number of pids %d out of %d reached",
                    ad->active_pids, ad->max_pids);
                break;
            }

            if (ad->pids[i].fd <= 0) {
                int pid = ad->pids[i].pid;
                // For pids=all emulation add just the PAT pid. process_pmt will
                // add the other pids
                if (opts.emulate_pids_all && pid == 8192)
                    ad->pids[i].fd = -1;
                else if (ad->set_pid &&
                         (ad->pids[i].fd = ad->set_pid(ad, pid)) < 0) {
                    ad->max_pids = ad->max_active_pids - 1;
                    LOG0("Maximum pid filter reached, lowering the value to %d",
                         opts.max_pids);
                    break;
                }
                ad->active_pids++;
                if (ad->max_active_pids < ad->active_pids)
                    ad->max_active_pids = ad->active_pids;
            }
            ad->pids[i].flags = PID_STATE_ACTIVE;
            if (ad->pids[i].pid == 0)
                ad->pat_processed = 0;
            ad->pids[i].packets = 0;
            ad->pids[i].packets2 = 0;
            ad->pids[i].cc = -1;
            ad->pids[i].cc1 = -1;
            ad->pids[i].cc2 = -1;
            ad->pids[i].cc_err = 0;
            ad->pids[i].cc_err2 = 0;
            ad->pids[i].dec_err = 0;
        }

    ad->updating_pids = 0;
    ad->pids_updates++;
    sort_pids(ad->id);
    return 0;
}

void post_tune(adapter *ad) {
#if !defined(DISABLE_PMT)
    int aid = ad->id;
    LOGM("adapter post_tune: aid %d", aid);
#endif
#ifndef DISABLE_PMT
    // If we're not requesting all PIDs, add default PIDs unless they've been
    // explicitly added. Doesn't apply to CI adapters.
    SPid *p_all = find_pid(aid, 8192);
    if (ad->type != ADAPTER_CI &&
        (!p_all || p_all->flags == PID_STATE_DELETED)) {
        for (auto &pid : DEFAULT_PIDS) {
            SPid *p = find_pid(aid, pid);
            if (!p || p->flags == PID_STATE_DELETED) {
                LOG("Adding pid %d to the list of pids as not explicitly added "
                    "for adapter %d",
                    pid, aid);
                mark_pid_add(PID_STREAM_ID_UNDEFINED, aid, pid);
            }
        }
    }
    pmt_tune(ad);
#endif
}

int tune(int aid, int sid) {
    adapter *ad = get_adapter(aid);
    int rv = 0, flush_data = 0;

    LOGM("adapter tune: sid %d aid %d => sock: %d ", sid, aid,
         ad ? ad->sock : -1);

    if (!ad)
        return -400;

    std::lock_guard<SMutex> lock(ad->mutex);

    if (sid == ad->master_sid && ad->do_tune) {
        ad->tp.diseqc_param = ad->diseqc_param;

        rv = ad->tune(ad->id, &ad->tp);
        ad->status = -1;
        ad->status_cnt = 0;
        ad->wait_new_stream = 1;
        ad->strength = 0;
        ad->snr = 0;
        ad->db = MAX_DB;
        flush_data = 1;
        ad->is_t2mi = 0;
        set_socket_pos(ad->sock, 0); // flush the existing buffer
        ad->rlen = 0;
        if (ad->sid_cnt > 1) // the master changed the frequency
        {
            close_streams_for_adapter(aid, sid);
            if (update_pids(aid)) {
                ad->do_tune = 0;
                return -503;
            }
        }
        post_tune(ad);
    } else
        LOG("not tuning for sid %d (do_tune=%d, master_sid=%d)", sid,
            ad->do_tune, ad->master_sid);
    if (rv < 0)
        mark_pids_deleted(aid, sid, NULL);
    if (update_pids(aid)) {
        ad->do_tune = 0;
        return -503;
    }
    if (flush_data) {
        ad->tune_time = getTick();
        set_socket_iteration(ad->sock, 0);
    }
    adapter_commit(ad);
    ad->do_tune = 0;
    return rv;
}

SPid *find_pid(int aid, int p) {
    int i;
    adapter *ad = get_adapter(aid);

    if (!ad)
        return NULL;

    for (i = 0; i < MAX_PIDS; i++) {
        if ((ad->pids[i].flags > PID_STATE_INACTIVE) && (ad->pids[i].pid == p))
            return &ad->pids[i];
        else if (ad->pids[i].flags ==
                 PID_STATE_INACTIVE) // sort_pids ensures there is no active pid
                                     // so we can exit early
            break;
    }
    return NULL;
}

void mark_pid_deleted(int aid, int sid, int _pid, SPid *p) {
    if (!p)
        p = find_pid(aid, _pid);
    if (!p)
        return;

    if (sid == PID_STREAM_ID_UNDEFINED) // delete all sids and the pid
    {
        if (p->flags != PID_STATE_INACTIVE)
            p->flags = PID_STATE_DELETED;
        p->sid.clear();
        return;
    }
    // sid != -1
    p->sid.erase(sid);
    bool is_empty = p->sid.empty();
    int keep = 0;

#ifndef DISABLE_PMT
    if (is_empty && p->filter != -1 && p->flags > PID_STATE_INACTIVE)
        keep = get_active_filters_for_pid(
            p->filter, aid, _pid,
            FILTER_ADD_REMOVE); // 0 - no filter with type ADD_REMOVE
#endif

    if (is_empty && (p->flags != PID_STATE_INACTIVE) && !keep)
        p->flags = PID_STATE_DELETED;
}

void mark_pids_deleted(int aid, int sid,
                       char *pids) // pids==NULL -> delete all pids
{
    int i, la, pid;
    adapter *ad;
    char *arg[MAX_PIDS];

    ad = get_adapter(aid);
    if (!ad)
        return;

    LOG("deleting pids on adapter %d, sid %d, pids=%s", aid, sid,
        pids ? pids : "NULL");

    // If "delpids=all" provided, delete all the pids for this stream
    if (pids && strstr(pids, "all")) {
        LOG("deleting all the pids on adapter %d, sid %d, pids=%s", aid, sid,
            pids);
        pids = NULL;
    }

    if (pids) {
        la = split(arg, pids, ARRAY_SIZE(arg), ',');
        for (i = 0; i < la; i++) {
            pid = map_int(arg[i], NULL);
            mark_pid_deleted(aid, sid, pid, NULL);
        }
        return;
    }

    for (i = 0; i < MAX_PIDS; i++)
        mark_pid_deleted(aid, sid, ad->pids[i].pid, &ad->pids[i]);
}

int mark_pid_add(int sid, int aid, int _pid) {
    adapter *ad;
    int i;
    ad = get_adapter(aid);
    SPid *p;
    if (!ad)
        return -1;
    // check if the pid already exists, if yes add the sid
    if ((p = find_pid(aid, _pid))) {
        LOG("found already existing pid %d flags %d", _pid, p->flags);
        if (sid != PID_STREAM_ID_UNDEFINED)
            p->sid.insert(sid);
        if (p->flags == PID_STATE_DELETED)
            p->flags = PID_STATE_NEW;
        return 0;
    }
    // add the new pid in a new position
    for (i = 0; i < MAX_PIDS; i++)
        if (ad->pids[i].flags == PID_STATE_INACTIVE) {
            ad->pids[i].flags = PID_STATE_NEW;
            ad->pids[i].pid = _pid;
            ad->pids[i].sid.clear();
            if (sid != PID_STREAM_ID_UNDEFINED)
                ad->pids[i].sid.insert(sid);
            ad->pids[i].pmt = -1;
            ad->pids[i].filter = -1;
            ad->pids[i].sock = -1;
            return 0;
        }
    LOG("MAX_PIDS (%d) reached for adapter %d in adding PID: %d", MAX_PIDS, aid,
        _pid);
    dump_pids(aid);
    dump_adapters();
    return -1;
}

int mark_pids_add(int sid, int aid, char *pids) {
    int i, la;
    adapter *ad;
    char *arg[MAX_PIDS + 2];
    int pid;

    ad = get_adapter(aid);
    if (!ad)
        return -1;

    if (!pids)
        return -1;
    LOG("adding pids to adapter %d, sid %d, pids=%s", aid, sid,
        pids ? pids : "NULL");

    la = split(arg, pids, ARRAY_SIZE(arg), ',');
    for (i = 0; i < la; i++) {
        pid = map_intd(arg[i], NULL, -1);
        if (pid < 0 || pid > 8192)
            continue;
        if (mark_pid_add(sid, aid, pid) < 0)
            return -1;
    }
    return 0;
}

int get_lnb_hiband(transponder *tp, diseqc *diseqc_param) {
    if (tp->pol > 2 && diseqc_param->lnb_circular > 0)
        return 0;
    if (diseqc_param->lnb_switch > 0 && tp->freq > diseqc_param->lnb_switch)
        return 1;
    return 0;
}

int get_lnb_int_freq(transponder *tp, diseqc *diseqc_param) {
    int freq = tp->freq;

    if (tp->pol > 2 && diseqc_param->lnb_circular > 0)
        return (freq - diseqc_param->lnb_circular);
    if (diseqc_param->lnb_switch > 0 && freq > diseqc_param->lnb_switch)
        return (freq - diseqc_param->lnb_high);
    return abs(freq - diseqc_param->lnb_low);
}

int compare_tunning_parameters(int aid, transponder *tp) {
    adapter *ad = get_adapter(aid);
    if (!ad)
        return -1;

    LOGM("new parameters: f:%d, plp/isi:%d, diseqc:%d, pol:%d, sr:%d, mtype:%d",
         tp->freq, tp->plp_isi, tp->diseqc, tp->pol, tp->sr, tp->mtype);
    LOGM("old parameters: f:%d, plp/isi:%d, diseqc:%d, pol:%d, sr:%d, mtype:%d",
         ad->tp.freq, ad->tp.plp_isi, ad->tp.diseqc, ad->tp.pol, ad->tp.sr,
         ad->tp.mtype);
    if ((abs(tp->freq - ad->tp.freq) > 1000) || tp->plp_isi != ad->tp.plp_isi ||
        get_absolute_source_for_adapter(aid, tp->diseqc, tp->sys) !=
            ad->tp.diseqc ||
        (tp->pol > 0 && tp->pol != ad->tp.pol) ||
        (tp->sr > 1000 && ad->tp.sr > 1000 && tp->sr != ad->tp.sr) ||
        (tp->mtype != 6 && ad->tp.mtype != 6 && tp->mtype != ad->tp.mtype))

        return 1;

    return 0;
}

int set_adapter_parameters(int aid, int sid, transponder *tp) {
    adapter *ad = get_adapter(aid);

    if (!ad)
        return -1;

    LOG("setting DVB parameters for adapter %d - master_sid %d sid %d old f:%d",
        aid, ad->master_sid, sid, ad->tp.freq);
    std::lock_guard<SMutex> lock(ad->mutex);
    if (ad->master_sid == -1)
        ad->master_sid = sid; // master sid was closed

    ad->do_tune = 0;
    if (compare_tunning_parameters(aid, tp)) {
        if (sid != ad->master_sid) // slave sid requesting to tune to a
                                   // different frequency
        {
            LOG("secondary stream requested tune, not gonna happen ad: f:%d sr:%d pol:%d plp/isi:%d src:%d mod %d -> \
			new: f:%d sr:%d pol:%d plp/isi:%d src:%d mod %d",
                ad->tp.freq, ad->tp.sr, ad->tp.pol, ad->tp.plp_isi,
                ad->tp.diseqc, ad->tp.mtype, tp->freq, tp->sr, tp->pol,
                tp->plp_isi, tp->diseqc, tp->mtype);
            return -1;
        }
        ad->do_tune = 1;
        mark_pids_deleted(aid, PID_STREAM_ID_UNDEFINED, NULL);
        if (update_pids(aid)) {
            ad->do_tune = 0;
            return -1;
        }
    }

    copy_dvb_parameters(tp, &ad->tp);
    // FE= is not specified by the client
    if (ad->tp.fe < 1) {
        ad->tp.diseqc =
            get_absolute_source_for_adapter(ad->id, ad->tp.diseqc, ad->tp.sys);
    }

    if (ad->tp.pids) // pids can be specified in SETUP and then followed by a
                     // delpids in PLAY, make sure the behaviour is right
    {
        mark_pids_deleted(aid, sid, NULL); // delete all the pids for this
        if (mark_pids_add(sid, aid, ad->tp.pids) < 0) {
            return -1;
        }
    }

    if (ad->tp.dpids) {
        mark_pids_deleted(aid, sid, ad->tp.dpids);
    }
    if (ad->tp.apids) {
        if (mark_pids_add(sid, aid, ad->tp.apids ? ad->tp.apids : ad->tp.pids) <
            0) {
            return -1;
        }
    }

    return 0;
}

adapter *get_adapter1(int aid, const char *file, int line) {
    if (aid < 0 || aid >= MAX_ADAPTERS || !a[aid] || !a[aid]->enabled) {
        LOG("%s:%d: get_adapter returns NULL for adapter_id %d", file, line,
            aid);
        return NULL;
    }
    return a[aid];
}

inline adapter *get_configured_adapter_nw(int aid) {
    if (aid < 0 || aid >= MAX_ADAPTERS || !a[aid] || disabled[aid]) {
        return NULL;
    }
    return a[aid];
}

char *itoa_positive(char *dest, int val) {
    dest[0] = 0;
    if (val >= 0)
        sprintf(dest, "%d", val);
    return dest;
}

char *get_stream_pids(int s_id, char *dest, int max_size);
char *describe_adapter(int sid, int aid, char *dad, int ld) {
    int use_ad, len;
    transponder *t;
    adapter *ad;
    streams *ss;
    char plp_isi[10], ds[10];
    int status = 1, strength = 255, snr = 15;

    ss = get_sid(sid);

    use_ad = 1;
    if (!(ad = get_adapter_nw(aid)) || (ss && !ss->do_play)) {
        if (aid < 0)
            aid = 0;
        if (!ss)
            return (char *)"";
        t = &ss->tp;
        use_ad = 0;
    } else
        t = &ad->tp;
    memset(dad, 0, ld);

    if (use_ad) {
        if (ad->status < 0) {
            status = 1;
            snr = 15;
            strength = 255;
        } else {
            strength = ad->strength;
            snr = ad->snr;
            if (snr > 15)
                snr = snr >> 4;
            status = (ad->status & FE_HAS_LOCK) > 0;

            if (strength > 255 || strength < 0)
                strength = 1;
            if (snr > 15 || snr < 0)
                snr = 1;
        }
    }
    if (t->sys == 0)
        len = snprintf(dad, ld,
                       "ver=1.0;src=1;tuner=%d,0,0,0,0,,,,,,,;pids=", aid + 1);
    else if (t->sys == SYS_DVBS || t->sys == SYS_DVBS2)
        len = snprintf(
            dad, ld,
            "ver=1.0;src=%d;tuner=%d,%d,%d,%d,%d,%s,%s,%s,%s,%s,%d,%s;pids=",
            t->diseqc, (ad && ad->tp.fe > 0) ? ad->tp.fe : aid + 1, strength,
            status, snr, t->freq / 1000, get_pol(t->pol), get_delsys(t->sys),
            get_modulation(t->mtype), get_pilot(t->plts), get_rolloff(t->ro),
            t->sr / 1000, get_fec(t->fec));
    else if (t->sys == SYS_DVBT || t->sys == SYS_DVBT2)
        len = snprintf(
            dad, ld,
            "ver=1.1;tuner=%d,%d,%d,%d,%.2f,%d,%s,%s,%s,%s,%s,%s,%d,%d;pids=",
            (ad && ad->tp.fe > 0) ? ad->tp.fe : aid + 1, strength, status, snr,
            (double)t->freq / 1000.0, t->bw / 1000000, get_delsys(t->sys),
            get_tmode(t->tmode), get_modulation(t->mtype), get_gi(t->gi),
            get_fec(t->fec), itoa_positive(plp_isi, t->plp_isi), t->t2id,
            t->sm);
    else
        len = snprintf(
            dad, ld,
            "ver=1.2;tuner=%d,%d,%d,%d,%.2f,%d,%s,%s,%d,%d,%s,%s,%s;pids=",
            (ad && ad->tp.fe > 0) ? ad->tp.fe : aid + 1, strength, status, snr,
            (double)t->freq / 1000, t->bw / 1000000, get_delsys(t->sys),
            get_modulation(t->mtype), t->sr / 1000, t->c2tft,
            itoa_positive(ds, t->ds), itoa_positive(plp_isi, t->plp_isi),
            get_inversion(t->inversion));

    if (use_ad) {
        int len1 = len;
        len += strlen(get_stream_pids(sid, dad + len, ld - len));
        if (len == len1)
            strlcatf(dad, ld, len, "%s", "none");
    }

    if (!use_ad && (t->apids || t->pids)) {
        if (t->pids && strstr(t->pids, "8192"))
            strlcatf(dad, ld, len, "%s", "all");
        else
            strlcatf(dad, ld, len, "%s", t->pids ? t->pids : t->apids);
    } else if (!use_ad)
        strlcatf(dad, ld, len, "%s", "none");

    LOGM("describe_adapter: sid %d, aid %d => %s", sid, aid, dad);

    return dad;
}

// sorting the pid list in order to get faster the pids that are frequestly used
// the enabled pids will always be in front of the array
void sort_pids(int aid) {
    int b, i;
    SPid pp;
    SPid *p;
    adapter *ad = get_adapter(aid);

    if (!ad)
        return;
    p = ad->pids;
    b = 1;
    while (b) {
        b = 0;
        for (i = 0; i < MAX_PIDS - 1; i++) {
            if (p[i + 1].flags ==
                PID_STATE_INACTIVE) // do not exchange inactive pids
                continue;
            if (p[i].flags == PID_STATE_INACTIVE ||
                p[i].packets < p[i + 1].packets) {
                b = 1;
                std::swap(p[i], p[i + 1]);
            }
        }
    }
}

void free_all_adapters() {
    int i;
    sockets_del(sock_signal);
    for (i = 0; i < MAX_ADAPTERS; i++)
        if (a[i] && a[i]->enabled) {
            a[i]->adapter_timeout = opts.adapter_timeout;
            close_adapter(i);
        }

    for (i = 0; i < MAX_ADAPTERS; i++)
        if (a[i]) {
            if (a[i]->free)
                a[i]->free(a[i]);
            if (a[i]->buf)
                free(a[i]->buf);
            delete a[i];
            a[i] = NULL;
        }

#ifndef DISABLE_NETCVCLIENT
    fprintf(stderr, "\n\nREEL: recv_exit\n");
    if (opts.netcv_if && recv_exit())
        LOG("Netceiver exit failed");
#endif
}
char is_adapter_disabled(int i) {
    if (i >= 0 && i < MAX_ADAPTERS)
        return disabled[i];
    return 1;
}

void set_disable(int i, int v) {
    if (i >= 0 && i < MAX_ADAPTERS)
        disabled[i] = v;
}

void enable_adapters(char *o) {
    int i, la, st, end, j;
    char buf[strlen(o) + 1], *arg[40], *sep;
    for (i = 0; i < MAX_ADAPTERS; i++)
        set_disable(i, 1);
    safe_strncpy(buf, o);

    la = split(arg, buf, ARRAY_SIZE(arg), ',');
    for (i = 0; i < la; i++) {
        sep = strchr(arg[i], '-');
        if (sep == NULL) {
            st = map_int(arg[i], NULL);
            set_disable(st, 0);
        } else {
            st = map_int(arg[i], NULL);
            end = map_int(sep + 1, NULL);
            for (j = st; j <= end; j++)
                set_disable(j, 0);
        }
    }
}

void set_unicable_adapters(char *o, int type) {
    int i, la, a_id, slot, freq, pin, o13v;
    char buf[strlen(o) + 1], *arg[40], *sep1, *sep2, *sep3;
    adapter *ad;
    safe_strncpy(buf, o);
    la = split(arg, buf, ARRAY_SIZE(arg), ',');
    for (i = 0; i < la; i++) {
        a_id = map_intd(arg[i], NULL, -1);
        if (a_id < 0 || a_id >= MAX_ADAPTERS)
            continue;

        if (!a[a_id])
            a[a_id] = adapter_alloc();
        ad = a[a_id];

        sep1 = strchr(arg[i], ':');
        sep2 = strchr(arg[i], '-');

        if (!sep1 || !sep2)
            continue;
        if ((o13v = (sep2[1] == '*')) != 0)
            sep2++;
        slot = map_intd(sep1 + 1, NULL, -1);
        freq = map_intd(sep2 + 1, NULL, -1);
        if (slot < 0 || freq < 0)
            continue;
        sep3 = strchr(sep2 + 1, '-');
        pin = TP_VALUE_UNSET;
        if (sep3)
            pin = map_intd(sep3 + 1, NULL, TP_VALUE_UNSET);

        ad->diseqc_param.uslot = slot;
        ad->diseqc_param.ufreq = freq;
        ad->diseqc_param.switch_type = type;
        ad->diseqc_param.pin = pin;
        ad->diseqc_param.only13v = o13v;
        LOG("Setting %s adapter %d slot %d freq %d pin %d",
            type == SWITCH_UNICABLE ? "unicable" : "jess", a_id, slot, freq,
            pin);
    }
}

void set_diseqc_adapters(char *o) {
    int i, la, a_id, fast, addr, committed_no, uncommitted_no;
    char buf[strlen(o) + 1], *arg[40], *sep1, *sep2;
    adapter *ad;
    safe_strncpy(buf, o);
    la = split(arg, buf, ARRAY_SIZE(arg), ',');
    for (i = 0; i < la; i++) {
        if (arg[i] && arg[i][0] == '*') {
            ad = NULL;
            a_id = -1;
        } else {
            a_id = map_intd(arg[i], NULL, -1);
            if (a_id < 0 || a_id >= MAX_ADAPTERS)
                continue;

            if (!a[a_id])
                a[a_id] = adapter_alloc();
            ad = a[a_id];
        }

        sep1 = strchr(arg[i], ':');
        sep2 = strchr(arg[i], '-');

        if (!sep1 || !sep2)
            continue;

        fast = 0;
        addr = 0x10;
        while (sep1[1] == '*' || sep1[1] == '@' || sep1[1] == '.') {
            if (sep1[1] == '*') {
                fast = 1;
                sep1++;
            } else if (sep1[1] == '@') {
                addr = 0;
                sep1++;
            } else if (sep1[1] == '.') {
                addr = 0x11;
                sep1++;
            }
        }

        committed_no = map_intd(sep1 + 1, NULL, -1);
        uncommitted_no = map_intd(sep2 + 1, NULL, -1);
        if (committed_no < 0 || uncommitted_no < 0)
            continue;

        if (ad) {
            ad->diseqc_param.fast = fast;
            ad->diseqc_param.addr = addr;
            ad->diseqc_param.committed_no = committed_no;
            ad->diseqc_param.uncommitted_no = uncommitted_no;
        } else {
            opts.diseqc_fast = fast;
            opts.diseqc_addr = addr;
            opts.diseqc_committed_no = committed_no;
            opts.diseqc_uncommitted_no = uncommitted_no;
            int j;
            for (j = 0; j < MAX_ADAPTERS; j++)
                if (a[j]) {
                    a[j]->diseqc_param.fast = fast;
                    a[j]->diseqc_param.addr = addr;
                    a[j]->diseqc_param.committed_no = committed_no;
                    a[j]->diseqc_param.uncommitted_no = uncommitted_no;
                }
        }
        LOG("Setting diseqc adapter %d fast %d addr 0x%02x committed_no %d "
            "uncommitted_no %d",
            a_id, fast, addr, committed_no, uncommitted_no);
    }
}

void set_absolute_src(char *o) {
    int i, la, src, inp, pos, range;
    char buf[strlen(o) + 1], *arg[40], *inps, *poss, *ranges;
    adapter *ad;

    safe_strncpy(buf, o);
    buf[sizeof(buf) - 1] = '\0';
    la = split(arg, buf, ARRAY_SIZE(arg), ',');
    for (i = 0; i < la; i++) {
        inps = strchr(arg[i], ':');
        if (!inps)
            continue;
        inps++;
        poss = strchr(inps, ':');
        if (!poss)
            continue;
        poss++;

        src = map_intd(arg[i], NULL, -1);
        inp = map_intd(inps, NULL, -1);
        pos = map_intd(poss, NULL, -1);

        range = src;
        ranges = strchr(arg[i], '-');
        if (ranges && ranges < inps) {
            range = map_intd(ranges + 1, NULL, -1);
        }

        if (src < 0 || src >= MAX_SOURCES)
            continue;
        if (range < 0 || range >= MAX_SOURCES)
            continue;
        if (pos < 0 || pos >= 15)
            continue;
        int cnt = 1;
        while (src <= range) {
            LOG("Setting source %d (src=%d) to adapter %d position %d", src,
                src + 1, inp, pos + cnt - 1);
            if (!a[inp])
                a[inp] = adapter_alloc();
            ad = a[inp];

            ad->absolute_table[src] = pos + cnt;
            src++;
            cnt++;
        }
        absolute_switch = 1;
    }
}

void set_diseqc_multi(char *o) {
    int i, la, a_id, position;
    char buf[strlen(o) + 1], *arg[40], *sep1;
    adapter *ad;
    safe_strncpy(buf, o);
    la = split(arg, buf, ARRAY_SIZE(arg), ',');
    for (i = 0; i < la; i++) {
        if (arg[i] && arg[i][0] == '*') {
            ad = NULL;
            a_id = -1;
        } else {
            a_id = map_intd(arg[i], NULL, -1);
            if (a_id < 0 || a_id >= MAX_ADAPTERS)
                continue;

            if (!a[a_id])
                a[a_id] = adapter_alloc();
            ad = a[a_id];
        }

        sep1 = strchr(arg[i], ':');

        if (!sep1)
            continue;
        position = map_intd(sep1 + 1, NULL, -1);
        if (position < 0)
            continue;
        if (ad) {
            ad->diseqc_multi = position;
        } else {
            opts.diseqc_multi = position;
            int j;
            for (j = 0; j < MAX_ADAPTERS; j++)
                if (a[j]) {
                    a[j]->diseqc_multi = position;
                }
        }
        LOG("Setting diseqc multi adapter %d position %d", a_id, position);
    }
}

void set_lnb_adapters(char *o) {
    int i, la, a_id, lnb_low, lnb_high, lnb_switch;
    char buf[strlen(o) + 1], *arg[40], *sep1, *sep2, *sep3;
    adapter *ad;
    safe_strncpy(buf, o);
    la = split(arg, buf, ARRAY_SIZE(arg), ',');
    for (i = 0; i < la; i++) {
        if (arg[i] && arg[i][0] == '*') {
            ad = NULL;
            a_id = -1;
        } else {
            a_id = map_intd(arg[i], NULL, -1);
            if (a_id < 0 || a_id >= MAX_ADAPTERS)
                continue;

            if (!a[a_id])
                a[a_id] = adapter_alloc();
            ad = a[a_id];
        }
        sep3 = NULL;
        sep1 = strchr(arg[i], ':');
        sep2 = strchr(arg[i], '-');
        if (sep2)
            sep3 = strchr(sep2 + 1, '-');

        if (!sep1 || !sep2 || !sep3) {
            LOG("LNB parameters not correctly specified: %s", arg[i]);
            continue;
        }

        lnb_low = map_intd(sep1 + 1, NULL, -1) * 1000;
        lnb_high = map_intd(sep2 + 1, NULL, -1) * 1000;
        lnb_switch = map_intd(sep3 + 1, NULL, -1) * 1000;
        if (lnb_low < 0 || lnb_high < 0 || lnb_switch < 0)
            continue;

        if (ad) {
            ad->diseqc_param.lnb_low = lnb_low;
            ad->diseqc_param.lnb_high = lnb_high;
            ad->diseqc_param.lnb_switch = lnb_switch;
            ad->diseqc_param.lnb_circular = 0;
        } else {
            opts.lnb_low = lnb_low;
            opts.lnb_high = lnb_high;
            opts.lnb_switch = lnb_switch;
            opts.lnb_circular = 0;
            int j;
            for (j = 0; j < MAX_ADAPTERS; j++)
                if (a[j]) {
                    a[j]->diseqc_param.lnb_low = lnb_low;
                    a[j]->diseqc_param.lnb_high = lnb_high;
                    a[j]->diseqc_param.lnb_switch = lnb_switch;
                    a[j]->diseqc_param.lnb_circular = 0;
                }
        }
        LOG("Setting diseqc adapter %d lnb_low %d lnb_high %d lnb_switch %d",
            a_id, lnb_low, lnb_high, lnb_switch);
    }
}

void set_diseqc_timing(char *o) {
    int i, la, a_id;
    int before_cmd, after_cmd, after_repeated_cmd;
    int after_switch, after_burst, after_tone;
    char buf[strlen(o) + 1], *arg[40];
    char *sep1, *sep2, *sep3, *sep4, *sep5, *sep6;
    adapter *ad;
    safe_strncpy(buf, o);
    la = split(arg, buf, ARRAY_SIZE(arg), ',');
    for (i = 0; i < la; i++) {
        if (arg[i] && arg[i][0] == '*') {
            ad = NULL;
            a_id = -1;
        } else {
            a_id = map_intd(arg[i], NULL, -1);
            if (a_id < 0 || a_id >= MAX_ADAPTERS)
                continue;

            if (!a[a_id])
                a[a_id] = adapter_alloc();
            ad = a[a_id];
        }

        sep1 = strchr(arg[i], ':');
        sep2 = strchr(arg[i], '-');
        sep3 = sep2 ? strchr(sep2 + 1, '-') : NULL;
        sep4 = sep3 ? strchr(sep3 + 1, '-') : NULL;
        sep5 = sep4 ? strchr(sep4 + 1, '-') : NULL;
        sep6 = sep5 ? strchr(sep5 + 1, '-') : NULL;

        if (!sep1 || !sep2 || !sep3 || !sep4 || !sep5 || !sep6)
            continue;
        before_cmd = map_intd(sep1 + 1, NULL, -1);
        after_cmd = map_intd(sep2 + 1, NULL, -1);
        after_repeated_cmd = map_intd(sep3 + 1, NULL, -1);
        after_switch = map_intd(sep4 + 1, NULL, -1);
        after_burst = map_intd(sep5 + 1, NULL, -1);
        after_tone = map_intd(sep6 + 1, NULL, -1);
        if (before_cmd < 0 || after_cmd < 0 || after_repeated_cmd < 0 ||
            after_switch < 0 || after_burst < 0 || after_tone < 0)
            continue;

        if (ad) {
            ad->diseqc_param.before_cmd = before_cmd;
            ad->diseqc_param.after_cmd = after_cmd;
            ad->diseqc_param.after_repeated_cmd = after_repeated_cmd;
            ad->diseqc_param.after_switch = after_switch;
            ad->diseqc_param.after_burst = after_burst;
            ad->diseqc_param.after_tone = after_tone;
        } else {
            opts.diseqc_before_cmd = before_cmd;
            opts.diseqc_after_cmd = after_cmd;
            opts.diseqc_after_repeated_cmd = after_repeated_cmd;
            opts.diseqc_after_switch = after_switch;
            opts.diseqc_after_burst = after_burst;
            opts.diseqc_after_tone = after_tone;
            int j;
            for (j = 0; j < MAX_ADAPTERS; j++)
                if (a[j]) {
                    a[j]->diseqc_param.before_cmd = before_cmd;
                    a[j]->diseqc_param.after_cmd = after_cmd;
                    a[j]->diseqc_param.after_repeated_cmd = after_repeated_cmd;
                    a[j]->diseqc_param.after_switch = after_switch;
                    a[j]->diseqc_param.after_burst = after_burst;
                    a[j]->diseqc_param.after_tone = after_tone;
                }
        }
        LOG("Setting diseqc timing for adapter %d before cmd %d after cmd %d "
            "after repeated cmd %d after switch %d after burst %d after tone "
            "%d",
            a_id, before_cmd, after_cmd, after_repeated_cmd, after_switch,
            after_burst, after_tone);
    }
}

void set_slave_adapters(char *o) {
    int i, j, la, a_id, a_id2, master = 0;
    char buf[strlen(o) + 1], *arg[40], *sep, *sep2;
    adapter *ad;
    safe_strncpy(buf, o);
    la = split(arg, buf, ARRAY_SIZE(arg), ',');
    for (i = 0; i < la; i++) {
        a_id = map_intd(arg[i], NULL, -1);
        if (a_id < 0 || a_id >= MAX_ADAPTERS)
            continue;

        sep = strchr(arg[i], '-');
        a_id2 = a_id;
        if (sep)
            a_id2 = map_intd(sep + 1, NULL, -1);

        if (a_id2 < 0 || a_id2 >= MAX_ADAPTERS)
            continue;

        sep2 = strchr(arg[i], ':');
        if (sep2)
            master = map_intd(sep2 + 1, NULL, 0);

        for (j = a_id; j <= a_id2; j++) {
            if (!a[j])
                a[j] = adapter_alloc();

            ad = a[j];

            // make sure the source is not already set
            if (ad && ad->master_source != -1)
                continue;

            if (master >= 0 && master < MAX_ADAPTERS) {
                if (!a[master] && !is_adapter_disabled(master))
                    a[master] = adapter_alloc();

                if (a[master] && a[master]->master_source < 0) {
                    a[master]->master_source =
                        -2; // force this adapter as master
                    ad->master_source = master;
                    LOG("Setting master adapter %d for adapter %d",
                        ad->master_source, j);
                }
            }
        }
    }
}

void set_timeout_adapters(char *o) {
    int i, j, la, a_id, a_id2;
    int timeout = opts.adapter_timeout / 1000;
    char buf[strlen(o) + 1], *arg[40], *sep;
    adapter *ad;
    safe_strncpy(buf, o);
    sep = strchr(buf, ':');
    if (sep)
        timeout = map_intd(sep + 1, NULL, timeout);
    la = split(arg, buf, ARRAY_SIZE(arg), ',');
    if (arg[0] && (arg[0][0] == '*')) {
        opts.adapter_timeout = timeout * 1000;
        int j;
        for (j = 0; j < MAX_ADAPTERS; j++)
            if (a[j]) {
                a[j]->adapter_timeout = timeout * 1000;
            }
        LOG("Set default timeout to %d", opts.adapter_timeout);
        return;
    }
    for (i = 0; i < la; i++) {
        a_id = map_intd(arg[i], NULL, -1);
        if (a_id < 0 || a_id >= MAX_ADAPTERS)
            continue;

        sep = strchr(arg[i], '-');
        a_id2 = a_id;
        if (sep)
            a_id2 = map_intd(sep + 1, NULL, -1);

        if (a_id2 < 0 || a_id2 >= MAX_ADAPTERS)
            continue;

        for (j = a_id; j <= a_id2; j++) {
            if (!a[j])
                a[j] = adapter_alloc();

            ad = a[j];
            ad->adapter_timeout = timeout * 1000;

            LOG("Set timeout for adapter %d to %d", j, ad->adapter_timeout);
        }
    }
}

extern const char *fe_delsys[];
void set_adapters_delsys(char *o) {
    int i, la, a_id, ds;
    char buf[strlen(o) + 1], *arg[40], *sep;
    adapter *ad;
    safe_strncpy(buf, o);
    la = split(arg, buf, ARRAY_SIZE(arg), ',');
    for (i = 0; i < la; i++) {
        a_id = map_intd(arg[i], NULL, -1);
        if (a_id < 0 || a_id >= MAX_ADAPTERS)
            continue;

        sep = strchr(arg[i], ':');
        if (!sep) {
            LOG("Delivery system is missing, the format is "
                "adapter_number:delivery_system\n example: 2:dvbs2");
            return;
        }
        ds = map_intd(sep + 1, (char **)fe_delsys, 0);

        if (!a[a_id])
            a[a_id] = adapter_alloc();

        ad = a[a_id];
        ad->sys[0] = (fe_delivery_system_t)ds;

        if (ad->sys[0] == SYS_DVBS2)
            ad->sys[1] = SYS_DVBS;
        if (ad->sys[0] == SYS_DVBT2)
            ad->sys[1] = SYS_DVBT;
        if (ad->sys[0] == SYS_DVBC2)
            ad->sys[1] = SYS_DVBC_ANNEX_A;

        LOG("Setting delivery system for adapter %d to %s and %s", a_id,
            get_delsys(ad->sys[0]), get_delsys(ad->sys[1]));
    }
}

void set_signal_multiplier(char *o) {
    int i, la, a_id;
    float strength_multiplier, snr_multiplier;
    char force_tuner_signal = TUNER_FORCE_NO;
    char force_str[128];
    char buf[strlen(o) + 1], *arg[40], *sep1, *sep2;
    adapter *ad;
    safe_strncpy(buf, o);
    la = split(arg, buf, ARRAY_SIZE(arg), ',');
    for (i = 0; i < la; i++) {
        if (arg[i] && arg[i][0] == '*') {
            ad = NULL;
            a_id = -1;
        } else {
            a_id = map_intd(arg[i], NULL, -1);
            if (a_id < 0 || a_id >= MAX_ADAPTERS)
                continue;

            if (!a[a_id])
                a[a_id] = adapter_alloc();
            ad = a[a_id];
        }

        sep1 = strchr(arg[i], ':');
        sep2 = strchr(arg[i], '-');

        if (!sep1 || !sep2)
            continue;

        if (sep1[1] == '%' || sep1[1] == '#') {
            force_tuner_signal |= sep1[1] == '%' ? TUNER_FORCE_STRENGTH_PERCENT
                                                 : TUNER_FORCE_STRENGTH_DECIBEL;
            strength_multiplier = strtod(sep1 + 2, NULL);
        } else {
            strength_multiplier = strtod(sep1 + 1, NULL);
        }
        if (sep2[1] == '%' || sep2[1] == '#') {
            force_tuner_signal |= sep2[1] == '%' ? TUNER_FORCE_SNR_PERCENT
                                                 : TUNER_FORCE_SNR_DECIBEL;
            snr_multiplier = strtod(sep2 + 2, NULL);
        } else {
            snr_multiplier = strtod(sep2 + 1, NULL);
        }
        if (strength_multiplier < 0 || snr_multiplier < 0)
            continue;

        if (ad) {
            ad->strength_multiplier = strength_multiplier;
            ad->snr_multiplier = snr_multiplier;
            ad->force_tuner_signal = force_tuner_signal;
        } else {
            opts.strength_multiplier = strength_multiplier;
            opts.snr_multiplier = snr_multiplier;
            int j;
            for (j = 0; j < MAX_ADAPTERS; j++)
                if (a[j]) {
                    a[j]->strength_multiplier = strength_multiplier;
                    a[j]->snr_multiplier = snr_multiplier;
                }
        }
        if (force_tuner_signal) {
            sprintf(force_str, " (forcing driver:%s%s%s%s)",
                    (force_tuner_signal & TUNER_FORCE_STRENGTH_PERCENT) > 0
                        ? " strength_in_percentage"
                        : "",
                    (force_tuner_signal & TUNER_FORCE_STRENGTH_DECIBEL) > 0
                        ? " strength_in_decibels"
                        : "",
                    (force_tuner_signal & TUNER_FORCE_SNR_PERCENT) > 0
                        ? " snr_in_percentage"
                        : "",
                    (force_tuner_signal & TUNER_FORCE_SNR_DECIBEL) > 0
                        ? " snr_in_decibels"
                        : "");
        } else
            force_str[0] = '\0';
        LOG("Setting signal multipler for adapter %d%s "
            "strength_multiplier %.2f "
            "snr_multiplier %.2f",
            a_id, force_str, (double)strength_multiplier,
            (double)snr_multiplier);
    }
}

int delsys_match(adapter *ad, int del_sys) {
    int i;
    if (!ad)
        LOG_AND_RETURN(0, "delsys_match: adapter is NULL, delsys %d", del_sys);

    if (del_sys == 0)
        LOG_AND_RETURN(
            0, "delsys_match: requesting delsys is 0 for adapter handle %d",
            ad->fe);

    for (i = 0; i < 10; i++)
        if (ad->sys[i] == (unsigned int)del_sys)
            return 1;
    return 0;
}

int signal_thread(sockets *s __attribute__((unused))) {
    int i, status;
    int64_t ts, ctime;
    adapter *ad;
    for (i = 0; i < MAX_ADAPTERS; i++) {
        if ((ad = get_adapter_nw(i)) == NULL || ad->get_signal == NULL)
            continue;
        if (ad->fe <= 0 || ad->tp.freq <= 0)
            continue;
        status = ad->status;
        if (ad->status_cnt++ <=
            0) // make sure the kernel has updated the status
            continue;
        // do not get the signal when the adapter is being changed
        if (!ad->mutex.try_lock())
            continue;
        ts = getTick();
        ad->get_signal(ad);
        ctime = getTick();
        if (status < 0 || (opts.log & DEFAULT_LOG))
            LOG("get_signal%s took %jd ms for adapter %d handle %d (status: "
                "%d, ber: "
                "%d, strength:%d, snr: %d, force scan %d)",
                (ad->new_gs == 1) ? "_new" : "", ctime - ts, ad->id, ad->fe,
                ad->status, ad->ber, ad->strength, ad->snr, opts.force_scan);
        ad->mutex.unlock();
    }
    return 0;
}

char *get_adapter_pids(int aid, char *dest, int max_size) {
    int len = 0;
    int i;
    adapter *ad = get_adapter_nw(aid);
    dest[0] = 0;

    if (!ad)
        return dest;

    for (i = 0; i < MAX_PIDS; i++)
        if (ad->pids[i].flags == PID_STATE_ACTIVE ||
            ad->pids[i].flags == PID_STATE_NEW) {
            int pid = ad->pids[i].pid;
            if (pid == 8192) {
                strlcatf(dest, max_size, len, "all,");
                break;
            } else
                strlcatf(dest, max_size, len, "%d,", pid);
        }
    if (len > 0)
        dest[len - 1] = 0;
    else
        strlcatf(dest, max_size, len, "none");

    return dest;
}

char *get_all_delsys(int aid, char *dest, int max_size) {
    int i, len;
    adapter *ad = get_adapter_nw(aid);
    dest[0] = 0;
    len = 0;
    if (!ad)
        return dest;

    for (i = 0; i < MAX_DELSYS; i++)
        if (ad->sys[i] > 0)
            strlcatf(dest, max_size, len, "%s,", get_delsys(ad->sys[i]));

    if (len > 0)
        dest[len - 1] = 0;

    return dest;
}

char *get_adapter_name(int aid, char *dest, int max_size) {
    adapter *ad = get_configured_adapter_nw(aid);
    if (!ad)
        return dest;

    _strncpy(dest, const_cast<char *>(ad->adapter_name.data()),
             ad->adapter_name.length() + 1);

    return dest;
}

int get_adapter_ccerrs(int aid) {
    int i, cc = 0;
    adapter *ad = get_adapter_nw(aid);
    if (!ad)
        return 0;

    for (i = 0; i < 2; i++)
        if (ad->pids[i].flags == PID_STATE_ACTIVE)
            cc += ad->pids[i].cc_err;
    return cc;
}

int get_adapter_decerrs(int aid) {
    int i, dec = 0;
    adapter *ad = get_adapter_nw(aid);
    if (!ad)
        return 0;

    for (i = 0; i < 2; i++)
        if (ad->pids[i].flags == PID_STATE_ACTIVE)
            dec += ad->pids[i].dec_err;
    return dec;
}

int get_adapter_fe(int aid) {
    int fe;
    uint32_t i;
    adapter *ad = get_adapter_nw(aid);
    if (!ad)
        return 0;
    fe = ad->tp.fe;
    if (fe == 0)
        return 0;

    for (i = 0; i < sizeof(fe_map) / sizeof(fe_map[0]); i++)
        if (fe_map[i] == fe - 1)
            return i + 1;
    return -1;
}

_symbols adapters_sym[] = {
    {"ad_enabled", VAR_AARRAY_INT8, a, 1, MAX_ADAPTERS,
     offsetof(adapter, enabled)},
    {"ad_type", VAR_AARRAY_INT8, a, 1, MAX_ADAPTERS, offsetof(adapter, type)},
    {"ad_pos", VAR_AARRAY_INT, a, 1, MAX_ADAPTERS,
     offsetof(adapter, tp.diseqc)},
    {"ad_freq", VAR_AARRAY_INT, a, 1. / 1000, MAX_ADAPTERS,
     offsetof(adapter, tp.freq)},
    {"ad_strength", VAR_AARRAY_UINT16, a, 1, MAX_ADAPTERS,
     offsetof(adapter, strength)},
    {"ad_snr", VAR_AARRAY_UINT16, a, 1, MAX_ADAPTERS, offsetof(adapter, snr)},
    {"ad_db", VAR_AARRAY_UINT16, a, 1, MAX_ADAPTERS, offsetof(adapter, db)},
    {"ad_ber", VAR_AARRAY_INT, a, 1, MAX_ADAPTERS, offsetof(adapter, ber)},
    {"ad_pol", VAR_AARRAY_INT, a, 1, MAX_ADAPTERS, offsetof(adapter, tp.pol)},
    {"ad_sr", VAR_AARRAY_INT, a, 1. / 1000, MAX_ADAPTERS,
     offsetof(adapter, tp.sr)},
    {"ad_bw", VAR_AARRAY_INT, a, 1. / 1000, MAX_ADAPTERS,
     offsetof(adapter, tp.bw)},
    {"ad_stream", VAR_AARRAY_INT, a, 1, MAX_ADAPTERS,
     offsetof(adapter, tp.plp_isi)},
    {"ad_fe", VAR_FUNCTION_INT, (void *)&get_adapter_fe, 0, MAX_ADAPTERS, 0},
    {"ad_master", VAR_AARRAY_UINT8, a, 1, MAX_ADAPTERS,
     offsetof(adapter, master_sid)},
    {"ad_sidcount", VAR_AARRAY_UINT8, a, 1, MAX_ADAPTERS,
     offsetof(adapter, sid_cnt)},
    {"ad_phyad", VAR_AARRAY_INT, a, 1, MAX_ADAPTERS, offsetof(adapter, pa)},
    {"ad_phyfd", VAR_AARRAY_INT, a, 1, MAX_ADAPTERS, offsetof(adapter, fn)},
    {"ad_sys", VAR_AARRAY_INT, a, 1, MAX_ADAPTERS, offsetof(adapter, tp.sys)},
    {"ad_mtype", VAR_AARRAY_INT, a, 1, MAX_ADAPTERS,
     offsetof(adapter, tp.mtype)},

    {"ad_t2id", VAR_AARRAY_INT, a, 1, MAX_ADAPTERS, offsetof(adapter, tp.t2id)},
    {"ad_c2tft", VAR_AARRAY_INT, a, 1, MAX_ADAPTERS,
     offsetof(adapter, tp.c2tft)},
    {"ad_plp", VAR_AARRAY_INT, a, 1, MAX_ADAPTERS, offsetof(adapter, tp.ds)},
    {"ad_plsc", VAR_AARRAY_INT, a, 1, MAX_ADAPTERS,
     offsetof(adapter, tp.pls_code)},
    {"ad_sm", VAR_AARRAY_INT, a, 1, MAX_ADAPTERS, offsetof(adapter, tp.sm)},
    {"ad_gi", VAR_AARRAY_INT, a, 1, MAX_ADAPTERS, offsetof(adapter, tp.gi)},

    {"ad_allsys", VAR_FUNCTION_STRING, (void *)&get_all_delsys, 0, MAX_ADAPTERS,
     0},
    {"ad_pids", VAR_FUNCTION_STRING, (void *)&get_adapter_pids, 0, MAX_ADAPTERS,
     0},
    {"ad_ccerr", VAR_FUNCTION_INT, (void *)&get_adapter_ccerrs, 0, MAX_ADAPTERS,
     0},
    {"ad_decerr", VAR_FUNCTION_INT, (void *)&get_adapter_decerrs, 0,
     MAX_ADAPTERS, 0},
    {"tuner_s2", VAR_INT, &tuner_s2, 1, 0, 0},
    {"tuner_t2", VAR_INT, &tuner_t2, 1, 0, 0},
    {"tuner_c2", VAR_INT, &tuner_c2, 1, 0, 0},
    {"tuner_t", VAR_INT, &tuner_t, 1, 0, 0},
    {"tuner_c", VAR_INT, &tuner_c, 1, 0, 0},
    {"tuner_ac", VAR_INT, &tuner_ac, 1, 0, 0},
    {"tuner_at", VAR_INT, &tuner_at, 1, 0, 0},
    {"ad_disabled", VAR_ARRAY_INT8, disabled, 1, MAX_ADAPTERS, 0},
    {"ad_adapter_names", VAR_FUNCTION_STRING, (void *)&get_adapter_name, 0,
     MAX_ADAPTERS, 0},
    {NULL, 0, NULL, 0, 0, 0}};
