#ifndef GRID_MACRO_MAGIC_H
#define GRID_MACRO_MAGIC_H

///////////////////////////////////////////
// Strong credit to :
//
// http://jhnet.co.uk/articles/cpp_magic
//
// 
// "The C Pre-Processor (CPP) is the somewhat basic macro system used by the C 
// programming language to implement features such as #include and #define 
// which allow very simple text-substitutions to be carried out at compile time.
// In this article we abuse the humble #define to implement if-statements and iteration.
//
// Before we begin, a disclaimer: these tricks, while perfectly valid C, should not be 
// considered good development practice and should almost certainly not be used for "real work".
// That said it can totally be used for fun home-automation projects...
//
// https://github.com/18sg/uSHET/blob/master/lib/cpp_magic.h
//
// The cpp_magic.h license (prior to my modifications):
/*
Copyright (c) 2014 Thomas Nixon, Jonathan Heathcote

  Permission is hereby granted, free of charge, to any person obtaining a copy
  of this software and associated documentation files (the "Software"), to deal
  in the Software without restriction, including without limitation the rights
  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  copies of the Software, and to permit persons to whom the Software is
  furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
///////////////////////////////////////////

#ifndef MAX
#define MAX(x,y) ((x)>(y)?(x):(y))
#define MIN(x,y) ((x)>(y)?(y):(x))
#endif

#define GRID_MACRO_FIRST(a, ...) a
#define GRID_MACRO_SECOND(a, b, ...) b

#define GRID_MACRO_EMPTY()

#define GRID_MACRO_EVAL(...)     GRID_MACRO_EVAL64(__VA_ARGS__)
#define GRID_MACRO_EVAL1024(...) GRID_MACRO_EVAL512(GRID_MACRO_EVAL512(__VA_ARGS__))
#define GRID_MACRO_EVAL512(...)  GRID_MACRO_EVAL256(GRID_MACRO_EVAL256(__VA_ARGS__))
#define GRID_MACRO_EVAL256(...)  GRID_MACRO_EVAL128(GRID_MACRO_EVAL128(__VA_ARGS__))
#define GRID_MACRO_EVAL128(...)  GRID_MACRO_EVAL64(GRID_MACRO_EVAL64(__VA_ARGS__))
#define GRID_MACRO_EVAL64(...)   GRID_MACRO_EVAL32(GRID_MACRO_EVAL32(__VA_ARGS__))
#define GRID_MACRO_EVAL32(...)   GRID_MACRO_EVAL16(GRID_MACRO_EVAL16(__VA_ARGS__))
#define GRID_MACRO_EVAL16(...)   GRID_MACRO_EVAL8(GRID_MACRO_EVAL8(__VA_ARGS__))
#define GRID_MACRO_EVAL8(...)    GRID_MACRO_EVAL4(GRID_MACRO_EVAL4(__VA_ARGS__))
#define GRID_MACRO_EVAL4(...)    GRID_MACRO_EVAL2(GRID_MACRO_EVAL2(__VA_ARGS__))
#define GRID_MACRO_EVAL2(...)    GRID_MACRO_EVAL1(GRID_MACRO_EVAL1(__VA_ARGS__))
#define GRID_MACRO_EVAL1(...) __VA_ARGS__

#define GRID_MACRO_DEFER1(m) m GRID_MACRO_EMPTY()
#define GRID_MACRO_DEFER2(m) m GRID_MACRO_EMPTY GRID_MACRO_EMPTY()()
#define GRID_MACRO_DEFER3(m) m GRID_MACRO_EMPTY GRID_MACRO_EMPTY GRID_MACRO_EMPTY()()()
#define GRID_MACRO_DEFER4(m) m GRID_MACRO_EMPTY GRID_MACRO_EMPTY GRID_MACRO_EMPTY GRID_MACRO_EMPTY()()()()

#define GRID_MACRO_IS_PROBE(...) GRID_MACRO_SECOND(__VA_ARGS__, 0)
#define GRID_MACRO_PROBE() ~, 1

#define GRID_MACRO_CAT(a,b) a ## b

#define GRID_MACRO_NOT(x) GRID_MACRO_IS_PROBE(GRID_MACRO_CAT(_GRID_MACRO_NOT_, x))
#define _GRID_MACRO_NOT_0 GRID_MACRO_PROBE()

#define GRID_MACRO_BOOL(x) GRID_MACRO_NOT(GRID_MACRO_NOT(x))

#define GRID_MACRO_IF_ELSE(condition) _GRID_MACRO_IF_ELSE(GRID_MACRO_BOOL(condition))
#define _GRID_MACRO_IF_ELSE(condition) GRID_MACRO_CAT(_GRID_MACRO_IF_, condition)

#define _GRID_MACRO_IF_1(...) __VA_ARGS__ _GRID_MACRO_IF_1_ELSE
#define _GRID_MACRO_IF_0(...)             _GRID_MACRO_IF_0_ELSE

#define _GRID_MACRO_IF_1_ELSE(...)
#define _GRID_MACRO_IF_0_ELSE(...) __VA_ARGS__

#define GRID_MACRO_HAS_ARGS(...) GRID_MACRO_BOOL(GRID_MACRO_FIRST(_GRID_MACRO_END_OF_ARGUMENTS_ __VA_ARGS__)())
#define _GRID_MACRO_END_OF_ARGUMENTS_() 0

#define GRID_MACRO_MAP(m, first, second, ...)   \
  m(first,second)                           \
  GRID_MACRO_IF_ELSE(GRID_MACRO_HAS_ARGS(__VA_ARGS__))(				       \
				 GRID_MACRO_DEFER4(_GRID_MACRO_MAP)()(m, __VA_ARGS__)   \
				     )(                                 \
				       /* Do nothing, just terminate */ \
									)

