// Copyright(c) 2021 to 2022 ZettaScale Technology and others
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License
// v. 1.0 which is available at
// http://www.eclipse.org/org/documents/edl-v10.php.
//
// SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause

#include <assert.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <inttypes.h>

#include "libidlc__generator.h"

#include "idlc/generator.h"
#include "idl/file.h"
#include "idl/heap.h"
#include "idl/retcode.h"
#include "idl/stream.h"
#include "idl/string.h"
#include "idl/version.h"
#include "idl/processor.h"
#include "idl/print.h"
#include "libidlc__types.h"

const char *export_macro = NULL;
const char *header_guard_prefix = "DDSC_";
int generate_cdrstream_desc = 0;

static idl_retcode_t print_header(FILE *fh, const char *in, const char *out)
{
  static const char fmt[] =
    "/****************************************************************\n"
    "\n"
    "  Generated by Eclipse Cyclone DDS IDL to C Translator\n"
    "  File name: %s\n"
    "  Source: %s\n"
    "  Cyclone DDS: V%s\n"
    "\n"
    "*****************************************************************/\n";

  if (idl_fprintf(fh, fmt, out, in, IDL_VERSION) < 0)
    return IDL_RETCODE_NO_MEMORY;
  return IDL_RETCODE_OK;
}

static idl_retcode_t print_guard(FILE *fh, const char *in)
{
  for (const char *ptr = in; *ptr; ptr++) {
    int chr = (unsigned char)*ptr;
    if (idl_islower((unsigned char)*ptr))
      chr = idl_toupper((unsigned char)*ptr);
    else if (!idl_isalnum((unsigned char)*ptr))
      chr = '_';
    if (fputc(chr, fh) == EOF)
      return IDL_RETCODE_NO_MEMORY;
  }

  return IDL_RETCODE_OK;
}

static idl_retcode_t print_guard_if(FILE *fh, const char *in)
{
  if (fputs("#ifndef ", fh) < 0)
    return IDL_RETCODE_NO_MEMORY;
  if (print_guard(fh, in))
    return IDL_RETCODE_NO_MEMORY;
  if (fputs("\n#define ", fh) < 0)
    return IDL_RETCODE_NO_MEMORY;
  if (print_guard(fh, in))
    return IDL_RETCODE_NO_MEMORY;
  if (fputs("\n\n", fh) < 0)
    return IDL_RETCODE_NO_MEMORY;
  return IDL_RETCODE_OK;
}

static idl_retcode_t print_guard_endif(FILE *fh, const char *in)
{
  if (fputs("#endif /* ", fh) < 0)
    return IDL_RETCODE_NO_MEMORY;
  if (print_guard(fh, in))
    return IDL_RETCODE_NO_MEMORY;
  if (fputs(" */\n", fh) < 0)
    return IDL_RETCODE_NO_MEMORY;
  return IDL_RETCODE_OK;
}

static idl_retcode_t print_includes(FILE *fh, const idl_source_t *source)
{
  const idl_source_t *include;

  for (include = source->includes; include; include = include->next) {
    const char *ext = include->file->name;
    for (const char *ptr = ext; *ptr; ptr++) {
      if (*ptr == '.')
        ext = ptr;
    }

    if (idl_strcasecmp(ext, ".idl") == 0) {
      const char *fmt = "#include \"%.*s.h\"\n";
      int len = (int)(ext - include->file->name);
      if (idl_fprintf(fh, fmt, len, include->file->name) < 0)
        return IDL_RETCODE_NO_MEMORY;
    } else {
      const char *fmt = "#include \"%s\"\n";
      if (idl_fprintf(fh, fmt, include->file->name) < 0)
        return IDL_RETCODE_NO_MEMORY;
    }
    if (fputs("\n", fh) < 0)
      return IDL_RETCODE_NO_MEMORY;
  }

  return IDL_RETCODE_OK;
}

