/*
 * Copyright (C) 2014-2022 CZ.NIC
 *
 * 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/>.
 *
 * In addition, as a special exception, the copyright holders give
 * permission to link the code of portions of this program with the
 * OpenSSL library under certain conditions as described in each
 * individual source file, and distribute linked combinations including
 * the two.
 */

#if defined(__APPLE__) || defined(__clang__)
#  define __USE_C99_MATH
#  define _Bool bool
#else /* !__APPLE__ */
#  include <cstdbool>
#endif /* __APPLE__ */

#include <cstdlib> /* ::std::malloc */
#include <cstring> /* ::std::memset */
#include <libdatovka/isds.h>

#include "src/datovka_shared/isds/internal_conversion.h"
#include "src/datovka_shared/isds/type_conversion.h"
#include "src/isds/box_conversion.h"
#include "src/isds/conversion_internal.h"
#include "src/isds/internal_conversion.h" /* struct isds_timeval */
#include "src/isds/internal_type_conversion.h"

/*!
 * @brief Set address according to isds_Address structure.
 */
static
bool setAddressContent(Isds::Address &tgt, const struct isds_Address *src)
{
	if (Q_UNLIKELY(src == NULL)) {
		return true;
	}

	tgt.setCity(Isds::fromCStr(src->adCity));
	//tgt.setDistrict(Isds::fromCStr(src->adDistrict));
	tgt.setStreet(Isds::fromCStr(src->adStreet));
	tgt.setNumberInStreet(Isds::fromCStr(src->adNumberInStreet));
	tgt.setNumberInMunicipality(Isds::fromCStr(src->adNumberInMunicipality));
	tgt.setZipCode(Isds::fromCStr(src->adZipCode));
	tgt.setState(Isds::fromCStr(src->adState));
	//tgt.setAmCode(Isds::fromLongInt(src->adCode));
	return true;
}

Isds::Address Isds::libisds2address(const struct isds_Address *ia, bool *ok)
{
	Address address;
	bool ret = setAddressContent(address, ia);
	if (ok != Q_NULLPTR) {
		*ok = ret;
	}
	return address;
}

/*!
 * @brief Set isds_Address structure according to the address.
 */
static
bool setLibisdsAddressContent(struct isds_Address *tgt,
    const Isds::Address &src)
{
	if (Q_UNLIKELY(tgt == NULL)) {
		Q_ASSERT(0);
		return false;
	}

	if (Q_UNLIKELY(!Isds::toCStrCopy(&tgt->adCity, src.city()))) {
		return false;
	}
	//if (Q_UNLIKELY(!Isds::toCStrCopy(&tgt->adDistrict, src.district()))) {
	//	return false;
	//}
	if (Q_UNLIKELY(!Isds::toCStrCopy(&tgt->adStreet, src.street()))) {
		return false;
	}
	if (Q_UNLIKELY(!Isds::toCStrCopy(&tgt->adNumberInStreet, src.numberInStreet()))) {
		return false;
	}
	if (Q_UNLIKELY(!Isds::toCStrCopy(&tgt->adNumberInMunicipality, src.numberInMunicipality()))) {
		return false;
	}
	if (Q_UNLIKELY(!Isds::toCStrCopy(&tgt->adZipCode, src.zipCode()))) {
		return false;
	}
	if (Q_UNLIKELY(!Isds::toCStrCopy(&tgt->adState, src.state()))) {
		return false;
	}
	//if (Q_UNLIKELY(!Isds::toLongInt(&tgt->adCode, src.amCode()))) {
	//	return false;
	//}

	return true;
}

struct isds_Address *Isds::address2libisds(const Address &a, bool *ok)
{
	if (Q_UNLIKELY(a.isNull())) {
		if (ok != Q_NULLPTR) {
			*ok = true;
		}
		return NULL;
	}

	struct isds_Address *ia =
	    (struct isds_Address *)::std::malloc(sizeof(*ia));
	if (Q_UNLIKELY(ia == NULL)) {
		Q_ASSERT(0);
		if (ok != Q_NULLPTR) {
			*ok = false;
		}
		return NULL;
	}
	::std::memset(ia, 0, sizeof(*ia));

	if (Q_UNLIKELY(!setLibisdsAddressContent(ia, a))) {
		isds_Address_free(&ia);
		if (ok != Q_NULLPTR) {
			*ok = false;
		}
		return NULL;
	}
	if (ok != Q_NULLPTR) {
		*ok = true;
	}
	return ia;
}

/*!
 * @brief Set birth info according to isds_BirthInfo structure.
 */
static
bool setBirthInfoContent(Isds::BirthInfo &tgt,
     const struct isds_BirthInfo *src)
{
	if (Q_UNLIKELY(src == NULL)) {
		return true;
	}

	tgt.setDate(Isds::dateFromStructTM(src->biDate));
	tgt.setCity(Isds::fromCStr(src->biCity));
	tgt.setCounty(Isds::fromCStr(src->biCounty));
	tgt.setState(Isds::fromCStr(src->biState));
	return true;
}

Isds::BirthInfo Isds::libisds2birthInfo(const struct isds_BirthInfo *ibi,
    bool *ok)
{
	BirthInfo birthInfo;
	bool ret = setBirthInfoContent(birthInfo, ibi);
	if (ok != Q_NULLPTR) {
		*ok = ret;
	}
	return birthInfo;
}

/*!
 * @brief Set isds_BirthInfo structure according to the birth info.
 */
