#ifndef CHANGE_T50_H
#define CHANGE_T50_H

/*
 * Change T5.0 C interface reconstructed from the provided vendor document and
 * verified against the 16 exports of the supplied 32-bit Change.dll.
 *
 * Confirm with the vendor before production use:
 *   1. CHANGE_CALL: default __cdecl; define CHANGE_USE_STDCALL if required.
 *   2. Structure packing: default 8; define CHANGE_PACK_4 if required.
 *   3. CapDBConnect string type: default ANSI; define CHANGE_DB_CONNECT_WIDE
 *      if the DLL was built with Unicode.
 *
 * The supplied DLL is PE32/x86. Do not build the caller as Win64.
 */

#include <stddef.h>
#include <stdint.h>

#if !defined(_WIN32) && !defined(CHANGE_ALLOW_NON_WINDOWS)
#error "Change.dll is a Windows-only library. Build this source on Windows."
#endif

#if defined(_WIN64) && !defined(CHANGE_ALLOW_UNSUPPORTED_ARCH)
#error "The supplied Change.dll is 32-bit. Build the caller for Win32/x86."
#endif

#if defined(CHANGE_PACK_4) && defined(CHANGE_PACK_8)
#error "Define only one of CHANGE_PACK_4 or CHANGE_PACK_8."
#endif

#if defined(_MSC_VER)
#define CHANGE_CDECL __cdecl
#define CHANGE_STDCALL __stdcall
#define CHANGE_DLLIMPORT __declspec(dllimport)
#elif defined(__GNUC__) && defined(_WIN32)
#define CHANGE_CDECL __attribute__((cdecl))
#define CHANGE_STDCALL __attribute__((stdcall))
#define CHANGE_DLLIMPORT __attribute__((dllimport))
#else
#define CHANGE_CDECL
#define CHANGE_STDCALL
#define CHANGE_DLLIMPORT
#endif

#if defined(CHANGE_USE_STDCALL)
#define CHANGE_CALL CHANGE_STDCALL
#else
#define CHANGE_CALL CHANGE_CDECL
#endif

#if defined(CHANGE_STATIC_IMPORT)
#define CHANGE_IMPORT CHANGE_DLLIMPORT
#else
#define CHANGE_IMPORT
#endif

#if defined(__cplusplus)
#define CHANGE_STATIC_ASSERT(condition, message) static_assert((condition), message)
#elif defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L)
#define CHANGE_STATIC_ASSERT(condition, message) _Static_assert((condition), message)
#else
#define CHANGE_JOIN_INNER(a, b) a##b
#define CHANGE_JOIN(a, b) CHANGE_JOIN_INNER(a, b)
#define CHANGE_STATIC_ASSERT(condition, message) \
    typedef char CHANGE_JOIN(change_static_assert_at_line_, __LINE__)[(condition) ? 1 : -1]
#endif