static idl_retcode_t
generate_nosetup(const idl_pstate_t *pstate, struct generator *generator)
{
  idl_retcode_t ret;
  const char *sep, *file = generator->path;
  char *const header_file = generator->header.path;
  char *const source_file = generator->source.path;

  if ((ret = print_header(generator->header.handle, file, header_file)))
    return ret;
  if ((ret = print_guard_if(generator->header.handle, generator->config.guard_macro)))
    return ret;
  if ((ret = print_includes(generator->header.handle, pstate->sources)))
    return ret;
  if (fputs("#include \"dds/ddsc/dds_public_impl.h\"\n", generator->header.handle) < 0)
    return IDL_RETCODE_NO_MEMORY;
  if (generator->config.generate_cdrstream_desc && fputs("#include \"dds/cdr/dds_cdrstream.h\"\n", generator->header.handle) < 0)
    return IDL_RETCODE_NO_MEMORY;
  if (fputs("\n", generator->header.handle) < 0)
    return IDL_RETCODE_NO_MEMORY;
  if (fputs("#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n", generator->header.handle) < 0)
    return IDL_RETCODE_NO_MEMORY;
  if ((ret = print_header(generator->source.handle, file, source_file)))
    return ret;
  /* generate include statement for header in source */
  sep = header_file;
  for (const char *ptr = sep; *ptr; ptr++)
    if (idl_isseparator((unsigned char)*ptr))
      sep = ptr+1;
  if (idl_fprintf(generator->source.handle, "#include \"%s\"\n\n", sep) < 0)
    return IDL_RETCODE_NO_MEMORY;
  if ((ret = generate_types(pstate, generator)))
    return ret;
  if (fputs("#ifdef __cplusplus\n}\n#endif\n\n", generator->header.handle) < 0)
    return IDL_RETCODE_NO_MEMORY;
  if ((ret = print_guard_endif(generator->header.handle, generator->config.guard_macro)))
    return ret;

  return IDL_RETCODE_OK;
}

static char *
create_guard(const char *prefix, const char *base, const idl_md5_byte_t digest[16])
{
  char *guard = NULL;
  size_t guard_size = strlen(prefix) + strlen(base) + 34;
  size_t offset = 0;

  if(!(guard = idl_malloc(guard_size)))
    return NULL;
  if(!idl_strlcpy(guard, prefix, guard_size))
    goto err_guard;
  offset = strlen(guard);
  if(!idl_strlcpy(&guard[offset], base, guard_size-offset))
    goto err_guard;
  offset = strlen(guard);
  if(!idl_strlcpy(&guard[offset], "_", guard_size-offset))
    goto err_guard;
  for (size_t i=0; i<16; i++) {
    offset = strlen(guard);
    if(idl_snprintf(&guard[offset], guard_size-offset, "%02X", digest[i]) < 0)
      goto err_guard;
  }
  return guard;

err_guard:
  if (guard != NULL)
    idl_free(guard);
  return NULL;
}

static const idlc_option_t *opts[] = {
  &(idlc_option_t){
    IDLC_STRING, { .string = &export_macro }, 'e', "", "<export macro>",
    "Add export macro before topic descriptors." },
  &(idlc_option_t){
    IDLC_FLAG, { .flag = &generate_cdrstream_desc }, 'f', "cdrstream-desc", "",
    "Generate CDR descriptor in addition to regular topic descriptor." },
  &(idlc_option_t){
    IDLC_STRING, { .string = &header_guard_prefix },
    'f', "header-guard-prefix", "<header guard prefix>",
    "Prefix to use for header inclusion guard macros (default: DDSC_)." },
  NULL
};

const idlc_option_t** generator_options(void)
{
  return opts;
}

idl_retcode_t
generate(const idl_pstate_t *pstate, const idlc_generator_config_t *config)
{
  idl_retcode_t ret = IDL_RETCODE_NO_MEMORY;
  struct generator generator;

  assert(pstate->paths);
  assert(pstate->paths->name);
  assert(config);
  const char* path = pstate->sources->path->name;

  memset(&generator, 0, sizeof(generator));
  generator.path = path;

  if (idl_generate_out_file(path, config->output_dir, config->base_dir, "h", &generator.header.path, false) < 0)
    goto err_header;
  if (!(generator.header.handle = idl_fopen(generator.header.path, "wb")))
    goto err_header;
  if (idl_generate_out_file(path, config->output_dir, config->base_dir, "c", &generator.source.path, false) < 0)
    goto err_source;
  if (!(generator.source.handle = idl_fopen(generator.source.path, "wb")))
    goto err_source;
  generator.config.c = *config;
  if (export_macro) {
    if (!(generator.config.export_macro = idl_strdup (export_macro)))
      goto err_options;
  } else {
    generator.config.export_macro = NULL;
  }
  if(!(generator.config.guard_macro = create_guard(header_guard_prefix, generator.header.path, pstate->digest)))
    goto err_options;
  generator.config.generate_cdrstream_desc = (generate_cdrstream_desc != 0);
  ret = generate_nosetup(pstate, &generator);
  if(generator.config.guard_macro)
    idl_free(generator.config.guard_macro);

err_options:
  if (generator.config.export_macro)
    idl_free(generator.config.export_macro);
err_source:
  if (generator.source.handle)
    fclose(generator.source.handle);
  if (generator.source.path)
    idl_free(generator.source.path);
err_header:
  if (generator.header.handle)
    fclose(generator.header.handle);
  if (generator.header.path)
    idl_free(generator.header.path);
  return ret;
}
