#!/usr/bin/env python3

# Copyright (c) 2022 Firebuild Inc.
# All rights reserved.
# Free for personal use and commercial trial.
# Non-trial commercial use requires licenses available from https://firebuild.com.
# Modification and redistribution are permitted, but commercial use of
# derivative works is subject to the same requirements of this license
#
# 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.

import os
import sys
from jinja2 import Environment, FileSystemLoader

if len(sys.argv) != 3:
  print("Usage: generate_fbb namespace outputdir", file=sys.stderr)
  print("  Processes the input file \"<namespace>\".def from the current directory", file=sys.stderr)
  exit(1)
fbbdir = os.path.dirname(sys.argv[0])  # wherever generate_fbb and tpl.[ch] reside
namespace = sys.argv[1]
outdir = sys.argv[2]

env = Environment(loader=FileSystemLoader(fbbdir),
                  line_statement_prefix='###',
                  trim_blocks=True,
                  lstrip_blocks=True,
                  keep_trailing_newline=True,
                  extensions=['jinja2.ext.do'])

# Use a practically unique temporary filename, see #314
tmpsuffix = ".tmp." + str(os.getpid())

# Symbolic constants to reduce the chance of typos
REQUIRED = "required"
OPTIONAL = "optional"
ARRAY = "array"
STRING = "string"
FBB = "fbb"

def gen_fbb(params):
  msgs = params.get("tags")

  # Python can easily unpack tuples whose size isn't known in advance. Jinja cannot. I don't want to
  # bloat the FBB definitions by requiring a None for all the rarely used debugging-related 4th
  # fields. Alter the big msgs object here so that all its tuples contain all 4 fields.
  for (msg, fields) in msgs:
    for (index, (req, type, var, *args)) in enumerate(fields):
      debugger_method = args[0] if len(args) > 0 else None
      fields[index] = (req, type, var, debugger_method)

  for (msg, fields) in msgs:
    for (req, type, var, dbgfn) in fields:
      if req not in [REQUIRED, OPTIONAL, ARRAY]:
        print("Unknown value instead of REQUIRED, OPTIONAL or ARRAY", file=sys.stderr)
        exit(1)

  for extension in [".c", ".h", "_decode.c"]:
    template = env.get_template("tpl" + extension)
    rendered = template.render(ns=namespace,
                               msgs=msgs,
                               types_with_custom_debugger=params.get("types_with_custom_debugger", []),
                               varnames_with_custom_debugger=params.get("varnames_with_custom_debugger", []),
                               extra_c=params.get("extra_c", ""),
                               extra_h=params.get("extra_h", ""),
                               REQUIRED=REQUIRED,
                               OPTIONAL=OPTIONAL,
                               ARRAY=ARRAY,
                               STRING=STRING,
                               FBB=FBB)
    if rendered:
      filename = outdir + "/" + namespace + extension
      with open(filename + tmpsuffix, "w") as f:
        f.write(rendered)
      os.rename(filename + tmpsuffix, filename)

      # Generate the same file with both .c and .cc extension, so that cmake can
      # easily compile the C and C++ variants separately.
      if extension == ".c":
        extension = ".cc"
        filename = outdir + "/" + namespace + extension
        with open(filename + tmpsuffix, "w") as f:
          f.write(rendered)
        os.rename(filename + tmpsuffix, filename)

with open("" + namespace + ".def", "r") as f:
  params = eval(f.read())
  gen_fbb(params)
