//! @file emit.c
//! @author J. Marcel van der Veer
//
//! @section Copyright
//
// This file is part of VIF - vintage FORTRAN compiler.
// Copyright 2020-2025 J. Marcel van der Veer <algol68g@xs4all.nl>.
//
//! @section License
//
// This program is free software; you can redistribute it and/or modify it 
// under the terms of the GNU General Public License as published by the 
// Free Software Foundation; either version 3 of the License, or 
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but 
// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 
// more details. You should have received a copy of the GNU General Public 
// License along with this program. If not, see <http://www.gnu.org/licenses/>.

//! @section Synopsis
//!
//! Emit C object code.

#include <vif.h>

//
// Write intermediate code
//

char *newpage (char *module, char *section)
{
  NEW_RECORD (str);
  _srecordf (str, "\f %s %s", module, section);
  return f_stralloc (str);
}

logical_4 no_comment (char *str)
{
  if (strncmp (str, "//", 2) == 0) {
    return FALSE;
  }
  if (strncmp (str, "#", 1) == 0) {
    return FALSE;
  }
  return TRUE;
}

void newline (FILE * obj, char *info, int_4 phase, int_4 force)
{
  if (page == 0 || (info != NULL && info[0] == '\f')) {
    sscanf (&info[1], "%s %s", hmodule, hsection);
  }
  if (force || line >= LINES_PER_PAGE) {
    NEW_RECORD (str);
    page++;
    _srecordf (str, "// %s  %16s  ** %-28s ** %-48s PAGE %05d", _strupper (PACKAGE), _strupper (hdate), _strupper (hmodule), _strupper (hsection), page);
    for (int_4 k = 0; k < (int_4) strlen (str); k++) {
      if (str[k] == '-') {
        str[k] = ' ';
      }
    }
    if (page == 1) {
      fprintf (obj, "%s\n\n", str);
      line = 1;
    } else {
      fprintf (obj, "\f\n%s\n\n", str);
      line = 1;
    }
    if (phase == JCL) {
      fprintf (obj, "\n//   Line    JCL *...*....1....|....2....|....3....|....4....|....5....|....6....|....7..*.|....8\n");
      line += 2;
    }
    if (phase == LIST) {
      fprintf (obj, "\n//   Line    ISN *...*....1....|....2....|....3....|....4....|....5....|....6....|....7..*.|....8\n");
      line += 2;
    }
  } else {
    fprintf (obj, "\n");
    line++;
  }
}

void indentation (FILE *obj, int_4 ind) {
  for (int_4 k = 1; k <= ind; k++) {
    fprintf (obj, " ");
  }
}

void emit_code (FILE * obj, int_4 proc)
{
// Write the procedure 'proc'.
  int_4 nl = FALSE, printed = 0;
  indent = 0;
  for (int_4 phase = HEADER; phase < MAXPHA; phase++) {
    int_4 N = 0;
    NEW_RECORD (prev);
    prev[0] = '\0';
    for (int_4 c_src = 0; c_src < n_c_src; c_src++) {
      C_SRC *lin = &object[c_src];
      if (! (lin->proc == proc && lin->phase == phase)) {
        continue;
      } else if (lin->text != NULL) {
        int_4 last = strlen (lin->text) - 1;
        if (lin->text[0] == '\f') {
          newline (obj, lin->text, phase, TRUE);
          prev[0] = '\0';
          N = 0;
        } else if (lin->text[0] == '\"' && lin->text[last] == '\"') {
          fprintf (obj, "%s", lin->text);
        } else if (lin->text[0] == '\'' && lin->text[last] == '\'') {
          fprintf (obj, "%s", lin->text);
        } else {
// Close block - indent less.
          if (no_comment (lin->text) && strchr (lin->text, '}') != NULL && strchr (lin->text, '{') == NULL) {
            indent = _max (0, indent - INDENT);
          }
// Indent, but not comments or cpp directives.
          if (nl && strncmp (lin->text, "# ", 2) != 0 && strncmp (lin->text, "//", 2) != 0) {
            if (strncmp (lin->text, "#define", 7) != 0) { 
              indentation (obj, indent);
              printed += INDENT;
            }
            nl = FALSE;
          }
// Write new line.
          if (lin->text[last] == '\n') {
            nl = TRUE;
            last--;
          }
          if (last >= 0) {
// Open block - indent more.
            if (no_comment (lin->text) && strchr (lin->text, '{') != NULL && strchr (lin->text, '}') == NULL) {
              indent += INDENT;
            }
// Write respecting LINE_WIDTH.
            if (strncmp (lin->text, "#", 1) == 0 || strncmp (lin->text, "//", 2) == 0) {
              NEW_RECORD (str);
              bufcpy (str, lin->text, RECLN);
              int_4 len = strlen (str);
              if (len > 0 && str[len - 1] == '\n') {
                str[len - 1] = '\0';
              }
              fprintf (obj, "%s", str);
            } else if (strncmp (lin->text, "~", 1) == 0) {
              NEW_RECORD (str);
              bufcpy (str, lin->text, RECLN);
              int_4 len = strlen (str);
              if (len > 0 && str[len - 1] == '\n') {
                str[len - 1] = '\0';
              }
              fprintf (obj, "%s", &str[1]);
            } else {
              NEW_RECORD (str);
              bufcpy (str, lin->text, RECLN);
              char *rest = NULL, *token;
              for (token = strtok_r (str, " ", &rest); token != NULL; token = strtok_r (NULL, " ", &rest)) {
                int_4 len = strlen (token);
                if (N > 0) {
                  if (strchr (",;)}", token[0]) == NULL) {
                    if (strlen (prev) > 0 && strchr ("([", prev[strlen(prev) - 1]) == NULL) {
                      fprintf (obj, " ");
                      printed++;
                    }
                  }
                }
                if (printed + len >= LINE_WIDTH) {
                  newline (obj, lin->text, phase, FALSE);
                  indentation (obj, indent);
                  printed = INDENT;
                  prev[0] = '\0';
                  N = 0;
                }
                NEW_RECORD (tok);
                bufcpy (tok, token, RECLN);
                int_4 M = strlen (tok);
                if (M > 0 && tok[M - 1] == '\n') {
                  tok[M - 1] = '\0';
                }
                fprintf (obj, "%s", tok);
                bufcpy (prev, tok /* token ? */, RECLN);
                printed += strlen (tok);
                N++;
              }
            }
          }
// New line afterwards.
          if (nl) {
            newline (obj, lin->text, phase, FALSE);
            printed = 0;
            prev[0] = '\0';
            N = 0;
          }
        }
      }
    }
// Final new line.
    if (proc == 0) {
      newline (obj, "\n", MAXPHA, FALSE);
    }
  }
  if (proc > 0) {
    newline (obj, "\n", MAXPHA, FALSE);
  }
}

void write_object (char *name)
{
// Object code to file.
  FILE *obj;
  int_4 proc;
  if ((obj = fopen (name, "w")) == NULL) {
    FATAL (1301, "cannot open for writing", name);
    exit (EXIT_FAILURE);
  };
  page = 0;
  for (proc = 0; proc <= nprocs; proc++) {
    emit_code (obj, proc);
  }
  fclose (obj);
}
