/***************************************************************************//**
  @addtogroup lip_dns_sd
  @{
  @file lip_dns_sd.c
  @brief Файл содержит реализацию модуля @ref lip_dns_sd

  @author Borisov Alexey <borisov@lcard.ru>
  @date   05.09.2011
 ******************************************************************************/
#include "lip_config.h"
#ifdef LIP_USE_DNSSD
#include "string.h"
#include "lip_dns_sd_private.h"

#if (LIP_DNSSD_SERVICES_CNT > 255) || (LIP_DNSSD_SERVICES_CNT <= 0)
    #error "LIP_DNSSD_SERVICES_CNT must be between 1 and 255"
#endif

#include "lip/app/dns/mdns/lip_mdns_private.h"

/* Структура с информацией о зарегистрированном сервисе */
typedef struct {
    t_lip_dns_name srv_domain; /* Доменное имя, соответствующее типу сервиса */
    t_lip_dns_name srv_inst_domain; /* Доменное имя, соответствующее экземпляру сервиса */
    t_lip_dns_rdata_srv srv_data; /* Данные доменной записи о сервисе */
    t_lip_dns_rdata_txt txt; /* Даннве текстовой записи о сервисе */
    t_lip_dns_txt_keyval keys[LIP_DNSSD_SERVICE_MAX_TXT_KEYS]; /* Строки текстовой записи в види пар ключ/значение */
} t_service_state;

/* Информация о всех зарегистрированных сервисах */
static t_service_state f_services_state[LIP_DNSSD_SERVICES_CNT];
/* Текущее кол-во зарегистрированных сервисов */
static uint8_t f_services_cnt;
/* Вспомогательный домен, для поиска всех сервисов через @ref lip_mdns */
static t_lip_dns_name f_mdns_sd_services_metadomain;
/* Поддомен, служащий для поиска всех сервисов */
static const char f_dnssd_meta_srv_name[] = "_services._dns-sd._udp";



/***************************************************************************//**
  @internal
  Инициализация модуля DNS-SD.
  Вызывается ядром стека из lip_init().
  Устанавливаем локальные переменные в значения по-умолчаниюю
 ******************************************************************************/
void lip_dnssd_init(void) {
    f_mdns_sd_services_metadomain.name = f_dnssd_meta_srv_name;
    f_mdns_sd_services_metadomain.parent = lip_mdns_get_parent_domain();
    f_services_cnt = 0;
}
/***************************************************************************//**
  @internal
  Закрытие модуля @ref lip_dns_sd. Вызывается ядром из lip_close().
  Удаляем все зарегистрированные сервисы
 ******************************************************************************/
void lip_dnssd_close(void) {
    for (int i=f_services_cnt-1; i>=0; i--)  {
        lip_dnssd_unregister_local(f_services_state[i].srv_inst_domain.name,
                                   f_services_state[i].srv_domain.name, 0);
    }
}


/***************************************************************************//**
  Регистрация сервиса в локальной сети с помощью @ref lip_mdns.
  @param[in] srv_instanse    Имя экземпляра сервиса (должно быть уникально в сети)
  @param[in] srv_name        Имя типа сервеса в виде _servicetype._tcp или
                             _servicetype._udp, где servicetype --- произвольное
                             имя типа сервиса.
  @param[in] port            Используемый порт протокола [транспортного уровня](@ref lip_lvl_transport)
  @param[in] cb              Callback-функция, вызываемая при возникновении конфликта
                             для указаонного имени экземпляра --- должна предоставить
                             новое имя
  @param[in] keyvals         Массив структур, содержащих пары строк ключ-значение,
                             в количестве @c keys_cnt. Данные ключи будут добавлены
                             в текстовую запись, описывающую экземпляр сервиса
  @param[in] keys_cnt        Количество пар ключ-значение, описывающих заданный сервис
  @return                    Код ошибки
  *****************************************************************************/
