/*
 * (C) Copyright 2013 ECMWF.
 *
 * This software is licensed under the terms of the Apache Licence Version 2.0
 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
 * In applying this licence, ECMWF does not waive the privileges and immunities
 * granted to it by virtue of its status as an intergovernmental organisation
 * nor does it submit to any jurisdiction.
 */

#pragma once

// This file contains preprocessor black magic. It contains macros that
// can return the number of arguments passed

//-----------------------------------------------------------------------------------------------------------
// Public

/// Returns the number of passed arguments
#define __ATLAS_IO_NARG(...)

/// Splice a and b together
#define __ATLAS_IO_SPLICE(a, b)

#define __ATLAS_IO_STRINGIFY(a) a

#define __ATLAS_IO_TYPE(Type, ...)
#define __ATLAS_IO_TYPE_SCOPE(Type, ...)

//-----------------------------------------------------------------------------------------------------------
// Details

// Undefine these, to be redefined further down.
#undef __ATLAS_IO_NARG
#undef __ATLAS_IO_SPLICE
#undef __ATLAS_IO_TYPE
#undef __ATLAS_IO_TYPE_SCOPE

#define __ATLAS_IO_REVERSE 5, 4, 3, 2, 1, 0
#define __ATLAS_IO_ARGN(_1, _2, _3, _4, _5, N, ...) N
#define __ATLAS_IO_NARG__(dummy, ...) __ATLAS_IO_ARGN(__VA_ARGS__)
#define __ATLAS_IO_NARG_(...) __ATLAS_IO_NARG__(dummy, ##__VA_ARGS__, __ATLAS_IO_REVERSE)
#define __ATLAS_IO_SPLICE(a, b) __ATLAS_IO_SPLICE_1(a, b)
#define __ATLAS_IO_SPLICE_1(a, b) __ATLAS_IO_SPLICE_2(a, b)
#define __ATLAS_IO_SPLICE_2(a, b) a##b

#define __ATLAS_IO_ARG16(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, ...) _15
#define __ATLAS_IO_HAS_COMMA(...) __ATLAS_IO_ARG16(__VA_ARGS__, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0)
#define __ATLAS_IO_TRIGGER_PARENTHESIS(...) ,
#define __ATLAS_IO_ISEMPTY(...)                                                                                       \
    __ATLAS_IO_ISEMPTY_(/* test if there is just one argument, eventually an empty  \
                      one */                                 \
                        __ATLAS_IO_HAS_COMMA(__VA_ARGS__), /* test if                  \
                                                      _TRIGGER_PARENTHESIS_    \
                                                      together with the        \
                                                      argument adds a comma */                              \
                        __ATLAS_IO_HAS_COMMA(                                                                         \
                            __ATLAS_IO_TRIGGER_PARENTHESIS __VA_ARGS__), /* test if the argument together with \
                                            a parenthesis adds a comma */      \
                        __ATLAS_IO_HAS_COMMA(__VA_ARGS__(/*empty*/)),    /* test if placing it between              \
                                       __ATLAS_IO_TRIGGER_PARENTHESIS and the     \
                                       parenthesis adds a comma */ \
                        __ATLAS_IO_HAS_COMMA(__ATLAS_IO_TRIGGER_PARENTHESIS __VA_ARGS__(/*empty*/)))

#define __ATLAS_IO_PASTE5(_0, _1, _2, _3, _4) _0##_1##_2##_3##_4
#define __ATLAS_IO_ISEMPTY_(_0, _1, _2, _3) \
    __ATLAS_IO_HAS_COMMA(__ATLAS_IO_PASTE5(__ATLAS_IO_IS_EMPTY_CASE_, _0, _1, _2, _3))
#define __ATLAS_IO_IS_EMPTY_CASE_0001 ,

#define __ATLAS_IO_NARG(...) __ATLAS_IO_SPLICE(__ATLAS_IO_CALL_NARG_, __ATLAS_IO_ISEMPTY(__VA_ARGS__))(__VA_ARGS__)
#define __ATLAS_IO_CALL_NARG_1(...) 0
#define __ATLAS_IO_CALL_NARG_0 __ATLAS_IO_NARG_

#define __ATLAS_IO_COMMA_ARGS(...) \
    __ATLAS_IO_SPLICE(__ATLAS_IO_COMMA_ARGS_, __ATLAS_IO_ISEMPTY(__VA_ARGS__))(__VA_ARGS__)
#define __ATLAS_IO_COMMA_ARGS_1(...)
#define __ATLAS_IO_COMMA_ARGS_0(...) , __VA_ARGS__

#define __ATLAS_IO_ARGS_OR_DUMMY(...)                                             \
    __ATLAS_IO_SPLICE(__ATLAS_IO_ARGS_OR_DUMMY_, __ATLAS_IO_ISEMPTY(__VA_ARGS__)) \
    (__VA_ARGS__)
#define __ATLAS_IO_ARGS_OR_DUMMY_0(...) __VA_ARGS__
#define __ATLAS_IO_ARGS_OR_DUMMY_1(...) 0

#define __ATLAS_IO_TYPE(Type, ...)                                       \
    __ATLAS_IO_SPLICE(__ATLAS_IO_TYPE_, __ATLAS_IO_ISEMPTY(__VA_ARGS__)) \
    (Type, __ATLAS_IO_ARGS_OR_DUMMY(__VA_ARGS__))
#define __ATLAS_IO_TYPE_1(Type, dummy) Type __ATLAS_IO_SPLICE(__variable_, __LINE__)
#define __ATLAS_IO_TYPE_0(Type, ...) Type __ATLAS_IO_SPLICE(__variable_, __LINE__)(__VA_ARGS__)

#define __ATLAS_IO_TYPE_SCOPE(Type, ...)                                       \
    __ATLAS_IO_SPLICE(__ATLAS_IO_TYPE_SCOPE_, __ATLAS_IO_ISEMPTY(__VA_ARGS__)) \
    (Type, __ATLAS_IO_ARGS_OR_DUMMY(__VA_ARGS__))
#define __ATLAS_IO_TYPE_SCOPE_1(Type, ...)                                                                 \
    for (bool __ATLAS_IO_SPLICE(__done_, __LINE__) = false; __ATLAS_IO_SPLICE(__done_, __LINE__) != true;) \
        for (Type __ATLAS_IO_SPLICE(__variable_, __LINE__); __ATLAS_IO_SPLICE(__done_, __LINE__) != true;  \
             __ATLAS_IO_SPLICE(__done_, __LINE__) = true)
#define __ATLAS_IO_TYPE_SCOPE_0(Type, ...)                                                                             \
    for (bool __ATLAS_IO_SPLICE(__done_, __LINE__) = false; __ATLAS_IO_SPLICE(__done_, __LINE__) != true;)             \
        for (Type __ATLAS_IO_SPLICE(__variable_, __LINE__)(__VA_ARGS__); __ATLAS_IO_SPLICE(__done_, __LINE__) != true; \
             __ATLAS_IO_SPLICE(__done_, __LINE__) = true)