static
bool setLibisdsBirthInfoContent(struct isds_BirthInfo *tgt,
    const Isds::BirthInfo &src)
{
	if (Q_UNLIKELY(tgt == NULL)) {
		Q_ASSERT(0);
		return false;
	}

	if (Q_UNLIKELY(!Isds::toCDateCopy(&tgt->biDate, src.date()))) {
		return false;
	}
	if (Q_UNLIKELY(!Isds::toCStrCopy(&tgt->biCity, src.city()))) {
		return false;
	}
	if (Q_UNLIKELY(!Isds::toCStrCopy(&tgt->biCounty, src.county()))) {
		return false;
	}
	if (Q_UNLIKELY(!Isds::toCStrCopy(&tgt->biState, src.state()))) {
		return false;
	}

	return true;
}

struct isds_BirthInfo *Isds::birthInfo2libisds(const BirthInfo &bi, bool *ok)
{
	if (Q_UNLIKELY(bi.isNull())) {
		if (ok != Q_NULLPTR) {
			*ok = true;
		}
		return NULL;
	}

	struct isds_BirthInfo *ibi =
	    (struct isds_BirthInfo *)::std::malloc(sizeof(*ibi));
	if (Q_UNLIKELY(ibi == NULL)) {
		Q_ASSERT(0);
		if (ok != Q_NULLPTR) {
			*ok = false;
		}
		return NULL;
	}
	::std::memset(ibi, 0, sizeof(*ibi));

	if (Q_UNLIKELY(!setLibisdsBirthInfoContent(ibi, bi))) {
		isds_BirthInfo_free(&ibi);
		if (ok != Q_NULLPTR) {
			*ok = false;
		}
		return NULL;
	}
	if (ok != Q_NULLPTR) {
		*ok = true;
	}
	return ibi;
}

/*!
 * @brief Set person name according to isds_PersonName structure.
 */
static
bool setPersonNameContent(Isds::PersonName &tgt,
    const struct isds_PersonName *src)
{
	if (Q_UNLIKELY(src == NULL)) {
		return true;
	}

	tgt.setFirstName(Isds::fromCStr(src->pnFirstName));
	tgt.setMiddleName(Isds::fromCStr(src->pnMiddleName));
	tgt.setLastName(Isds::fromCStr(src->pnLastName));
	tgt.setLastNameAtBirth(Isds::fromCStr(src->pnLastNameAtBirth));
	return true;
}

Isds::PersonName Isds::libisds2personName(const struct isds_PersonName *ipn,
    bool *ok)
{
	PersonName personName;
	bool ret = setPersonNameContent(personName, ipn);
	if (ok != Q_NULLPTR) {
		*ok = ret;
	}
	return personName;
}

/*!
 * @brief Set isds_PersonName structure according to the person name.
 */
static
bool setLibisdsPersonNameContent(struct isds_PersonName *tgt,
    const Isds::PersonName &src)
{
	if (Q_UNLIKELY(tgt == NULL)) {
		Q_ASSERT(0);
		return false;
	}

	if (Q_UNLIKELY(!Isds::toCStrCopy(&tgt->pnFirstName, src.firstName()))) {
		return false;
	}
	if (Q_UNLIKELY(!Isds::toCStrCopy(&tgt->pnMiddleName, src.middleName()))) {
		return false;
	}
	if (Q_UNLIKELY(!Isds::toCStrCopy(&tgt->pnLastName, src.lastName()))) {
		return false;
	}
	if (Q_UNLIKELY(!Isds::toCStrCopy(&tgt->pnLastNameAtBirth, src.lastNameAtBirth()))) {
		return false;
	}

	return true;
}

struct isds_PersonName *Isds::personName2libisds(const PersonName &pn, bool *ok)
{
	if (Q_UNLIKELY(pn.isNull())) {
		if (ok != Q_NULLPTR) {
			*ok = true;
		}
		return NULL;
	}

	struct isds_PersonName *ipn =
	    (struct isds_PersonName *)::std::malloc(sizeof(*ipn));
	if (Q_UNLIKELY(ipn == NULL)) {
		Q_ASSERT(0);
		if (ok != Q_NULLPTR) {
			*ok = false;
		}
		return NULL;
	}
	::std::memset(ipn, 0, sizeof(*ipn));

	if (Q_UNLIKELY(!setLibisdsPersonNameContent(ipn, pn))) {
		isds_PersonName_free(&ipn);
		if (ok != Q_NULLPTR) {
			*ok = false;
		}
		return NULL;
	}
	if (ok != Q_NULLPTR) {
		*ok = true;
	}
	return ipn;
}

Isds::DbOwnerInfo Isds::libisds2dbOwnerInfo(const struct isds_DbOwnerInfo *idoi,
    bool *ok)
{
	if (Q_UNLIKELY(idoi == Q_NULLPTR)) {
		if (ok != Q_NULLPTR) {
			*ok = true;
		}
		return DbOwnerInfo();
	}

	bool iOk = false;
	DbOwnerInfo ownerInfo;

	ownerInfo.setDbID(Isds::fromCStr(idoi->dbID));
	ownerInfo.setDbType(
	    IsdsInternal::libisdsDbTypePtr2DbType(idoi->dbType, &iOk));
	if (Q_UNLIKELY(!iOk)) {
		goto fail;
	}
	ownerInfo.setIc(Isds::fromCStr(idoi->ic));
	ownerInfo.setPersonName(libisds2personName(idoi->personName, &iOk));
	if (Q_UNLIKELY(!iOk)) {
		goto fail;
	}
	ownerInfo.setFirmName(Isds::fromCStr(idoi->firmName));
	ownerInfo.setBirthInfo(libisds2birthInfo(idoi->birthInfo, &iOk));
	if (Q_UNLIKELY(!iOk)) {
		goto fail;
	}
	ownerInfo.setAddress(libisds2address(idoi->address, &iOk));
	if (Q_UNLIKELY(!iOk)) {
		goto fail;
	}
	ownerInfo.setNationality(Isds::fromCStr(idoi->nationality));
	ownerInfo.setEmail(Isds::fromCStr(idoi->email));
	ownerInfo.setTelNumber(Isds::fromCStr(idoi->telNumber));
	ownerInfo.setIdentifier(Isds::fromCStr(idoi->identifier));
	ownerInfo.setRegistryCode(Isds::fromCStr(idoi->registryCode));
	ownerInfo.setDbState(
	    IsdsInternal::longPtr2DbState(idoi->dbState, &iOk));
	if (Q_UNLIKELY(!iOk)) {
		goto fail;
	}
	ownerInfo.setDbEffectiveOVM(fromBoolPtr(idoi->dbEffectiveOVM));
	ownerInfo.setDbOpenAddressing(fromBoolPtr(idoi->dbOpenAddressing));

	if (ok != Q_NULLPTR) {
		*ok = true;
	}
	return ownerInfo;

fail:
	if (ok != Q_NULLPTR) {
		*ok = false;
	}
	return DbOwnerInfo();
}

