proc constType(t: Snippet): Snippet =
  # needs manipulation of `t` in nifc
  "NIM_CONST " & t

proc constPtrType(t: Snippet): Snippet =
  t & "* NIM_CONST"

proc ptrConstType(t: Snippet): Snippet =
  "NIM_CONST " & t & "*"

proc ptrType(t: Snippet): Snippet =
  t & "*"

proc cppRefType(t: Snippet): Snippet =
  t & "&"

const
  CallingConvToStr: array[TCallingConvention, string] = ["N_NIMCALL",
    "N_STDCALL", "N_CDECL", "N_SAFECALL",
    "N_SYSCALL", # this is probably not correct for all platforms,
                 # but one can #define it to what one wants
    "N_INLINE", "N_NOINLINE", "N_FASTCALL", "N_THISCALL", "N_CLOSURE", "N_NOCONV",
    "N_NOCONV" #ccMember is N_NOCONV
    ]

proc procPtrTypeUnnamed(rettype, params: Snippet): Snippet =
  rettype & "(*)" & params

proc procPtrTypeUnnamedNimCall(rettype, params: Snippet): Snippet =
  rettype & "(N_RAW_NIMCALL*)" & params

proc procPtrTypeUnnamed(callConv: TCallingConvention, rettype, params: Snippet): Snippet =
  CallingConvToStr[callConv] & "_PTR(" & rettype & ", )" & params

type CppCaptureKind = enum None, ByReference, ByCopy

template addCppLambda(builder: var Builder, captures: CppCaptureKind, params: Snippet, body: typed) =
  builder.add("[")
  case captures
  of None: discard
  of ByReference: builder.add("&")
  of ByCopy: builder.add("=")
  builder.add("] ")
  builder.add(params)
  builder.addLineEndIndent(" {")
  body
  builder.addLineEndDedent("}")

proc cCast(typ, value: Snippet): Snippet =
  "((" & typ & ") " & value & ")"

proc wrapPar(value: Snippet): Snippet =
  # used for expression group, no-op on sexp
  "(" & value & ")"

proc removeSinglePar(value: Snippet): Snippet =
  # removes a single paren layer expected to exist, to silence Wparentheses-equality
  assert value[0] == '(' and value[^1] == ')'
  value[1..^2]

template addCast(builder: var Builder, typ: Snippet, valueBody: typed) =
  ## adds a cast to `typ` with value built by `valueBody`
  builder.add "(("
  builder.add typ
  builder.add ") "
  valueBody
  builder.add ")"

proc cAddr(value: Snippet): Snippet =
  "(&" & value & ")"

proc cLabelAddr(value: TLabel): Snippet =
  "&&" & value

proc cDeref(value: Snippet): Snippet =
  "(*" & value & ")"

proc subscript(a, b: Snippet): Snippet =
  a & "[" & b & "]"

proc dotField(a, b: Snippet): Snippet =
  a & "." & b

proc derefField(a, b: Snippet): Snippet =
  a & "->" & b

type CallBuilder = object
  needsComma: bool

proc initCallBuilder(builder: var Builder, callee: Snippet): CallBuilder =
  result = CallBuilder(needsComma: false)
  builder.add(callee)
  builder.add("(")

const cArgumentSeparator = ", "

proc addArgumentSeparator(builder: var Builder) =
  # no-op on NIFC
  # used by "single argument" builders
  builder.add(cArgumentSeparator)

template addArgument(builder: var Builder, call: var CallBuilder, valueBody: typed) =
  if call.needsComma:
    builder.addArgumentSeparator()
  else:
    call.needsComma = true
  valueBody

proc finishCallBuilder(builder: var Builder, call: CallBuilder) =
  builder.add(")")

template addCall(builder: var Builder, call: out CallBuilder, callee: Snippet, body: typed) =
  call = initCallBuilder(builder, callee)
  body
  finishCallBuilder(builder, call)

proc addCall(builder: var Builder, callee: Snippet, args: varargs[Snippet]) =
  builder.add(callee)
  builder.add("(")
  if args.len != 0:
    builder.add(args[0])
    for i in 1 ..< args.len:
      builder.add(", ")
      builder.add(args[i])
  builder.add(")")

