/*
 * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
 *  DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 *  This code is free software; you can redistribute it and/or modify it
 *  under the terms of the GNU General Public License version 2 only, as
 *  published by the Free Software Foundation.
 *
 *  This code 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
 *  version 2 for more details (a copy is included in the LICENSE file that
 *  accompanied this code).
 *
 *  You should have received a copy of the GNU General Public License version
 *  2 along with this work; if not, write to the Free Software Foundation,
 *  Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 *   Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 *  or visit www.oracle.com if you need additional information or have any
 *  questions.
 *
 */

#include <stdarg.h>
#include <stdlib.h>

#include "shared.h"

typedef void (*writeback_t)(int,void*);

typedef struct {
    writeback_t writeback;
    int* argids;
} call_info;

#define CASE(num, type) case num: { \
  type x = va_arg(a_list, type); \
  writeback(i, &x); \
} break;

enum NativeType {
    T_INT,
    T_DOUBLE,
    T_POINTER,
    T_S_I,
    T_S_F,
    T_S_D,
    T_S_P,
    T_S_II,
    T_S_IF,
    T_S_ID,
    T_S_IP,
    T_S_FI,
    T_S_FF,
    T_S_FD,
    T_S_FP,
    T_S_DI,
    T_S_DF,
    T_S_DD,
    T_S_DP,
    T_S_PI,
    T_S_PF,
    T_S_PD,
    T_S_PP,
    T_S_III,
    T_S_IIF,
    T_S_IID,
    T_S_IIP,
    T_S_IFI,
    T_S_IFF,
    T_S_IFD,
    T_S_IFP,
    T_S_IDI,
    T_S_IDF,
    T_S_IDD,
    T_S_IDP,
    T_S_IPI,
    T_S_IPF,
    T_S_IPD,
    T_S_IPP,
    T_S_FII,
    T_S_FIF,
    T_S_FID,
    T_S_FIP,
    T_S_FFI,
    T_S_FFF,
    T_S_FFD,
    T_S_FFP,
    T_S_FDI,
    T_S_FDF,
    T_S_FDD,
    T_S_FDP,
    T_S_FPI,
    T_S_FPF,
    T_S_FPD,
    T_S_FPP,
    T_S_DII,
    T_S_DIF,
    T_S_DID,
    T_S_DIP,
    T_S_DFI,
    T_S_DFF,
    T_S_DFD,
    T_S_DFP,
    T_S_DDI,
    T_S_DDF,
    T_S_DDD,
    T_S_DDP,
    T_S_DPI,
    T_S_DPF,
    T_S_DPD,
    T_S_DPP,
    T_S_PII,
    T_S_PIF,
    T_S_PID,
    T_S_PIP,
    T_S_PFI,
    T_S_PFF,
    T_S_PFD,
    T_S_PFP,
    T_S_PDI,
    T_S_PDF,
    T_S_PDD,
    T_S_PDP,
    T_S_PPI,
    T_S_PPF,
    T_S_PPD,
    T_S_PPP,
    T_S_FFFF,
};