/*!
 * @brief Set isds_DbOwnerInfo structure according to the owner info.
 */
static
bool setLibisdsDbOwnerInfoContent(struct isds_DbOwnerInfo *tgt,
    const Isds::DbOwnerInfo &src)
{
	if (Q_UNLIKELY(tgt == NULL)) {
		Q_ASSERT(0);
		return false;
	}

	bool iOk = false;

	if (Q_UNLIKELY(!Isds::toCStrCopy(&tgt->dbID, src.dbID()))) {
		return false;
	}
	if (Q_UNLIKELY(!IsdsInternal::dbType2libisdsDbTypePtr(&tgt->dbType,
	        src.dbType()))) {
		return false;
	}
	if (Q_UNLIKELY(!Isds::toCStrCopy(&tgt->ic, src.ic()))) {
		return false;
	}
	tgt->personName = Isds::personName2libisds(src.personName(), &iOk);
	if (Q_UNLIKELY(!iOk)) {
		return false;
	}
	if (Q_UNLIKELY(!Isds::toCStrCopy(&tgt->firmName, src.firmName()))) {
		return false;
	}
	tgt->birthInfo = Isds::birthInfo2libisds(src.birthInfo(), &iOk);
	if (Q_UNLIKELY(!iOk)) {
		return false;
	}
	tgt->address = Isds::address2libisds(src.address(), &iOk);
	if (Q_UNLIKELY(!iOk)) {
		return false;
	}
	if (Q_UNLIKELY(!Isds::toCStrCopy(&tgt->nationality, src.nationality()))) {
		return false;
	}
	if (Q_UNLIKELY(!Isds::toCStrCopy(&tgt->email, src.email()))) {
		return false;
	}
	if (Q_UNLIKELY(!Isds::toCStrCopy(&tgt->telNumber, src.telNumber()))) {
		return false;
	}
	if (Q_UNLIKELY(!Isds::toCStrCopy(&tgt->identifier, src.identifier()))) {
		return false;
	}
	if (Q_UNLIKELY(!Isds::toCStrCopy(&tgt->registryCode, src.registryCode()))) {
		return false;
	}
	if (Q_UNLIKELY(!IsdsInternal::dbState2longPtr(&tgt->dbState,
	        src.dbState()))) {
		return false;
	}
	if (Q_UNLIKELY(!toBoolPtr(&tgt->dbEffectiveOVM, src.dbEffectiveOVM()))) {
		return false;
	}
	if (Q_UNLIKELY(!toBoolPtr(&tgt->dbOpenAddressing, src.dbOpenAddressing()))) {
		return false;
	}

	return true;
}

struct isds_DbOwnerInfo *Isds::dbOwnerInfo2libisds(const DbOwnerInfo &doi,
    bool *ok)
{
	if (Q_UNLIKELY(doi.isNull())) {
		if (ok != Q_NULLPTR) {
			*ok = true;
		}
		return NULL;
	}

	struct isds_DbOwnerInfo *idoi =
	    (struct isds_DbOwnerInfo *)::std::malloc(sizeof(*idoi));
	if (Q_UNLIKELY(idoi == NULL)) {
		Q_ASSERT(0);
		if (ok != Q_NULLPTR) {
			*ok = false;
		}
		return NULL;
	}
	::std::memset(idoi, 0, sizeof(*idoi));

	if (Q_UNLIKELY(!setLibisdsDbOwnerInfoContent(idoi, doi))) {
		isds_DbOwnerInfo_free(&idoi);
		if (ok != Q_NULLPTR) {
			*ok = false;
		}
		return NULL;
	}
	if (ok != Q_NULLPTR) {
		*ok = true;
	}
	return idoi;
}

QList<Isds::DbOwnerInfo> Isds::libisds2dbOwnerInfoList(
    const struct isds_list *ioil, bool *ok)
{
	/* Owner info destructor function type. */
	typedef void (*own_info_destr_func_t)(struct isds_DbOwnerInfo **);

	QList<DbOwnerInfo> ownerInfoList;

	while (ioil != NULL) {
		const struct isds_DbOwnerInfo *ioi = (struct isds_DbOwnerInfo *)ioil->data;
		own_info_destr_func_t idestr = (own_info_destr_func_t)ioil->destructor;
		/* Destructor function must be set. */
		if (Q_UNLIKELY(!IsdsInternal::crudeEqual(idestr, isds_DbOwnerInfo_free))) {
			Q_ASSERT(0);
			if (ok != Q_NULLPTR) {
				*ok = false;
			}
			return QList<DbOwnerInfo>();
		}

		if (ioi != NULL) {
			bool iOk = false;
			ownerInfoList.append(libisds2dbOwnerInfo(ioi, &iOk));
			if (Q_UNLIKELY(!iOk)) {
				if (ok != Q_NULLPTR) {
					*ok = false;
				}
				return QList<DbOwnerInfo>();
			}
		}

		ioil = ioil->next;
	}

	if (ok != Q_NULLPTR) {
		*ok = true;
	}
	return ownerInfoList;
}