#define _GRID_MACRO_MAP() GRID_MACRO_MAP

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// These are the Grid extensions to serialisation (beyond popping first AND second)
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#define GRID_MACRO_MEMBER(A,B)        A B;

#define GRID_MACRO_COMP_MEMBER(A,B) result = (result and CompareMember(lhs. B, rhs. B));
#define GRID_MACRO_OS_WRITE_MEMBER(A,B) os<< #A <<" " #B << " = "; WriteMember( os, obj. B ); os << " ; " <<std::endl;
#define GRID_MACRO_READ_MEMBER(A,B) ::Grid::read(RD,#B,obj. B);
#define GRID_MACRO_WRITE_MEMBER(A,B) ::Grid::write(WR,#B,obj. B);

#define GRID_SERIALIZABLE_CLASS_MEMBERS(cname,...)\
static inline std::string SerialisableClassName(void) {return std::string(#cname);}	\
static constexpr bool isEnum = false; \
GRID_MACRO_EVAL(GRID_MACRO_MAP(GRID_MACRO_MEMBER,__VA_ARGS__))\
template <typename T>\
static inline void write(::Grid::Writer<T> &WR,const std::string &s, const cname &obj){ \
  push(WR,s);\
  GRID_MACRO_EVAL(GRID_MACRO_MAP(GRID_MACRO_WRITE_MEMBER,__VA_ARGS__))	\
  pop(WR);\
}\
template <typename T>\
static inline void read(::Grid::Reader<T> &RD,const std::string &s, cname &obj){	\
  if (!push(RD,s))\
  {\
    std::cout << ::Grid::GridLogWarning << "IO: Cannot open node '" << s << "'" << std::endl; \
    return;\
  };\
  GRID_MACRO_EVAL(GRID_MACRO_MAP(GRID_MACRO_READ_MEMBER,__VA_ARGS__))	\
  pop(RD);\
}\
friend inline std::ostream & operator << (std::ostream &os, const cname &obj ) { \
  os<<"class "<<#cname<<" {"<<std::endl;\
  GRID_MACRO_EVAL(GRID_MACRO_MAP(GRID_MACRO_OS_WRITE_MEMBER,__VA_ARGS__))	\
    os<<"}";								\
  return os;\
}\
friend inline bool operator==(const cname &lhs, const cname &rhs) {\
  bool result = true;\
  GRID_MACRO_EVAL(GRID_MACRO_MAP(GRID_MACRO_COMP_MEMBER,__VA_ARGS__))\
  return result;\
}

#define GRID_ENUM_TYPE(obj) std::remove_reference<decltype(obj)>::type
#define GRID_MACRO_ENUMVAL(A,B) A = B,
#define GRID_MACRO_ENUMCASE(A,B) case GRID_ENUM_TYPE(obj)::A: ::Grid::write(WR,s,#A); break;
#define GRID_MACRO_ENUMTEST(A,B) else if (buf == #A) {obj = GRID_ENUM_TYPE(obj)::A;}
#define GRID_MACRO_ENUMCASEIO(A,B) case GRID_ENUM_TYPE(obj)::A: os << #A; break;

#define GRID_SERIALIZABLE_ENUM(name,undefname,...)\
class name: public ::Grid::Serializable	  \
{\
public:\
  enum\
  {\
    GRID_MACRO_EVAL(GRID_MACRO_MAP(GRID_MACRO_ENUMVAL,__VA_ARGS__))\
    undefname = -1\
  };\
public:\
  accelerator name(void)     : value_(undefname) {};		\
  accelerator name(int value): value_(value) {};			\
  static inline std::string SerialisableClassName(void) {return std::string(#name);}\
  static constexpr bool isEnum = true; \
  template <typename T>\
  static inline void write(::Grid::Writer<T> &WR,const std::string &s, const name &obj) \
  {\
    switch (obj.value_)\
    {\
      GRID_MACRO_EVAL(GRID_MACRO_MAP(GRID_MACRO_ENUMCASE,__VA_ARGS__))\
      default: ::Grid::write(WR,s,#undefname); break;\
    }\
  }\
  \
  template <typename T>\
  static inline void read(::Grid::Reader<T> &RD,const std::string &s, name &obj)\
  {\
    std::string buf;\
    ::Grid::read(RD, s, buf);\
    if (buf == #undefname) {obj = name::undefname;}\
    GRID_MACRO_EVAL(GRID_MACRO_MAP(GRID_MACRO_ENUMTEST,__VA_ARGS__))\
    else {obj = name::undefname;}\
  }\
  accelerator_inline operator int(void) const\
  {\
    return value_;\
  }\
  inline friend std::ostream & operator<<(std::ostream &os, const name &obj)\
  {\
    switch (obj) {\
      GRID_MACRO_EVAL(GRID_MACRO_MAP(GRID_MACRO_ENUMCASEIO,__VA_ARGS__))\
      default: os << #undefname; break;\
    }\
    return os;\
  }\
  inline friend std::istream & operator>>(std::istream &is, name &obj)\
  {\
    std::string buf;\
    is >> buf;\
    if (buf == #undefname) {obj = name::undefname;}\
    GRID_MACRO_EVAL(GRID_MACRO_MAP(GRID_MACRO_ENUMTEST,__VA_ARGS__))\
    else {obj = name::undefname;}\
    return is;\
  }\
private:\
  int value_;\
};


#endif
