/******************************************************************************
 * Copyright (c) 2000-2021 Ericsson Telecom AB
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.html
 *
 * Contributors:
 *   Baji, Laszlo
 *   Balasko, Jeno
 *   Baranyi, Botond
 *   Cserveni, Akos
 *   Delic, Adam
 *   Forstner, Matyas
 *   Kovacs, Ferenc
 *   Kremer, Peter
 *   Lovassy, Arpad
 *   Raduly, Csaba
 *   Szabados, Kristof
 *   Szabo, Janos Zoltan – initial implementation
 *   Szabo, Bence Janos
 *   Zalanyi, Balazs Andor
 *   Pandi, Krisztian
 *
 ******************************************************************************/
/* Main program for the TTCN-3 compiler */

/* C declarations */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

#include "../../common/memory.h"
#include "../../common/version_internal.h"
#include "../../common/path.h"
#include "../../common/userinfo.h"
#include "compiler.h"
#include "../main.hh"
#include "../error.h"
#include "profiler.h"

#ifdef LICENSE
#include "../../common/license.h"
#endif

#define BUFSIZE 1024

#undef COMMENT_PREFIX
#define COMMENT_PREFIX "// "

static unsigned int nof_updated_files=0;
static unsigned int nof_uptodate_files=0;
unsigned int nof_notupdated_files=0;

static boolean skip_over_lines(FILE *fp, size_t nof_lines)
{
    size_t i = 0;
    while (i < nof_lines) {
	switch (getc(fp)) {
	case EOF:
	    return TRUE;
	case '\n':
	    i++;
	default:
	    break;
	}
    }
    return FALSE;
}

static boolean update_file(const char *file_name, FILE *fp, size_t skip_lines)
{
    boolean files_differ = FALSE;

    FILE *target = fopen(file_name, "r");
    if (target == NULL) files_differ = TRUE;

    if (!files_differ) files_differ = skip_over_lines(target, skip_lines);

    if (!files_differ) {
        if (fseek(fp, 0L, SEEK_SET)) {
	    ERROR("Seek operation failed on a temporary file when trying to "
		"update output file `%s': %s", file_name, strerror(errno));
	}
	files_differ = skip_over_lines(fp, skip_lines);
    }

    if (!files_differ) {
	/* compare the rest of the files */
        for ( ; ; ) {
	    char buf1[BUFSIZE], buf2[BUFSIZE];
	    size_t len1 = fread(buf1, 1, BUFSIZE, fp);
	    size_t len2 = fread(buf2, 1, BUFSIZE, target);
	    if ((len1 != len2) || memcmp(buf1, buf2, len1)) {
		files_differ = TRUE;
		break;
	    } else if (len1 < BUFSIZE) break;
	}
    }

    if (target != NULL) fclose(target);

    if (files_differ) {
        if (fseek(fp, 0L, SEEK_SET)) {
	    ERROR("Seek operation failed on a temporary file when trying to "
		"update output file `%s': %s", file_name, strerror(errno));
	}
	target = fopen(file_name, "w");
	if (target == NULL) {
	    ERROR("Cannot open output file `%s' for writing: %s", file_name,
		strerror(errno));
	    exit(EXIT_FAILURE);
	}
        /* copy the contents of fp into target */
	for ( ; ; ) {
	    char buf[BUFSIZE];
	    size_t len = fread(buf, 1, BUFSIZE, fp);
	    if (fwrite(buf, 1, len, target) != len) {
		ERROR("Cannot write to output file `%s': %s", file_name,
		    strerror(errno));
		exit(EXIT_FAILURE);
	    }
	    if (len < BUFSIZE) break;
	}
        fclose(target);
    }

    return files_differ;
}

FILE *open_output_file(const char *file_name, boolean *is_temporary)
{
    FILE *fp;
    switch (get_path_status(file_name)) {
    case PS_FILE:
	if (force_overwrite) *is_temporary = FALSE;
	else *is_temporary = TRUE;
	break;
    case PS_DIRECTORY:
	ERROR("Output file `%s' is a directory", file_name);
	exit(EXIT_FAILURE);
	break;
    case PS_NONEXISTENT:
	*is_temporary = FALSE;
    }
    if (*is_temporary) {
	fp = tmpfile();
	if (fp == NULL) {
	    ERROR("Creation of a temporary file failed when trying to update "
		"output file `%s': %s", file_name, strerror(errno));
	    exit(EXIT_FAILURE);
	}
    } else {
	fp = fopen(file_name, "w");
	if (fp == NULL) {
	    ERROR("Cannot open output file `%s' for writing: %s", file_name,
		strerror(errno));
	    exit(EXIT_FAILURE);
	}
    }
    return fp;
}