struct isds_list *Isds::dbOwnerInfoList2libisds(
    const QList<DbOwnerInfo> &oil, bool *ok)
{
	if (Q_UNLIKELY(oil.isEmpty())) {
		if (ok != Q_NULLPTR) {
			*ok = true;
		}
		return NULL;
	}

	struct isds_list *ioil = NULL;
	struct isds_list *lastItem = NULL;
	foreach (const DbOwnerInfo &oi, oil) {
		struct isds_list *item =
		    (struct isds_list *)::std::malloc(sizeof(*item));
		if (Q_UNLIKELY(item == NULL)) {
			Q_ASSERT(0);
			goto fail;
		}
		::std::memset(item, 0, sizeof(*item));

		bool iOk = false;
		struct isds_DbOwnerInfo *ioi = dbOwnerInfo2libisds(oi, &iOk);
		if (Q_UNLIKELY(!iOk)) {
			::std::free(item); item = NULL;
			goto fail;
		}

		/* Set list item. */
		item->next = NULL;
		item->data = ioi;
		item->destructor = (void (*)(void **))isds_DbOwnerInfo_free;

		/* Append item. */
		if (lastItem == NULL) {
			ioil = item;
		} else {
			lastItem->next = item;
		}
		lastItem = item;
	}

	if (ok != Q_NULLPTR) {
		*ok = true;
	}
	return ioil;

fail:
	isds_list_free(&ioil);
	if (ok != Q_NULLPTR) {
		*ok = false;
	}
	return NULL;
}

Isds::DbUserInfo Isds::libisds2dbUserInfo(const struct isds_DbUserInfo *idui,
    bool *ok)
{
	if (Q_UNLIKELY(idui == Q_NULLPTR)) {
		if (ok != Q_NULLPTR) {
			*ok = true;
		}
		return DbUserInfo();
	}

	bool iOk = false;
	DbUserInfo userInfo;

	userInfo.setPersonName(libisds2personName(idui->personName, &iOk));
	if (Q_UNLIKELY(!iOk)) {
		goto fail;
	}
	userInfo.setAddress(libisds2address(idui->address, &iOk));
	if (Q_UNLIKELY(!iOk)) {
		goto fail;
	}
	userInfo.setBiDate(dateFromStructTM(idui->biDate));
	userInfo.setUserID(fromCStr(idui->userID));
	userInfo.setUserType(
	    IsdsInternal::libisdsUserType2UserType(idui->userType, &iOk));
	if (Q_UNLIKELY(!iOk)) {
		goto fail;
	}
	userInfo.setUserPrivils(
	    IsdsInternal::longPtr2Privileges(idui->userPrivils));
	userInfo.setIc(fromCStr(idui->ic));
	userInfo.setFirmName(fromCStr(idui->firmName));
	userInfo.setCaStreet(fromCStr(idui->caStreet));
	userInfo.setCaCity(fromCStr(idui->caCity));
	userInfo.setCaZipCode(fromCStr(idui->caZipCode));
	userInfo.setCaState(fromCStr(idui->caState));

	if (ok != Q_NULLPTR) {
		*ok = true;
	}
	return userInfo;

fail:
	if (ok != Q_NULLPTR) {
		*ok = false;
	}
	return DbUserInfo();
}

/*!
 * @brief Set isds_DbOwnerInfo structure according to the user info.
 */
static
bool setLibisdsDbUserInfoContent(struct isds_DbUserInfo *tgt,
    const Isds::DbUserInfo &src)
{
	if (Q_UNLIKELY(tgt == NULL)) {
		Q_ASSERT(0);
		return false;
	}

	bool iOk = false;

	if (Q_UNLIKELY(!Isds::toCStrCopy(&tgt->userID, src.userID()))) {
		return false;
	}
	if (Q_UNLIKELY(!IsdsInternal::userType2libisdsUserType(&tgt->userType,
	        src.userType()))) {
		return false;
	}
	if (Q_UNLIKELY(!IsdsInternal::privileges2longPtr(&tgt->userPrivils,
	        src.userPrivils()))) {
		return false;
	}
	tgt->personName = personName2libisds(src.personName(), &iOk);
	if (Q_UNLIKELY(!iOk)) {
		return false;
	}
	tgt->address = address2libisds(src.address(), &iOk);
	if (Q_UNLIKELY(!iOk)) {
		return false;
	}
	if (Q_UNLIKELY(!Isds::toCDateCopy(&tgt->biDate, src.biDate()))) {
		return false;
	}
	if (Q_UNLIKELY(!Isds::toCStrCopy(&tgt->ic, src.ic()))) {
		return false;
	}
	if (Q_UNLIKELY(!Isds::toCStrCopy(&tgt->firmName, src.firmName()))) {
		return false;
	}
	if (Q_UNLIKELY(!Isds::toCStrCopy(&tgt->caStreet, src.caStreet()))) {
		return false;
	}
	if (Q_UNLIKELY(!Isds::toCStrCopy(&tgt->caCity, src.caCity()))) {
		return false;
	}
	if (Q_UNLIKELY(!Isds::toCStrCopy(&tgt->caZipCode, src.caZipCode()))) {
		return false;
	}
	if (Q_UNLIKELY(!Isds::toCStrCopy(&tgt->caState, src.caState()))) {
		return false;
	}

	return true;
}