proc cCall(callee: Snippet, args: varargs[Snippet]): Snippet =
  result = callee
  result.add("(")
  if args.len != 0:
    result.add(args[0])
    for i in 1 ..< args.len:
      result.add(", ")
      result.add(args[i])
  result.add(")")

proc addSizeof(builder: var Builder, val: Snippet) =
  builder.add("sizeof(")
  builder.add(val)
  builder.add(")")

proc addAlignof(builder: var Builder, val: Snippet) =
  builder.add("NIM_ALIGNOF(")
  builder.add(val)
  builder.add(")")

proc addOffsetof(builder: var Builder, val, member: Snippet) =
  builder.add("offsetof(")
  builder.add(val)
  builder.add(", ")
  builder.add(member)
  builder.add(")")

template cSizeof(val: Snippet): Snippet =
  "sizeof(" & val & ")"

template cAlignof(val: Snippet): Snippet =
  "NIM_ALIGNOF(" & val & ")"

template cOffsetof(val, member: Snippet): Snippet =
  "offsetof(" & val & ", " & member & ")"

type TypedBinaryOp = enum
  Add, Sub, Mul, Div, Mod
  Shr, Shl, BitAnd, BitOr, BitXor

const typedBinaryOperators: array[TypedBinaryOp, string] = [
  Add: "+",
  Sub: "-",
  Mul: "*",
  Div: "/",
  Mod: "%",
  Shr: ">>",
  Shl: "<<",
  BitAnd: "&",
  BitOr: "|",
  BitXor: "^"
]

type TypedUnaryOp = enum
  Neg, BitNot

const typedUnaryOperators: array[TypedUnaryOp, string] = [
  Neg: "-",
  BitNot: "~",
]

type UntypedBinaryOp = enum
  LessEqual, LessThan, GreaterEqual, GreaterThan, Equal, NotEqual
  And, Or

const untypedBinaryOperators: array[UntypedBinaryOp, string] = [
  LessEqual: "<=",
  LessThan: "<",
  GreaterEqual: ">=",
  GreaterThan: ">",
  Equal: "==",
  NotEqual: "!=",
  And: "&&",
  Or: "||"
]

type UntypedUnaryOp = enum
  Not

const untypedUnaryOperators: array[UntypedUnaryOp, string] = [
  Not: "!"
]

proc addOp(builder: var Builder, binOp: TypedBinaryOp, t: Snippet, a, b: Snippet) =
  builder.add('(')
  builder.add(a)
  builder.add(' ')
  builder.add(typedBinaryOperators[binOp])
  builder.add(' ')
  builder.add(b)
  builder.add(')')

proc addOp(builder: var Builder, unOp: TypedUnaryOp, t: Snippet, a: Snippet) =
  builder.add('(')
  builder.add(typedUnaryOperators[unOp])
  builder.add('(')
  builder.add(a)
  builder.add("))")

proc addOp(builder: var Builder, binOp: UntypedBinaryOp, a, b: Snippet) =
  builder.add('(')
  builder.add(a)
  builder.add(' ')
  builder.add(untypedBinaryOperators[binOp])
  builder.add(' ')
  builder.add(b)
  builder.add(')')

proc addOp(builder: var Builder, unOp: UntypedUnaryOp, a: Snippet) =
  builder.add('(')
  builder.add(untypedUnaryOperators[unOp])
  builder.add('(')
  builder.add(a)
  builder.add("))")

template cOp(binOp: TypedBinaryOp, t: Snippet, a, b: Snippet): Snippet =
  '(' & a & ' ' & typedBinaryOperators[binOp] & ' ' & b & ')'

template cOp(binOp: TypedUnaryOp, t: Snippet, a: Snippet): Snippet =
  '(' & typedUnaryOperators[binOp] & '(' & a & "))"

template cOp(binOp: UntypedBinaryOp, a, b: Snippet): Snippet =
  '(' & a & ' ' & untypedBinaryOperators[binOp] & ' ' & b & ')'

template cOp(binOp: UntypedUnaryOp, a: Snippet): Snippet =
  '(' & untypedUnaryOperators[binOp] & '(' & a & "))"

template cIfExpr(cond, a, b: Snippet): Snippet =
  # XXX used for `min` and `max`, maybe add nifc primitives for these
  "(" & cond & " ? " & a & " : " & b & ")"

template cUnlikely(val: Snippet): Snippet =
  "NIM_UNLIKELY(" & val & ")"
