/*
 * 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/box_conversion2.h"
#include "src/isds/conversion_internal.h"
#include "src/isds/internal_type_conversion.h"

/*!
 * @brief Set AddressExt2 according to the isds_AddressExt2 structure.
 */
static
bool setAddressExt2Content(Isds::AddressExt2 &tgt,
    const struct isds_AddressExt2 *src)
{
	if (Q_UNLIKELY(src == NULL)) {
		return true;
	}

	tgt.setCode(Isds::fromCStr(src->adCode));
	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));
	return true;
}

Isds::AddressExt2 Isds::libisds2addressExt2(const struct isds_AddressExt2 *ia,
    bool *ok)
{
	AddressExt2 address;
	bool ret = setAddressExt2Content(address, ia);
	if (ok != Q_NULLPTR) {
		*ok = ret;
	}
	return address;
}

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

	if (Q_UNLIKELY(!Isds::toCStrCopy(&tgt->adCode, src.code()))) {
		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;
	}

	return true;
}

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

	struct isds_AddressExt2 *ia =
	    (struct isds_AddressExt2 *)::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(!setLibisdsAddressExt2Content(ia, a))) {
		isds_AddressExt2_free(&ia);
		if (ok != Q_NULLPTR) {
			*ok = false;
		}
		return NULL;
	}
	if (ok != Q_NULLPTR) {
		*ok = true;
	}
	return ia;
}

/*!
 * @brief Set PersonName2 according to the isds_PersonName2 structure.
 */
static
bool setPersonName2Content(Isds::PersonName2 &tgt,
    const struct isds_PersonName2 *src)
{
	if (Q_UNLIKELY(src == NULL)) {
		return true;
	}

	tgt.setGivenNames(Isds::fromCStr(src->pnGivenNames));
	tgt.setLastName(Isds::fromCStr(src->pnLastName));
	return true;
}

Isds::PersonName2 Isds::libisds2personName2(const struct isds_PersonName2 *ipn,
    bool *ok)
{
	PersonName2 personName;
	bool ret = setPersonName2Content(personName, ipn);
	if (ok != Q_NULLPTR) {
		*ok = ret;
	}
	return personName;
}

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

	if (Q_UNLIKELY(!Isds::toCStrCopy(&tgt->pnGivenNames,
	        src.givenNames()))) {
		return false;
	}
	if (Q_UNLIKELY(!Isds::toCStrCopy(&tgt->pnLastName, src.lastName()))) {
		return false;
	}

	return true;
}

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

	struct isds_PersonName2 *ipn =
	    (struct isds_PersonName2 *)::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(!setLibisdsPersonName2Content(ipn, pn))) {
		isds_PersonName2_free(&ipn);
		if (ok != Q_NULLPTR) {
			*ok = false;
		}
		return NULL;
	}
	if (ok != Q_NULLPTR) {
		*ok = true;
	}
	return ipn;
}

Isds::DbOwnerInfoExt2 Isds::libisds2dbOwnerInfoExt2(
    const struct isds_DbOwnerInfoExt2 *idoi, bool *ok)
{
	if (Q_UNLIKELY(idoi == Q_NULLPTR)) {
		if (ok != Q_NULLPTR) {
			*ok = true;
		}
		return DbOwnerInfoExt2();
	}

	bool iOk = false;
	DbOwnerInfoExt2 ownerInfo;

	ownerInfo.setDbID(Isds::fromCStr(idoi->dbID));
	ownerInfo.setAifoIsds(fromBoolPtr(idoi->aifoIsds));
	ownerInfo.setDbType(
	    IsdsInternal::libisdsDbTypePtr2DbType(idoi->dbType, &iOk));
	if (Q_UNLIKELY(!iOk)) {
		goto fail;
	}
	ownerInfo.setIc(Isds::fromCStr(idoi->ic));
	ownerInfo.setPersonName(libisds2personName2(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(libisds2addressExt2(idoi->address, &iOk));
	if (Q_UNLIKELY(!iOk)) {
		goto fail;
	}
	ownerInfo.setNationality(Isds::fromCStr(idoi->nationality));
	ownerInfo.setDbIdOVM(Isds::fromCStr(idoi->dbIdOVM));
	ownerInfo.setDbState(
	    IsdsInternal::longPtr2DbState(idoi->dbState, &iOk));
	if (Q_UNLIKELY(!iOk)) {
		goto fail;
	}
	ownerInfo.setDbOpenAddressing(fromBoolPtr(idoi->dbOpenAddressing));
	ownerInfo.setDbUpperID(Isds::fromCStr(idoi->dbUpperID));

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

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

/*!
 * @brief Set isds_DbOwnerInfoExt2 structure according to the DbOwnerInfoExt2.
 */
static
bool setLibisdsDbOwnerInfoExt2Content(struct isds_DbOwnerInfoExt2 *tgt,
    const Isds::DbOwnerInfoExt2 &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(!toBoolPtr(&tgt->aifoIsds, src.aifoIsds()))) {
		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::personName22libisds(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::addressExt22libisds(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->dbIdOVM, src.dbIdOVM()))) {
		return false;
	}
	if (Q_UNLIKELY(!IsdsInternal::dbState2longPtr(&tgt->dbState,
	        src.dbState()))) {
		return false;
	}
	if (Q_UNLIKELY(!toBoolPtr(&tgt->dbOpenAddressing, src.dbOpenAddressing()))) {
		return false;
	}
	if (Q_UNLIKELY(!Isds::toCStrCopy(&tgt->dbUpperID, src.dbUpperID()))) {
		return false;
	}

	return true;
}

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

	struct isds_DbOwnerInfoExt2 *idoi =
	    (struct isds_DbOwnerInfoExt2 *)::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(!setLibisdsDbOwnerInfoExt2Content(idoi, doi))) {
		isds_DbOwnerInfoExt2_free(&idoi);
		if (ok != Q_NULLPTR) {
			*ok = false;
		}
		return NULL;
	}
	if (ok != Q_NULLPTR) {
		*ok = true;
	}
	return idoi;
}

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

	QList<DbOwnerInfoExt2> ownerInfoList;

	while (ioil != NULL) {
		const struct isds_DbOwnerInfoExt2 *ioi =
		    (struct isds_DbOwnerInfoExt2 *)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_DbOwnerInfoExt2_free))) {
			Q_ASSERT(0);
			if (ok != Q_NULLPTR) {
				*ok = false;
			}
			return QList<DbOwnerInfoExt2>();
		}

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

		ioil = ioil->next;
	}

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