struct isds_DbUserInfo *Isds::dbUserInfo2libisds(const DbUserInfo &dui,
    bool *ok)
{
	if (Q_UNLIKELY(dui.isNull())) {
		if (ok != Q_NULLPTR) {
			*ok = true;
		}
		return NULL;
	}

	struct isds_DbUserInfo *idui =
	    (struct isds_DbUserInfo *)::std::malloc(sizeof(*idui));
	if (Q_UNLIKELY(idui == NULL)) {
		Q_ASSERT(0);
		if (ok != Q_NULLPTR) {
			*ok = false;
		}
		return NULL;
	}
	::std::memset(idui, 0, sizeof(*idui));

	if (Q_UNLIKELY(!setLibisdsDbUserInfoContent(idui, dui))) {
		isds_DbUserInfo_free(&idui);
		if (ok != Q_NULLPTR) {
			*ok = false;
		}
		return NULL;
	}
	if (ok != Q_NULLPTR) {
		*ok = true;
	}
	return idui;
}

/*!
 * @brief Compute number of characters between pointers.
 *
 * @note This method is needed to adjust for UTF encoding.
 *
 * @param[in]  start Start of string.
 * @param[in]  stop End of string.
 * @param[out] ok Set to false on error, true on success.
 * @return Number of characters (not bytes) between the two pointers.
 */
static
int charactersBetweenPtrs(const char *start, const char *stop,
    bool *ok = Q_NULLPTR)
{
	if (Q_UNLIKELY(start > stop)) {
		if (ok != Q_NULLPTR) {
			*ok = false;
		}
		return -1;
	}

	ssize_t len = stop - start;
	if (len == 0) {
		if (ok != Q_NULLPTR) {
			*ok = true;
		}
		return 0;
	}

	len = QString(QByteArray(start, len)).size();
	if (ok != Q_NULLPTR) {
		*ok = true;
	}
	return len;
}

/*!
 * @brief Converts list of start/stop pointers into indexes into QString.
 *
 * @param[in]  str Start of C string.
 * @param[in]  starts List of start indexes.
 * @param[in]  stops List of stop indexes.
 * @param[out] ok Set to false on error, true on success.
 * @return List of start/stop index pairs.
 */
static
QList< QPair<int, int> > libisdsStartStop2startStop(const char *str,
    struct isds_list *starts, struct isds_list *stops, bool *ok = Q_NULLPTR)
{
	if ((str == NULL) || ((starts == NULL) && (stops == NULL))) {
		if (ok != Q_NULLPTR) {
			*ok = true;
		}
		return QList< QPair<int, int> >();
	}

	if ((starts == NULL) || (stops == NULL)) {
		if (ok != Q_NULLPTR) {
			*ok = false;
		}
		return QList< QPair<int, int> >();
	}

	QList< QPair<int, int> > resList;

	while ((starts != NULL) && (stops != NULL)) {
		const char *start = (char *)starts->data;
		const char *stop = (char *)stops->data;

		/* Destructor functions must not be set. */
		if (Q_UNLIKELY((starts->destructor != NULL) || (stops->destructor != NULL))) {
			Q_ASSERT(0);
			if (ok != Q_NULLPTR) {
				*ok = false;
			}
			return QList< QPair<int, int> >();
		}

		qint64 diffStart;
		qint64 diffStop;
		/*
		 * UTF8 characters skew the positions, therefore simple pointer subtraction
		 * diffStart = start - str;
		 * diffStop = stop - str;
		 * canot be performed.
		 */
		diffStart = charactersBetweenPtrs(str, start);
		diffStop = charactersBetweenPtrs(str, stop);
		if (Q_UNLIKELY((diffStart < 0) || (diffStop < 0) || (diffStart > diffStop))) {
			if (ok != Q_NULLPTR) {
				*ok = false;
			}
			return QList< QPair<int, int> >();
		}

		resList.append(QPair<int, int>(diffStart, diffStop));

		starts = starts->next;
		stops = stops->next;
	}

	/* Both lists must be equally long. */
	if (Q_UNLIKELY((starts != NULL) || (stops != NULL))) {
		Q_ASSERT(0);
		if (ok != Q_NULLPTR) {
			*ok = false;
		}
		return QList< QPair<int, int> >();
	}

	if (ok != Q_NULLPTR) {
		*ok = true;
	}
	return resList;
}

Isds::FulltextResult Isds::libisds2fulltextResult(
    const struct isds_fulltext_result *ifr, bool *ok)
{
	if (Q_UNLIKELY(ifr == Q_NULLPTR)) {
		if (ok != Q_NULLPTR) {
			*ok = true;
		}
		return FulltextResult();
	}

	bool iOk = false;
	FulltextResult fulltextResult;

	fulltextResult.setDbID(fromCStr(ifr->dbID));
	fulltextResult.setDbType(IsdsInternal::libisdsDbType2DbType(ifr->dbType, &iOk));
	if (Q_UNLIKELY(!iOk)) {
		goto fail;
	}
	fulltextResult.setDbName(fromCStr(ifr->name));
	fulltextResult.setDbAddress(fromCStr(ifr->address));
	fulltextResult.setDbBiDate(dateFromStructTM(ifr->biDate));
	fulltextResult.setIc(fromCStr(ifr->ic));
	fulltextResult.setDbEffectiveOVM(ifr->dbEffectiveOVM);
	fulltextResult.setActive(ifr->active);
	fulltextResult.setPublicSending(ifr->public_sending);
	fulltextResult.setCommercialSending(ifr->commercial_sending);

	/* Convert pointers to lists. */
	fulltextResult.setNameMatches(libisdsStartStop2startStop(ifr->name,
	    ifr->name_match_start, ifr->name_match_end, &iOk));
	if (Q_UNLIKELY(!iOk)) {
		goto fail;
	}
	fulltextResult.setAddressMatches(libisdsStartStop2startStop(ifr->address,
	    ifr->address_match_start, ifr->address_match_end, &iOk));
	if (Q_UNLIKELY(!iOk)) {
		goto fail;
	}

	if (ok != Q_NULLPTR) {
		*ok = true;
	}
	return fulltextResult;

fail:
	if (ok != Q_NULLPTR) {
		*ok = false;
	}
	return FulltextResult();
}