// need to pass `num` separately as last argument preceding varargs according to spec (and for MSVC)
EXPORT void varargs(call_info* info, int num, ...) {
    va_list a_list;
    va_start(a_list, num);
    writeback_t writeback = info->writeback;

    for (int i = 0; i < num; i++) {
        int id = info->argids[i];
        switch (id) {
            CASE(T_INT, int)
            CASE(T_DOUBLE, double)
            CASE(T_POINTER, void*)
            CASE(T_S_I,   struct S_I)
            CASE(T_S_F,   struct S_F)
            CASE(T_S_D,   struct S_D)
            CASE(T_S_P,   struct S_P)
            CASE(T_S_II,  struct S_II)
            CASE(T_S_IF,  struct S_IF)
            CASE(T_S_ID,  struct S_ID)
            CASE(T_S_IP,  struct S_IP)
            CASE(T_S_FI,  struct S_FI)
            CASE(T_S_FF,  struct S_FF)
            CASE(T_S_FD,  struct S_FD)
            CASE(T_S_FP,  struct S_FP)
            CASE(T_S_DI,  struct S_DI)
            CASE(T_S_DF,  struct S_DF)
            CASE(T_S_DD,  struct S_DD)
            CASE(T_S_DP,  struct S_DP)
            CASE(T_S_PI,  struct S_PI)
            CASE(T_S_PF,  struct S_PF)
            CASE(T_S_PD,  struct S_PD)
            CASE(T_S_PP,  struct S_PP)
            CASE(T_S_III, struct S_III)
            CASE(T_S_IIF, struct S_IIF)
            CASE(T_S_IID, struct S_IID)
            CASE(T_S_IIP, struct S_IIP)
            CASE(T_S_IFI, struct S_IFI)
            CASE(T_S_IFF, struct S_IFF)
            CASE(T_S_IFD, struct S_IFD)
            CASE(T_S_IFP, struct S_IFP)
            CASE(T_S_IDI, struct S_IDI)
            CASE(T_S_IDF, struct S_IDF)
            CASE(T_S_IDD, struct S_IDD)
            CASE(T_S_IDP, struct S_IDP)
            CASE(T_S_IPI, struct S_IPI)
            CASE(T_S_IPF, struct S_IPF)
            CASE(T_S_IPD, struct S_IPD)
            CASE(T_S_IPP, struct S_IPP)
            CASE(T_S_FII, struct S_FII)
            CASE(T_S_FIF, struct S_FIF)
            CASE(T_S_FID, struct S_FID)
            CASE(T_S_FIP, struct S_FIP)
            CASE(T_S_FFI, struct S_FFI)
            CASE(T_S_FFF, struct S_FFF)
            CASE(T_S_FFD, struct S_FFD)
            CASE(T_S_FFP, struct S_FFP)
            CASE(T_S_FDI, struct S_FDI)
            CASE(T_S_FDF, struct S_FDF)
            CASE(T_S_FDD, struct S_FDD)
            CASE(T_S_FDP, struct S_FDP)
            CASE(T_S_FPI, struct S_FPI)
            CASE(T_S_FPF, struct S_FPF)
            CASE(T_S_FPD, struct S_FPD)
            CASE(T_S_FPP, struct S_FPP)
            CASE(T_S_DII, struct S_DII)
            CASE(T_S_DIF, struct S_DIF)
            CASE(T_S_DID, struct S_DID)
            CASE(T_S_DIP, struct S_DIP)
            CASE(T_S_DFI, struct S_DFI)
            CASE(T_S_DFF, struct S_DFF)
            CASE(T_S_DFD, struct S_DFD)
            CASE(T_S_DFP, struct S_DFP)
            CASE(T_S_DDI, struct S_DDI)
            CASE(T_S_DDF, struct S_DDF)
            CASE(T_S_DDD, struct S_DDD)
            CASE(T_S_DDP, struct S_DDP)
            CASE(T_S_DPI, struct S_DPI)
            CASE(T_S_DPF, struct S_DPF)
            CASE(T_S_DPD, struct S_DPD)
            CASE(T_S_DPP, struct S_DPP)
            CASE(T_S_PII, struct S_PII)
            CASE(T_S_PIF, struct S_PIF)
            CASE(T_S_PID, struct S_PID)
            CASE(T_S_PIP, struct S_PIP)
            CASE(T_S_PFI, struct S_PFI)
            CASE(T_S_PFF, struct S_PFF)
            CASE(T_S_PFD, struct S_PFD)
            CASE(T_S_PFP, struct S_PFP)
            CASE(T_S_PDI, struct S_PDI)
            CASE(T_S_PDF, struct S_PDF)
            CASE(T_S_PDD, struct S_PDD)
            CASE(T_S_PDP, struct S_PDP)
            CASE(T_S_PPI, struct S_PPI)
            CASE(T_S_PPF, struct S_PPF)
            CASE(T_S_PPD, struct S_PPD)
            CASE(T_S_PPP, struct S_PPP)
            CASE(T_S_FFFF, struct S_FFFF)
            default: exit(-1); // invalid id
        }
    }

    va_end(a_list);
}