#ifdef __cplusplus
extern "C" {
#endif

typedef int32_t CHANGE_LONG;
typedef int64_t CHANGE_INT64;

#if defined(CHANGE_DB_CONNECT_WIDE)
typedef const wchar_t *CHANGE_SERVICE_URL;
#else
typedef const char *CHANGE_SERVICE_URL;
#endif

#if defined(CHANGE_PACK_4)
#pragma pack(push, 4)
#define CHANGE_EXPECTED_UPLOADINFO_SIZE 112u
#define CHANGE_EXPECTED_PSAMID_OFFSET 92u
#else
#pragma pack(push, 8)
#define CHANGE_EXPECTED_UPLOADINFO_SIZE 120u
#define CHANGE_EXPECTED_PSAMID_OFFSET 96u
#endif

typedef struct tagCustomerInfo {
    CHANGE_LONG CardClass;      /* 4=M1, 8=CPU */
    CHANGE_LONG CustomerID;     /* account number */
    CHANGE_LONG CardNO;
    CHANGE_LONG CardSN;
    CHANGE_LONG Status;         /* F1H=normal, F3H=lost */
    CHANGE_LONG SubType;
    CHANGE_LONG Ze;             /* total amount, cents */
    CHANGE_LONG Ye;             /* main-wallet balance, cents */
    CHANGE_LONG OpCount;        /* main-wallet operation counter */
    CHANGE_LONG SubYe;          /* subsidy-wallet balance, cents */
    CHANGE_LONG SubCount;       /* subsidy-wallet operation counter */
    char CardASN[24];
} CustomerInfo;

typedef struct tagUpLoadInfo {
    CHANGE_LONG termID;
    CHANGE_LONG custID;
    CHANGE_LONG cardNO;
    CHANGE_LONG cardSn;
    CHANGE_LONG cardClass;      /* 4=M1, 8=CPU */
    char cardASN[24];
    CHANGE_LONG sumFare;        /* total amount, cents */
    CHANGE_LONG mngFare;        /* management fee, cents */
    CHANGE_LONG objNo;          /* 0=main wallet, 1=subsidy wallet */
    CHANGE_LONG opFare;         /* debit amount, cents */
    CHANGE_LONG oddFare;        /* remaining balance, cents */
    CHANGE_LONG opCount;        /* counter after card write */
    char jyDT[24];              /* YYYY-MM-DD HH:MM:SS */
    CHANGE_INT64 psamID;
    CHANGE_LONG psamJyNo;
    CHANGE_LONG tac;
    CHANGE_LONG rtn;            /* WebService business result */
} UPLOADINFO;

typedef struct tagDownInfo {
    char bm[200];
    char xh[60];
    char name[40];
    CHANGE_LONG sex;            /* 1=male, 0=female */
    CHANGE_LONG customerid;
    CHANGE_LONG cardno;
    CHANGE_LONG status;
    CHANGE_LONG cardsn;
} DOWNINFO;

typedef struct tagDownDepInfo {
    char DPCODE1[6];
    char DPCODE2[6];
    char DPCODE3[8];
    char DPCODE4[8];
    char DPNAME1[60];
    char DPNAME2[60];
    char DPNAME3[60];
    char DPNAME4[60];
} DOWNDEPINFO;

#pragma pack(pop)

CHANGE_STATIC_ASSERT(sizeof(CHANGE_LONG) == 4u, "CHANGE_LONG must be 4 bytes");
CHANGE_STATIC_ASSERT(sizeof(CHANGE_INT64) == 8u, "CHANGE_INT64 must be 8 bytes");
CHANGE_STATIC_ASSERT(sizeof(CustomerInfo) == 68u, "Unexpected CustomerInfo layout");
CHANGE_STATIC_ASSERT(sizeof(UPLOADINFO) == CHANGE_EXPECTED_UPLOADINFO_SIZE,
                     "Unexpected UPLOADINFO layout; check CHANGE_PACK_4");
CHANGE_STATIC_ASSERT(offsetof(UPLOADINFO, psamID) == CHANGE_EXPECTED_PSAMID_OFFSET,
                     "Unexpected UPLOADINFO.psamID offset; check structure packing");
CHANGE_STATIC_ASSERT(sizeof(DOWNINFO) == 320u, "Unexpected DOWNINFO layout");
CHANGE_STATIC_ASSERT(sizeof(DOWNDEPINFO) == 268u, "Unexpected DOWNDEPINFO layout");

#if !defined(CHANGE_ALLOW_UNSUPPORTED_ARCH)
CHANGE_STATIC_ASSERT(sizeof(void *) == 4u, "Change.dll requires a 32-bit process");
#endif

/* Direct-import declarations. Dynamic loading below does not require Change.lib. */
CHANGE_IMPORT CHANGE_LONG CHANGE_CALL OpenCom(void);
CHANGE_IMPORT void CHANGE_CALL CloseCom(void);
CHANGE_IMPORT CHANGE_LONG CHANGE_CALL CapNBQueryCard(CHANGE_LONG *uid);
CHANGE_IMPORT CHANGE_LONG CHANGE_CALL CapGetNBCardInfo(CustomerInfo *customer);
CHANGE_IMPORT CHANGE_LONG CHANGE_CALL CapSetNBCardInfo(
    CHANGE_LONG customer_id,
    CHANGE_LONG amount_cents,
    CHANGE_LONG wallet_no,
    char *trade_time,
    CHANGE_INT64 *psam_id,
    CHANGE_LONG *psam_trade_no,
    CHANGE_LONG *tac);
CHANGE_IMPORT CHANGE_LONG CHANGE_CALL CapUpload(UPLOADINFO *upload_info, char *message);
CHANGE_IMPORT CHANGE_LONG CHANGE_CALL CapDBConnect(CHANGE_SERVICE_URL service_url);
CHANGE_IMPORT void CHANGE_CALL CapDBDiscon(void);
CHANGE_IMPORT CHANGE_LONG CHANGE_CALL CapGetCustomerList(DOWNINFO *customers);
CHANGE_IMPORT CHANGE_LONG CHANGE_CALL CapGetCustomerCount(void);
CHANGE_IMPORT CHANGE_LONG CHANGE_CALL CapGetDepList(DOWNDEPINFO *departments);
CHANGE_IMPORT CHANGE_LONG CHANGE_CALL CapGetDepCount(void);
CHANGE_IMPORT CHANGE_LONG CHANGE_CALL CapDownBlacklist(CHANGE_LONG *card_numbers);
CHANGE_IMPORT CHANGE_LONG CHANGE_CALL CapDownBlacklistCount(void);
CHANGE_IMPORT CHANGE_LONG CHANGE_CALL CapDownCustomerInfo(
    CHANGE_LONG customer_id, DOWNINFO *customer);
CHANGE_IMPORT CHANGE_LONG CHANGE_CALL CapGetCustomerByNum(
    CHANGE_LONG first_customer_id, CHANGE_LONG count, DOWNINFO *customers);

typedef CHANGE_LONG(CHANGE_CALL *CHANGE_PFN_OpenCom)(void);
typedef void(CHANGE_CALL *CHANGE_PFN_CloseCom)(void);
typedef CHANGE_LONG(CHANGE_CALL *CHANGE_PFN_CapNBQueryCard)(CHANGE_LONG *uid);
typedef CHANGE_LONG(CHANGE_CALL *CHANGE_PFN_CapGetNBCardInfo)(CustomerInfo *customer);
typedef CHANGE_LONG(CHANGE_CALL *CHANGE_PFN_CapSetNBCardInfo)(
    CHANGE_LONG customer_id,
    CHANGE_LONG amount_cents,
    CHANGE_LONG wallet_no,
    char *trade_time,
    CHANGE_INT64 *psam_id,
    CHANGE_LONG *psam_trade_no,
    CHANGE_LONG *tac);
typedef CHANGE_LONG(CHANGE_CALL *CHANGE_PFN_CapUpload)(UPLOADINFO *upload_info, char *message);
typedef CHANGE_LONG(CHANGE_CALL *CHANGE_PFN_CapDBConnect)(CHANGE_SERVICE_URL service_url);
typedef void(CHANGE_CALL *CHANGE_PFN_CapDBDiscon)(void);
typedef CHANGE_LONG(CHANGE_CALL *CHANGE_PFN_CapGetCustomerList)(DOWNINFO *customers);
typedef CHANGE_LONG(CHANGE_CALL *CHANGE_PFN_CapGetCustomerCount)(void);
typedef CHANGE_LONG(CHANGE_CALL *CHANGE_PFN_CapGetDepList)(DOWNDEPINFO *departments);
typedef CHANGE_LONG(CHANGE_CALL *CHANGE_PFN_CapGetDepCount)(void);
typedef CHANGE_LONG(CHANGE_CALL *CHANGE_PFN_CapDownBlacklist)(CHANGE_LONG *card_numbers);
typedef CHANGE_LONG(CHANGE_CALL *CHANGE_PFN_CapDownBlacklistCount)(void);
typedef CHANGE_LONG(CHANGE_CALL *CHANGE_PFN_CapDownCustomerInfo)(
    CHANGE_LONG customer_id, DOWNINFO *customer);
typedef CHANGE_LONG(CHANGE_CALL *CHANGE_PFN_CapGetCustomerByNum)(
    CHANGE_LONG first_customer_id, CHANGE_LONG count, DOWNINFO *customers);

typedef struct ChangeApi {
    void *module;
    uint32_t last_system_error;
    CHANGE_PFN_OpenCom OpenCom;
    CHANGE_PFN_CloseCom CloseCom;
    CHANGE_PFN_CapNBQueryCard CapNBQueryCard;
    CHANGE_PFN_CapGetNBCardInfo CapGetNBCardInfo;
    CHANGE_PFN_CapSetNBCardInfo CapSetNBCardInfo;
    CHANGE_PFN_CapUpload CapUpload;
    CHANGE_PFN_CapDBConnect CapDBConnect;
    CHANGE_PFN_CapDBDiscon CapDBDiscon;
    CHANGE_PFN_CapGetCustomerList CapGetCustomerList;
    CHANGE_PFN_CapGetCustomerCount CapGetCustomerCount;
    CHANGE_PFN_CapGetDepList CapGetDepList;
    CHANGE_PFN_CapGetDepCount CapGetDepCount;
    CHANGE_PFN_CapDownBlacklist CapDownBlacklist;
    CHANGE_PFN_CapDownBlacklistCount CapDownBlacklistCount;
    CHANGE_PFN_CapDownCustomerInfo CapDownCustomerInfo;
    CHANGE_PFN_CapGetCustomerByNum CapGetCustomerByNum;
} ChangeApi;

enum ChangeLoaderStatus {
    CHANGE_LOADER_OK = 0,
    CHANGE_LOADER_INVALID_ARGUMENT = -1,
    CHANGE_LOADER_ABI_MISMATCH = -2,
    CHANGE_LOADER_DLL_NOT_FOUND = -3,
    CHANGE_LOADER_EXPORT_NOT_FOUND = -4
};

int Change_ValidateAbi(void);
int Change_Load(ChangeApi *api, const char *dll_path);
void Change_Unload(ChangeApi *api);
int Change_IsLoaded(const ChangeApi *api);
const char *Change_LoaderStatusText(int status);
size_t Change_FormatSystemError(uint32_t error_code, char *buffer, size_t buffer_size);

#ifdef __cplusplus
}
#endif

#endif /* CHANGE_T50_H */