QList<Isds::FulltextResult> Isds::libisds2fulltextResultList(
    const struct isds_list *ifrl, bool *ok)
{
	/* Full-text result destructor function type. */
	typedef void (*ft_result_destr_func_t)(struct isds_fulltext_result **);

	QList<FulltextResult> resultList;

	while (ifrl != NULL) {
		const struct isds_fulltext_result *ifr = (struct isds_fulltext_result *)ifrl->data;
		ft_result_destr_func_t idestr = (ft_result_destr_func_t)ifrl->destructor;
		/* Destructor function must be set. */
		if (Q_UNLIKELY(!IsdsInternal::crudeEqual(idestr, isds_fulltext_result_free))) {
			Q_ASSERT(0);
			if (ok != Q_NULLPTR) {
				*ok = false;
			}
			return QList<FulltextResult>();
		}

		if (ifr != NULL) {
			bool iOk = false;
			resultList.append(libisds2fulltextResult(ifr, &iOk));
			if (Q_UNLIKELY(!iOk)) {
				if (ok != Q_NULLPTR) {
					*ok = false;
				}
				return QList<FulltextResult>();
			}
		}

		ifrl = ifrl->next;
	}

	if (ok != Q_NULLPTR) {
		*ok = true;
	}
	return resultList;
}

/*!
 * @brief Converts credit event types.
 */
static
enum Isds::Type::CreditEventType libisdsCreditEventType2CreditEventType(
    isds_credit_event_type icet, bool *ok = Q_NULLPTR)
{
	bool iOk = true;
	enum Isds::Type::CreditEventType cet = Isds::Type::CET_UNKNOWN;

	switch (icet) {
	case ISDS_CREDIT_CHARGED: cet = Isds::Type::CET_CHARGED; break;
	case ISDS_CREDIT_DISCHARGED: cet = Isds::Type::CET_DISCHARGED; break;
	case ISDS_CREDIT_MESSAGE_SENT: cet = Isds::Type::CET_MESSAGE_SENT; break;
	case ISDS_CREDIT_STORAGE_SET: cet = Isds::Type::CET_STORAGE_SET; break;
	case ISDS_CREDIT_EXPIRED: cet = Isds::Type::CET_EXPIRED; break;
	default:
		Q_ASSERT(0);
		iOk = false;
		break;
	}

	if (ok != Q_NULLPTR) {
		*ok = iOk;
	}
	return cet;
}

static
Isds::CreditEventCharged libisds2creditEventCharged(
    const struct isds_credit_event_charged *icec, bool *ok = Q_NULLPTR)
{
	if (Q_UNLIKELY(icec == Q_NULLPTR)) {
		if (ok != Q_NULLPTR) {
			*ok = true;
		}
		return Isds::CreditEventCharged();
	}

	Isds::CreditEventCharged creditEventCharged;

	creditEventCharged.setTransactID(Isds::fromCStr(icec->transaction));

	if (ok != Q_NULLPTR) {
		*ok = true;
	}
	return creditEventCharged;
}

static
Isds::CreditEventDischarged libisds2creditEventDischarged(
    const struct isds_credit_event_discharged *iced, bool *ok = Q_NULLPTR)
{
	if (Q_UNLIKELY(iced == Q_NULLPTR)) {
		if (ok != Q_NULLPTR) {
			*ok = true;
		}
		return Isds::CreditEventDischarged();
	}

	Isds::CreditEventDischarged creditEventDischarged;

	creditEventDischarged.setTransactID(Isds::fromCStr(iced->transaction));

	if (ok != Q_NULLPTR) {
		*ok = true;
	}
	return creditEventDischarged;
}

static
Isds::CreditEventMsgSent libisds2CreditEventMsgSent(
    const struct isds_credit_event_message_sent *icems, bool *ok = Q_NULLPTR)
{
	if (Q_UNLIKELY(icems == Q_NULLPTR)) {
		if (ok != Q_NULLPTR) {
			*ok = true;
		}
		return Isds::CreditEventMsgSent();
	}

	Isds::CreditEventMsgSent creditEventMsgSent;

	creditEventMsgSent.setDbIDRecipient(Isds::fromCStr(icems->recipient));
	creditEventMsgSent.setDmID(Isds::fromCStr(icems->message_id));

	if (ok != Q_NULLPTR) {
		*ok = true;
	}
	return creditEventMsgSent;
}

static
Isds::CreditEventStorageSet libisds2CreditEventStorageSet(
    const struct isds_credit_event_storage_set *icess, bool *ok = Q_NULLPTR)
{
	if (Q_UNLIKELY(icess == Q_NULLPTR)) {
		if (ok != Q_NULLPTR) {
			*ok = true;
		}
		return Isds::CreditEventStorageSet();
	}

	Isds::CreditEventStorageSet creditEventStorageSet;

	creditEventStorageSet.setNewCapacity(icess->new_capacity);
	creditEventStorageSet.setNewFrom(Isds::dateFromStructTM(icess->new_valid_from));
	creditEventStorageSet.setNewTo(Isds::dateFromStructTM(icess->new_valid_to));
	creditEventStorageSet.setOldCapacity(Isds::fromLongInt(icess->old_capacity));
	creditEventStorageSet.setOldFrom(Isds::dateFromStructTM(icess->old_valid_from));
	creditEventStorageSet.setOldTo(Isds::dateFromStructTM(icess->old_valid_to));
	creditEventStorageSet.setInitiator(Isds::fromCStr(icess->initiator));

	if (ok != Q_NULLPTR) {
		*ok = true;
	}
	return creditEventStorageSet;
}