t_lip_errs lip_dnssd_register_local(const char *srv_instanse, const char *srv_name,
                             uint16_t port, t_lip_mdns_name_conflict_cb cb,
                             const t_lip_dns_txt_keyval *keyvals, unsigned keys_cnt) {
    t_lip_errs err = LIP_ERR_SUCCESS;
    if (keys_cnt > LIP_DNSSD_SERVICE_MAX_TXT_KEYS) {
        err = LIP_ERR_DNSSD_TOO_MANY_TXT_KEYS;
    } else if (f_services_cnt == LIP_DNSSD_SERVICES_CNT) {
        err = LIP_ERR_DNSSD_NO_FREE_SERVICES;
    } else {
        t_service_state *srv = &f_services_state[f_services_cnt];
        t_lip_dns_rr rr;
        rr.rclass = LIP_DNS_CLASS_IN;

        /* создаем доменное имя, соответствующее типу сервиса */
        srv->srv_domain.name = srv_name;
        srv->srv_domain.parent = lip_mdns_get_parent_domain();
        /* создаем доменное имя, соответствующее конкретному экземляру сервиса */
        srv->srv_inst_domain.name = srv_instanse;
        srv->srv_inst_domain.parent = &srv->srv_domain;
        /* заполняем поля записи SRV, указывающей что наш хост реализует экзепляр
           заданного сервиса */
        srv->srv_data.port = port;
        srv->srv_data.priority = srv->srv_data.weight = 0;
        srv->srv_data.target = lip_mdns_get_host_domain();

        rr.type = LIP_DNS_RRTYPE_SRV;
        rr.ttl = LIP_MDNS_HOSTNAME_TTL;
        rr.rdlength = sizeof(srv->srv_data);
        err = lip_mdns_rr_add(&srv->srv_inst_domain, &rr, &srv->srv_data,
                              LIP_MDNS_ADDFLAGS_UNIQUE, cb);
        if (err == LIP_ERR_SUCCESS) {
            /* добавляем текстовую запись с ключами */

            srv->txt.format = LIP_DNS_TXT_FORMAT_KEYS;
            srv->txt.str_cnt = keys_cnt*2;
            srv->txt.str = (const char**)&srv->keys;
            for (unsigned i=0; i < keys_cnt; i++) {
                srv->keys[i] = keyvals[i];
            }
            rr.type = LIP_DNS_RRTYPE_TXT;
            rr.ttl = LIP_MDNS_DEFAULT_TTL;
            rr.rdlength = sizeof(srv->txt);
            err = lip_mdns_rr_add(&srv->srv_inst_domain, &rr, &srv->txt, 0, NULL);

            if (err == LIP_ERR_SUCCESS) {
                /* добавляем запись PTR, указывающую на SRV - запись, связвающую
                   экзепляр и тип сервиса */
                rr.type = LIP_DNS_RRTYPE_PTR;
                rr.ttl = LIP_MDNS_DEFAULT_TTL;
                rr.rdlength = sizeof(t_lip_dns_name);
                err = lip_mdns_rr_add(&srv->srv_domain, &rr, &srv->srv_inst_domain, LIP_MDNS_ADDFLAGS_DNS_SD_PTR, NULL);
                if (err == LIP_ERR_SUCCESS) {
                    /* проверяем, что сервиса с таким именем еще не было (на случай
                       нескольких instance для одного сервиса) */
                    int fnd = 0;
                    for (unsigned i=0; !fnd && (i < f_services_cnt); i++) {
                        if (!strcmp(srv_name, f_services_state[i].srv_domain.name))
                            fnd = 1;
                    }

                    if (!fnd) {
                        /* Если сервис новый, то добавляем запись PTR, связывающую
                           имя для перечисления всех сервисов с регистрируемым типом
                           сервиса */
                        err = lip_mdns_rr_add(&f_mdns_sd_services_metadomain, &rr,
                                          &srv->srv_domain, 0, NULL);
                    }

                    /* при неудаче удаляем все уже зарегестрированные имена */
                    if (err != LIP_ERR_SUCCESS) {
                        lip_mdns_rr_remove(&srv->srv_domain, LIP_DNS_RRTYPE_PTR,
                                        &srv->srv_inst_domain, 0);
                    }
                }

                if (err != LIP_ERR_SUCCESS) {
                    lip_mdns_rr_remove(&srv->srv_inst_domain, LIP_DNS_RRTYPE_TXT,
                                       &srv->txt, 0);
                }
            }

            if (err != LIP_ERR_SUCCESS) {
                lip_mdns_rr_remove(&srv->srv_inst_domain, LIP_DNS_RRTYPE_SRV,
                                   &srv->srv_data, LIP_MDNS_REMFLAGS_FLUSH);
            }
        }

        if (err == LIP_ERR_SUCCESS)
            f_services_cnt++;
    }

    return err;
}