struct isds_list *Isds::dbOwnerInfoExt2List2libisds(
    const QList<DbOwnerInfoExt2> &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 DbOwnerInfoExt2 &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_DbOwnerInfoExt2 *ioi = dbOwnerInfoExt22libisds(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_DbOwnerInfoExt2_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::DbUserInfoExt2 Isds::libisds2dbUserInfoExt2(
    const struct isds_DbUserInfoExt2 *idui, bool *ok)
{
	if (Q_UNLIKELY(idui == Q_NULLPTR)) {
		if (ok != Q_NULLPTR) {
			*ok = true;
		}
		return DbUserInfoExt2();
	}

	bool iOk = false;
	DbUserInfoExt2 userInfo;

	userInfo.setAifoIsds(fromBoolPtr(idui->aifoIsds));
	userInfo.setPersonName(libisds2personName2(idui->personName, &iOk));
	if (Q_UNLIKELY(!iOk)) {
		goto fail;
	}
	userInfo.setAddress(libisds2addressExt2(idui->address, &iOk));
	if (Q_UNLIKELY(!iOk)) {
		goto fail;
	}
	userInfo.setBiDate(dateFromStructTM(idui->biDate));
	userInfo.setIsdsID(fromCStr(idui->isdsID));
	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 DbUserInfoExt2();
}

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

	bool iOk = false;

	if (Q_UNLIKELY(!toBoolPtr(&tgt->aifoIsds, src.aifoIsds()))) {
		return false;
	}
	tgt->personName = personName22libisds(src.personName(), &iOk);
	if (Q_UNLIKELY(!iOk)) {
		return false;
	}
	tgt->address = addressExt22libisds(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->isdsID, src.isdsID()))) {
		return false;
	}
	if (Q_UNLIKELY(!IsdsInternal::userType2libisdsUserType(&tgt->userType,
	        src.userType()))) {
		return false;
	}
	if (Q_UNLIKELY(!IsdsInternal::privileges2longPtr(&tgt->userPrivils,
	        src.userPrivils()))) {
		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_DbUserInfoExt2 *Isds::dbUserInfoExt22libisds(
    const DbUserInfoExt2 &dui, bool *ok)
{
	if (Q_UNLIKELY(dui.isNull())) {
		if (ok != Q_NULLPTR) {
			*ok = true;
		}
		return NULL;
	}

	struct isds_DbUserInfoExt2 *idui =
	    (struct isds_DbUserInfoExt2 *)::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(!setLibisdsDbUserInfoExt2Content(idui, dui))) {
		isds_DbUserInfoExt2_free(&idui);
		if (ok != Q_NULLPTR) {
			*ok = false;
		}
		return NULL;
	}
	if (ok != Q_NULLPTR) {
		*ok = true;
	}
	return idui;
}

QList<Isds::DbUserInfoExt2> Isds::libisds2dbUserInfoExt2List(
    const struct isds_list *iuil, bool *ok)
{
	/* User info destructor function type. */
	typedef void (*uwn_info_destr_func_t)(struct isds_DbUserInfoExt2 **);

	QList<DbUserInfoExt2> userInfoList;

	while (iuil != NULL) {
		const struct isds_DbUserInfoExt2 *iui =
		    (struct isds_DbUserInfoExt2 *)iuil->data;
		uwn_info_destr_func_t idestr =
		    (uwn_info_destr_func_t)iuil->destructor;
		/* Destructor function must be set. */
		if (Q_UNLIKELY(!IsdsInternal::crudeEqual(idestr,
		        isds_DbUserInfoExt2_free))) {
			Q_ASSERT(0);
			if (ok != Q_NULLPTR) {
				*ok = false;
			}
			return QList<DbUserInfoExt2>();
		}

		if (iui != NULL) {
			bool iOk = false;
			userInfoList.append(libisds2dbUserInfoExt2(iui, &iOk));
			if (Q_UNLIKELY(!iOk)) {
				if (ok != Q_NULLPTR) {
					*ok = false;
				}
				return QList<DbUserInfoExt2>();
			}
		}

		iuil = iuil->next;
	}

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

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

	struct isds_list *iuil = NULL;
	struct isds_list *lastItem = NULL;
	foreach (const DbUserInfoExt2 &ui, uil) {
		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_DbUserInfoExt2 *iui = dbUserInfoExt22libisds(ui, &iOk);
		if (Q_UNLIKELY(!iOk)) {
			::std::free(item); item = NULL;
			goto fail;
		}

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

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

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

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