Isds::CreditEvent Isds::libisds2creditEvent(const struct isds_credit_event *ice,
    bool *ok)
{
	if (Q_UNLIKELY(ice == Q_NULLPTR)) {
		if (ok != Q_NULLPTR) {
			*ok = true;
		}
		return CreditEvent();
	}

	bool iOk = false;
	CreditEvent creditEvent;

	creditEvent.setTime(dateTimeFromStructTimeval(ice->time));
	creditEvent.setCreditChange(ice->credit_change);
	creditEvent.setCreditAfter(ice->new_credit);
	enum Type::CreditEventType cet =
	    libisdsCreditEventType2CreditEventType(ice->type, &iOk);
	if (Q_UNLIKELY(!iOk)) {
		goto fail;
	}

	iOk = true;
	switch (cet) {
	case Type::CET_CHARGED:
		creditEvent.setCharged(
		    libisds2creditEventCharged(&ice->details.charged, &iOk));
		break;
	case Type::CET_DISCHARGED:
		creditEvent.setDischarged(
		    libisds2creditEventDischarged(&ice->details.discharged, &iOk));
		break;
	case Type::CET_MESSAGE_SENT:
		creditEvent.setMsgSent(
		    libisds2CreditEventMsgSent(&ice->details.message_sent, &iOk));
		break;
	case Type::CET_STORAGE_SET:
		creditEvent.setStorageSet(
		    libisds2CreditEventStorageSet(&ice->details.storage_set, &iOk));
		break;
	case Type::CET_EXPIRED:
		creditEvent.setExpired();
		break;
	default:
		Q_ASSERT(0);
		goto fail;
		break;
	}
	if (Q_UNLIKELY(!iOk)) {
		goto fail;
	}

	if (ok != Q_NULLPTR) {
		*ok = true;
	}
	return creditEvent;

fail:
	if (ok != Q_NULLPTR) {
		*ok = false;
	}
	return CreditEvent();
}

QList<Isds::CreditEvent> Isds::libisds2creditEventList(
    const struct isds_list *icel, bool *ok)
{
	/* Credit event destructor function type. */
	typedef void (*credit_event_destr_func_t)(struct isds_credit_event **);

	QList<CreditEvent> creditEventList;

	while (icel != NULL) {
		const struct isds_credit_event *ice = (struct isds_credit_event *)icel->data;
		credit_event_destr_func_t idestr = (credit_event_destr_func_t)icel->destructor;
		/* Destructor function must be set. */
		if (Q_UNLIKELY(!IsdsInternal::crudeEqual(idestr, isds_credit_event_free))) {
			Q_ASSERT(0);
			if (ok != Q_NULLPTR) {
				*ok = false;
			}
			return QList<CreditEvent>();
		}

		if (ice != NULL) {
			bool iOk = false;
			creditEventList.append(libisds2creditEvent(ice, &iOk));
			if (Q_UNLIKELY(!iOk)) {
				if (ok != Q_NULLPTR) {
					*ok = false;
				}
				return QList<CreditEvent>();
			}
		}

		icel = icel->next;
	}

	if (ok != Q_NULLPTR) {
		*ok = true;
	}
	return creditEventList;
}

Isds::CredentialsDelivery Isds::libisds2credentialsDelivery(
    const struct ::isds_credentials_delivery *icd, bool *ok)
{
	if (Q_UNLIKELY(icd == Q_NULLPTR)) {
		if (ok != Q_NULLPTR) {
			*ok = true;
		}
		return CredentialsDelivery();
	}

	CredentialsDelivery credentialsDelivery;

	credentialsDelivery.setEmail(Isds::fromCStr(icd->email));
	credentialsDelivery.setToken(Isds::fromCStr(icd->token));
	credentialsDelivery.setUsername(Isds::fromCStr(icd->new_user_name));

	if (ok != Q_NULLPTR) {
		*ok = true;
	}
	return credentialsDelivery;
}

/*!
 * @brief Set isds_credentials_delivery structure according to the
 *     CredentialsDelivery.
 */
static
bool setCredentialsDeliveryContent(struct isds_credentials_delivery *tgt,
    const Isds::CredentialsDelivery &src)
{
	if (Q_UNLIKELY(tgt == NULL)) {
		Q_ASSERT(0);
		return false;
	}

	if (Q_UNLIKELY(!Isds::toCStrCopy(&tgt->email, src.email()))) {
		return false;
	}
	if (Q_UNLIKELY(!Isds::toCStrCopy(&tgt->token, src.token()))) {
		return false;
	}
	if (Q_UNLIKELY(!Isds::toCStrCopy(&tgt->new_user_name, src.username()))) {
		return false;
	}
	return true;
}

struct ::isds_credentials_delivery *Isds::credentialsDelivery2libisds(
    const Isds::CredentialsDelivery &cd, bool *ok)
{
	if (Q_UNLIKELY(cd.isNull())) {
		if (ok != Q_NULLPTR) {
			*ok = true;
		}
		return NULL;
	}

	struct isds_credentials_delivery *icd =
	    (struct isds_credentials_delivery *)::std::malloc(sizeof(*icd));
	if (Q_UNLIKELY(icd == NULL)) {
		Q_ASSERT(0);
		if (ok != Q_NULLPTR) {
			*ok = false;
		}
		return NULL;
	}
	::std::memset(icd, 0, sizeof(*icd));

	if (Q_UNLIKELY(!setCredentialsDeliveryContent(icd, cd))) {
		isds_credentials_delivery_free(&icd);
		if (ok != Q_NULLPTR) {
			*ok = false;
		}
		return NULL;
	}
	if (ok != Q_NULLPTR) {
		*ok = true;
	}

	return icd;
}