/***************************************************************************//**
  Удаление всех записей, соответствующие зарегистрированному с помощью
  lip_dnssd_register_local() сервису
  @param[in] srv_instanse    Имя экземпляра сервиса (указатель на ту же строку,
                             что и в lip_dnssd_register_local())
  @param[in] srv_name        Имя типа сервеса (указатель на ту же строку,
                             что и в lip_dnssd_register_local())
  @param[in] flags           В флагах может использоваться #LIP_MDNS_REMFLAGS_FLUSH
                             для указания, что данные сервиса должны быть немедленно
                             удалены по завершению работы функции. Иначе они могут
                             использоваться до следующего продвижения стека
  *****************************************************************************/
void lip_dnssd_unregister_local(const char *srv_instanse, const char *srv_name, unsigned flags) {


    for (int i = f_services_cnt - 1; i >= 0; --i) {
        if ((f_services_state[i].srv_domain.name == srv_name)
                && (f_services_state[i].srv_inst_domain.name == srv_instanse)) {
            int fnd = 0;
            for (int j = 0; !fnd && (j < f_services_cnt); j++) {
                if ((i!=j) && !strcmp(srv_name, f_services_state[j].srv_domain.name))
                    fnd = 1;
            }

            /* если это последний экземпляр сервиса, то удаляем запись в списке
               всех сервисов сети */
            if (!fnd) {
                lip_mdns_rr_remove(&f_mdns_sd_services_metadomain, LIP_DNS_RRTYPE_PTR,
                                 &f_services_state[i].srv_domain, 0);
            }
            /* удаляем все записи, связанные с заданным сервисом */
            lip_mdns_rr_remove(&f_services_state[i].srv_domain, LIP_DNS_RRTYPE_PTR,
                                &f_services_state[i].srv_inst_domain, 0);
            lip_mdns_rr_remove(&f_services_state[i].srv_inst_domain, LIP_DNS_RRTYPE_TXT,
                                &f_services_state[i].txt, 0);
            lip_mdns_rr_remove(&f_services_state[i].srv_inst_domain, LIP_DNS_RRTYPE_SRV,
                                &f_services_state[i].srv_data, flags);

            /* уменьшаем количество зарегистрированных сервисов */
            f_services_cnt--;
#if LIP_DNSSD_SERVICES_CNT > 1
            if (i != f_services_cnt) {
                memmove(&f_services_state[i], &f_services_state[i+1],
                        (f_services_cnt-i)*sizeof(f_services_state[0]));
            }
#endif
        }
    }
}

void lip_dnssd_hostname_ch_cb(void) {
    for (unsigned i = 0; i < f_services_cnt; ++i) {
        lip_mdns_rr_reannonce(&f_services_state[i].srv_inst_domain, LIP_DNS_RRTYPE_SRV,
                              &f_services_state[i].srv_data, sizeof(t_lip_dns_rdata_srv));
    }
}


#endif

/** @}*/