void close_output_file(const char *file_name, FILE *fp,
    boolean is_temporary, size_t skip_lines)
{
    if (is_temporary) {
	if (update_file(file_name, fp, skip_lines)) {
	    NOTIFY("File `%s' was updated.", file_name);
	    nof_updated_files++;
	} else {
	    if (display_up_to_date) {
	      NOTIFY("File `%s' was up-to-date.", file_name);
	    } else {
	      DEBUG(1, "File `%s' was up-to-date.", file_name);
	    }
	    nof_uptodate_files++;
	}
    } else {
	NOTIFY("File `%s' was generated.", file_name);
	nof_updated_files++;
    }
    fclose(fp);
}

void write_output(output_struct *output, const char *module_name,
  const char *module_dispname, const char *filename_suffix, boolean is_ttcn,
  boolean has_circular_import, boolean is_module)
{
    char *header_name, *source_name, *user_info = NULL;
    FILE *fp;
    boolean is_temporary;

    if (output_dir != NULL) {
	header_name = mprintf("%s/%s%s.hh", output_dir,
	    duplicate_underscores ? module_name : module_dispname,
	    filename_suffix);
	source_name = mprintf("%s/%s%s.cc", output_dir,
	    duplicate_underscores ? module_name : module_dispname,
	    filename_suffix);
    } else {
	header_name = mprintf("%s%s.hh",
	    duplicate_underscores ? module_name : module_dispname,
	    filename_suffix);
	source_name = mprintf("%s%s.cc",
	    duplicate_underscores ? module_name : module_dispname,
	    filename_suffix);
    }
    if (disable_user_info == FALSE) {
      user_info = get_user_info();
    } else {
      user_info = mputstr(user_info, "The generation of user and time information were disabled by the -D flag.\n");
    }

    if (is_module) {

    DEBUG(1, "Writing file `%s' ... ", header_name);

    fp = open_output_file(header_name, &is_temporary);

    fprintf(fp, "// This C++ header file was generated by the %s compiler\n"
	"// of the TTCN-3 Test Executor version " PRODUCT_NUMBER "\n"
	"// %s%s\n"
	COPYRIGHT_STRING "\n\n"
	"// Do not edit this file unless you know what you are doing.\n\n"
	"#ifndef %s_HH\n"
	"#define %s_HH\n",
	is_ttcn ? "TTCN-3" : "ASN.1",
  disable_user_info == FALSE ? "for " : "",
	user_info, module_name, module_name);

  /* check if the generated code matches the runtime */
  fprintf(fp, "\n"
    "#if%sdef TITAN_RUNTIME_2\n"
    "#error Generated code does not match with used runtime.\\\n"
    " %s\n"
    "#endif\n",
    use_runtime_2 ? "n" : "",
    use_runtime_2 ?
      "Code was generated with -R option but -DTITAN_RUNTIME_2 was not used.":
      "Code was generated without -R option but -DTITAN_RUNTIME_2 was used.");

    /* #include directives must be the first in order to make GCC's
     * precompiled headers working. */
    if (output->header.includes != NULL) {
	fputs("\n/* Header file includes */\n\n", fp);
	fputs(output->header.includes, fp);
	fprintf(fp, "\n"
	    "#if TTCN3_VERSION != %d\n"
	    "#error Version mismatch detected.\\\n"
	    " Please check the version of the %s compiler and the "
	    "base library.\\\n"
	    " Run make clean and rebuild the project if the version of the compiler changed recently.\n"
	    "#endif\n",
	    TTCN3_VERSION, is_ttcn ? "TTCN-3" : "ASN.1");
	/* Check the platform matches */
#if defined SOLARIS || defined SOLARIS8
	fputs("#if !defined SOLARIS && !defined SOLARIS8 \n",fp);
	fputs("  #error This file should be compiled on SOLARIS or SOLARIS8 \n",fp);
	fputs("#endif \n",fp);

#else
	fprintf(fp, "\n"
	    "#ifndef %s\n"
	    "#error This file should be compiled on %s\n"
	    "#endif\n", expected_platform, expected_platform);
#endif
    }



	if (!has_circular_import)
	fprintf(fp, "\nnamespace %s {\n", module_name);

    if (output->header.class_decls != NULL) {
	if (has_circular_import) {
	    fprintf(fp, "\n#undef %s_HH\n"
		"#endif\n", module_name);
		fprintf(fp, "\nnamespace %s {\n", module_name);
	}
	fputs("\n/* Forward declarations of classes */\n\n",fp);
	fputs(output->header.class_decls, fp);
	if (has_circular_import) {
		fputs("\n} /* end of namespace */\n", fp);
	    fprintf(fp, "\n#ifndef %s_HH\n"
		"#define %s_HH\n", module_name, module_name);
	}
    }

    if (has_circular_import)
	fprintf(fp, "\nnamespace %s {\n", module_name);

    if (output->header.typedefs != NULL) {
	fputs("\n/* Type definitions */\n\n", fp);
	fputs(output->header.typedefs, fp);
    }

    if (output->header.class_defs != NULL) {
	fputs("\n/* Class definitions */\n\n", fp);
	fputs(output->header.class_defs, fp);
    }

    if (output->header.function_prototypes != NULL) {
	fputs("\n/* Function prototypes */\n\n", fp);
	fputs(output->header.function_prototypes, fp);
    }

    if (output->header.global_vars != NULL) {
	fputs("\n/* Global variable declarations */\n\n", fp);
	fputs(output->header.global_vars, fp);
    }

	fputs("\n} /* end of namespace */\n", fp);

    if (output->header.testport_includes != NULL) {
	fputs("\n/* Test port header files */\n\n", fp);
        fputs(output->header.testport_includes, fp);
    }

    fputs("\n#endif\n", fp);

    close_output_file(header_name, fp, is_temporary, 10);

    }

    DEBUG(1, "Writing file `%s' ... ", source_name);

    fp = open_output_file(source_name, &is_temporary);

    fprintf(fp, "// This C++ source file was generated by the %s compiler\n"
	"// of the TTCN-3 Test Executor version " PRODUCT_NUMBER "\n"
	"// %s%s\n"
	COPYRIGHT_STRING "\n\n"
	"// Do not edit this file unless you know what you are doing.\n",
	is_ttcn ? "TTCN-3" : "ASN.1", 
  disable_user_info == FALSE ? "for " : "",
  user_info);

    if (output->source.includes != NULL) {
	fputs("\n/* Including header files */\n\n", fp);
	fputs(output->source.includes, fp);
    }

	fprintf(fp, "\nnamespace %s {\n", module_name);

    if (output->source.static_function_prototypes != NULL) {
	fputs("\n/* Prototypes of static functions */\n\n", fp);
	fputs(output->source.static_function_prototypes, fp);
    }

    if (output->source.static_conversion_function_prototypes != NULL) {
  fputs("\n/* Prototypes of static conversion functions */\n\n", fp);
  fputs(output->source.static_conversion_function_prototypes, fp);
    }

    if (output->source.string_literals != NULL) {
	fputs("\n/* Literal string constants */\n\n", fp);
	fputs(output->source.string_literals, fp);
    }

    if (output->source.class_defs != NULL) {
	fputs("\n/* Class definitions for internal use */\n\n", fp);
	fputs(output->source.class_defs, fp);
    }

    if (output->source.global_vars != NULL) {
	fputs("\n/* Global variable definitions */\n\n", fp);
	fputs(output->source.global_vars, fp);
    }

    if (output->source.methods != NULL) {
	fputs("\n/* Member functions of C++ classes */\n\n", fp);
	fputs(output->source.methods, fp);
    }

    if (output->source.function_bodies != NULL) {
	fputs("\n/* Bodies of functions, altsteps and testcases */\n\n", fp);
	fputs(output->source.function_bodies, fp);
    }

    if (output->source.static_function_bodies != NULL) {
	fputs("\n/* Bodies of static functions */\n\n", fp);
	fputs(output->source.static_function_bodies, fp);
    }

    if (output->source.static_conversion_function_bodies != NULL) {
  fputs("\n/* Bodies of static conversion functions */\n\n", fp);
  fputs(output->source.static_conversion_function_bodies, fp);
    }

	fputs("\n} /* end of namespace */\n", fp);

    close_output_file(source_name, fp, is_temporary, 7);

    Free(header_name);
    Free(source_name);
    Free(user_info);
}

void report_nof_updated_files(void)
{
  DEBUG(1, "%u files updated, %u were up-to-date, %u excluded from code"
        " generation.",
        nof_updated_files, nof_uptodate_files, nof_notupdated_files);
  if(nof_updated_files!=0)
    NOTIFY("%u file%s updated.", nof_updated_files,
      nof_updated_files > 1 ? "s were" : " was");
  else
    NOTIFY("None of the files needed update.");
}