/*!
 * @brief Converts PDZ payment types.
 */
static
enum Isds::Type::PdzPaymentType libisdsPaymentType2pdzPaymentType(
    isds_payment_type ipt, bool *ok = Q_NULLPTR)
{
	bool iOk = true;
	enum Isds::Type::PdzPaymentType pt = Isds::Type::PPT_UNKNOWN;

	switch (ipt) {
	case PAYMENT_SENDER: pt = Isds::Type::PPT_K; break;
	case PAYMENT_STAMP: pt = Isds::Type::PPT_E; break;
	case PAYMENT_SPONSOR: pt = Isds::Type::PPT_G; break;
	case PAYMENT_RESPONSE: pt = Isds::Type::PPT_O; break;
	case PAYMENT_SPONSOR_LIMITED: pt = Isds::Type::PPT_Z; break;
	case PAYMENT_SPONSOR_EXTERNAL: pt = Isds::Type::PPT_D; break;
	default:
		Q_ASSERT(0);
		iOk = false;
		break;
	}

	if (ok != Q_NULLPTR) {
		*ok = iOk;
	}
	return pt;
}

Isds::PDZInfoRec Isds::libisds2pdzInfoRec(
    const struct ::isds_commercial_permission *icp, bool *ok)
{
	if (Q_UNLIKELY(icp == Q_NULLPTR)) {
		if (ok != Q_NULLPTR) {
			*ok = true;
		}
		return PDZInfoRec();
	}

	bool iOk = true;
	PDZInfoRec pdzInfo;

	pdzInfo.setPdzType(libisdsPaymentType2pdzPaymentType(icp->type, &iOk));
	if (Q_UNLIKELY(!iOk)) {
		goto fail;
	}
	pdzInfo.setPdzRecip(Isds::fromCStr(icp->recipient));
	pdzInfo.setPdzPayer(Isds::fromCStr(icp->payer));
	pdzInfo.setPdzExpire(dateTimeFromStructTimeval(icp->expiration));
	pdzInfo.setPdzCnt(Isds::fromULongInt(icp->count));
	pdzInfo.setOdzIdent(Isds::fromCStr(icp->reply_identifier));

	if (ok != Q_NULLPTR) {
		*ok = true;
	}
	return pdzInfo;

fail:
	if (ok != Q_NULLPTR) {
		*ok = false;
	}
	return PDZInfoRec();
}

QList<Isds::PDZInfoRec> Isds::libisds2pdzInfoRecList(
    const struct ::isds_list *icpl, bool *ok)
{
	/* Commercial permission destructor function type. */
	typedef void (*commercial_permission_destr_func_t)
	    (struct isds_commercial_permission **);

	QList<PDZInfoRec> pdzInfoList;

	while (icpl != NULL) {
		const struct isds_commercial_permission *icp =
		    (struct isds_commercial_permission *)icpl->data;
		commercial_permission_destr_func_t icpstr =
		    (commercial_permission_destr_func_t)icpl->destructor;
		/* Destructor function must be set. */
		if (Q_UNLIKELY(!IsdsInternal::crudeEqual(
		        icpstr, isds_commercial_permission_free))) {
			Q_ASSERT(0);
			if (ok != Q_NULLPTR) {
				*ok = false;
			}
			return QList<PDZInfoRec>();
		}

		if (icp != NULL) {
			bool iOk = false;
			pdzInfoList.append(libisds2pdzInfoRec(icp, &iOk));
			if (Q_UNLIKELY(!iOk)) {
				if (ok != Q_NULLPTR) {
					*ok = false;
				}
				return QList<PDZInfoRec>();
			}
		}

		icpl = icpl->next;
	}

	if (ok != Q_NULLPTR) {
		*ok = true;
	}
	return pdzInfoList;
}

Isds::DTInfoOutput Isds::libisds2dtInfoOutput(
    const struct ::isds_DTInfoOutput *idt, bool *ok)
{
	if (Q_UNLIKELY(idt == Q_NULLPTR)) {
		if (ok != Q_NULLPTR) {
			*ok = true;
		}
		return DTInfoOutput();
	}

	bool iOk = false;
	DTInfoOutput dtInfo;

	dtInfo.setActDTType(
	   IsdsInternal::libisdsDTType2DTType(idt->actDTType, &iOk));
	if (Q_UNLIKELY(!iOk)) {
		goto fail;
	}
	dtInfo.setActDTCapacity(Isds::fromULongInt(idt->actDTCapacity));
	dtInfo.setActDTFrom(dateFromStructTM(idt->actDTFrom));
	dtInfo.setActDTTo(dateFromStructTM(idt->actDTTo));
	dtInfo.setActDTCapUsed(Isds::fromULongInt(idt->actDTCapUsed));

	dtInfo.setFutDTType(
	    IsdsInternal::libisdsDTType2DTType(idt->futDTType, &iOk));
	if (Q_UNLIKELY(!iOk)) {
		goto fail;
	}
	dtInfo.setFutDTCapacity(Isds::fromULongInt(idt->futDTCapacity));
	dtInfo.setFutDTFrom(dateFromStructTM(idt->futDTFrom));
	dtInfo.setFutDTTo(dateFromStructTM(idt->futDTTo));
	dtInfo.setFutDTPaid(
	    IsdsInternal::libisdsDTPaid2DTPaid(idt->futDTPaid, &iOk));
	if (Q_UNLIKELY(!iOk)) {
		goto fail;
	}

	if (ok != Q_NULLPTR) {
		*ok = true;
	}
	return dtInfo;

fail:
	if (ok != Q_NULLPTR) {
		*ok = false;
	}
	return DTInfoOutput();
}
