/*=========================================================================

  Program:   Visualization Toolkit
  Module:    vtkWrapHierarchy.c

  Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
  All rights reserved.
  See Copyright.txt or http://www.kitware.com/Copyright.htm for details.

     This software is distributed WITHOUT ANY WARRANTY; without even
     the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
     PURPOSE.  See the above copyright notice for more information.

=========================================================================*/
/*-------------------------------------------------------------------------
  Copyright (c) 2010 David Gobbi.

  Contributed to the VisualizationToolkit by the author in June 2010
  under the terms of the Visualization Toolkit 2008 copyright.
-------------------------------------------------------------------------*/

/**
 The vtkWrapHierarchy program builds a text file that describes the
 class hierarchy.
 For each class, the output file will have a line in the following
 format:

 classname [ : superclass ] ; header.h ; kit [; flags]

 For each enum type,

 enumname : enum ; header.h ; kit [; flags]

 For each typedef,

 name = [2][3]* const int ; header.h ; kit [; flags]

*/

#include "vtkParse.h"
#include "vtkParseData.h"
#include "vtkParseExtras.h"
#include "vtkParseMain.h"
#include "vtkParsePreprocess.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#ifdef _WIN32
# include <windows.h>
#else
# include <unistd.h>
#endif

/**
 * Helper to append a text line to an array of lines
 */
static char **append_unique_line(char **lines, char *line, size_t *np)
{
  size_t l, n, m;

  n = *np;
  m = vtkParse_NameLength(line);

  /* check to make sure this line isn't a duplicate */
  for (l = 0; l < n; l++)
  {
    if (vtkParse_NameLength(lines[l]) == m &&
        strncmp(line, lines[l], m) == 0)
    {
      break;
    }
  }
  if (l == n)
  {
    /* allocate more memory if n+1 is a power of two */
    if (((n+1) & n) == 0)
    {
      lines = (char **)realloc(lines, (n+1)*2*sizeof(char *));
    }

    lines[n] = (char *)malloc(strlen(line)+1);
    strcpy(lines[n++], line);
    lines[n] = NULL;
  }

  *np = n;
  return lines;
}

/**
 * Helper to append to a line, given the end marker
 */
static char *append_to_line(
  char *line, const char *text, size_t *pos, size_t *maxlen)
{
  size_t n;

  n = strlen(text);

  if ((*pos) + n + 1 > (*maxlen))
  {
    *maxlen = ((*pos) + n + 1 + 2*(*maxlen));
    line = (char *)realloc(line, (*maxlen));
  }

  strcpy(&line[*pos], text);
  *pos = (*pos) + n;

  return line;
}

/**
 * Append scope to line
 */
static char *append_scope_to_line(
  char *line, size_t *m, size_t *maxlen, const char *scope)
{
  if (scope && scope[0] != '\0')
  {
    line = append_to_line(line, scope, m, maxlen);
    line = append_to_line(line, "::", m, maxlen);
  }

  return line;
}

/**
 * Append template info
 */
static char *append_template_to_line(
  char *line, size_t *m, size_t *maxlen, TemplateInfo *template_args)
{
  ValueInfo *arg;
  int j;

  line = append_to_line(line, "<", m, maxlen);

  for (j = 0; j < template_args->NumberOfParameters; j++)
  {
    arg = template_args->Parameters[j];
    if (arg->Name)
    {
      line = append_to_line(line, arg->Name, m, maxlen);
    }
    if (arg->Value && arg->Value[0] != '\n')
    {
      line = append_to_line(line, "=", m, maxlen);
      line = append_to_line(line, arg->Value, m, maxlen);
    }
    if (j+1 < template_args->NumberOfParameters)
    {
      line = append_to_line(line, ",", m, maxlen);
    }
  }

  line = append_to_line(line, ">", m, maxlen);

  return line;
}

/**
 * Append class info
 */
static char *append_class_to_line(
  char *line, size_t *m, size_t *maxlen, ClassInfo *class_info)
{
  int j;

  line = append_to_line(line, class_info->Name, m, maxlen);

  if (class_info->Template)
  {
    line = append_template_to_line(line, m, maxlen, class_info->Template);
  }

  line = append_to_line(line, " ", m, maxlen);

  if (class_info->NumberOfSuperClasses)
  {
    line = append_to_line(line, ": ", m, maxlen);
  }

  for (j = 0; j < class_info->NumberOfSuperClasses; j++)
  {
    line = append_to_line(line, class_info->SuperClasses[j], m, maxlen);
    line = append_to_line(line, " ", m, maxlen);
    if (j+1 < class_info->NumberOfSuperClasses)
    {
      line = append_to_line(line, ", ", m, maxlen);
    }
  }

  return line;
}

/**
 * Append enum info
 */
static char *append_enum_to_line(
  char *line, size_t *m, size_t *maxlen, EnumInfo *enum_info)
{
  line = append_to_line(line, enum_info->Name, m, maxlen);
  line = append_to_line(line, " : enum ", m, maxlen);

  return line;
}

/**
 * Append the trailer, i.e. the filename and flags
 */
static char *append_trailer(
  char *line, size_t *m, size_t *maxlen,
  const char *header_file, const char *module_name, const char *flags)
{
  line = append_to_line(line, "; ", m, maxlen);
  line = append_to_line(line, header_file, m, maxlen);

  line = append_to_line(line, " ; ", m, maxlen);
  line = append_to_line(line, module_name, m, maxlen);

  if (flags && flags[0] != '\0')
  {
    line = append_to_line(line, " ; ", m, maxlen);
    line = append_to_line(line, flags, m, maxlen);
  }

  return line;
}

/**
 * Append typedef info
 */
static char *append_typedef_to_line(
  char *line, size_t *m, size_t *maxlen, ValueInfo *typedef_info)
{
  unsigned int type;
  int ndims;
  int dim;

  line = append_to_line(line, typedef_info->Name, m, maxlen);
  line = append_to_line(line, " = ", m, maxlen);

  type = typedef_info->Type;

  if ((type & VTK_PARSE_REF) != 0)
  {
    line = append_to_line(line, "&", m, maxlen);
  }

  ndims = typedef_info->NumberOfDimensions;

  for (dim = 0; dim < ndims; dim++)
  {
    line = append_to_line(line, "[", m, maxlen);
    line = append_to_line(line, typedef_info->Dimensions[dim],
                          m, maxlen);
    line = append_to_line(line, "]", m, maxlen);
  }

  type = (type & VTK_PARSE_POINTER_MASK);
  if (ndims > 0 && (type & VTK_PARSE_POINTER_LOWMASK) == VTK_PARSE_ARRAY)
  {
    type = ((type >> 2) & VTK_PARSE_POINTER_MASK);
  }
  else if (ndims == 1)
  {
    type = ((type >> 2) & VTK_PARSE_POINTER_MASK);
  }

  /* pointers are printed after brackets, and are intentionally
   * printed in reverse order as compared to C++ declarations */
  while (type)
  {
    unsigned int bits = (type & VTK_PARSE_POINTER_LOWMASK);
    type = ((type >> 2) & VTK_PARSE_POINTER_MASK);

    if (bits == VTK_PARSE_POINTER)
    {
      line = append_to_line(line, "*", m, maxlen);
    }
    else if (bits == VTK_PARSE_CONST_POINTER)
    {
      line = append_to_line(line, "const*", m, maxlen);
    }
    else
    {
      line = append_to_line(line, "[]", m, maxlen);
    }
  }

  if (line[*m-1] != ' ')
  {
    line = append_to_line(line, " ", m, maxlen);
  }

  if ((type & VTK_PARSE_CONST) != 0)
  {
    line = append_to_line(line, "const ", m, maxlen);
  }

  line = append_to_line(line, typedef_info->Class, m, maxlen);
  line = append_to_line(line, " ", m, maxlen);

  return line;
}

/**
 * Append all types in a class
 */
static char **append_class_contents(
  char **lines, size_t *np, ClassInfo *data,
  const char *scope, const char *header_file, const char *module_name)
{
  int i;
  const char *tmpflags;
  char *new_scope;
  char *line;
  size_t m, n, maxlen;
  size_t scope_m, scope_maxlen;

  /* append the name to the scope */
  new_scope = 0;
  n = 0;
  m = 0;
  if (scope)
  {
    n = strlen(scope);
  }
  if (data->Name)
  {
    m = strlen(data->Name);
  }
  if (m && (n || data->Template))
  {
    scope_maxlen = n + m + 3;
    scope_m = 0;
    new_scope = (char *)malloc(scope_maxlen);
    new_scope[0] = '\0';
    if (n)
    {
      new_scope = append_to_line(new_scope, scope, &scope_m, &scope_maxlen);
      new_scope = append_to_line(new_scope, "::", &scope_m, &scope_maxlen);
    }
    new_scope = append_to_line(new_scope, data->Name, &scope_m, &scope_maxlen);
    if (data->Template)
    {
      new_scope = append_template_to_line(
        new_scope, &scope_m, &scope_maxlen, data->Template);
    }
    scope = new_scope;
  }
  else if (m)
  {
    scope = data->Name;
  }

  /* start with a buffer of 15 chars and grow from there */
  maxlen = 15;
  m = 0;
  line = (char *)malloc(maxlen);

  /* add a line for each type that is found */
  for (i = 0; i < data->NumberOfItems; i++)
  {
    m = 0;
    line[m] = '\0';

    tmpflags = 0;

    if (data->Items[i].Type == VTK_CLASS_INFO ||
        data->Items[i].Type == VTK_STRUCT_INFO)
    {
      ClassInfo *class_info =
        data->Classes[data->Items[i].Index];

      line = append_scope_to_line(line, &m, &maxlen, scope);
      line = append_class_to_line(line, &m, &maxlen, class_info);
      tmpflags = "WRAPEXCLUDE";
    }
    else if (data->Items[i].Type == VTK_ENUM_INFO)
    {
      line = append_scope_to_line(line, &m, &maxlen, scope);
      line = append_enum_to_line(line, &m, &maxlen,
        data->Enums[data->Items[i].Index]);
    }
    else if (data->Items[i].Type == VTK_TYPEDEF_INFO)
    {
      line = append_scope_to_line(line, &m, &maxlen, scope);
      line = append_typedef_to_line(line, &m, &maxlen,
        data->Typedefs[data->Items[i].Index]);
    }
    else
    {
      /* unhandled file element */
      continue;
    }

    /* append filename and flags */
    line = append_trailer(
      line, &m, &maxlen, header_file, module_name, tmpflags);

    /* append the line to the file */
    lines = append_unique_line(lines, line, np);

    /* for classes, add all typed defined within the class */
    if ((data->Items[i].Type == VTK_CLASS_INFO ||
         data->Items[i].Type == VTK_STRUCT_INFO) &&
        data->Classes[data->Items[i].Index]->Name)
    {
      lines = append_class_contents(lines, np,
        data->Classes[data->Items[i].Index],
        scope, header_file, module_name);
    }
  }

  free(line);

  if (new_scope != 0)
  {
    free(new_scope);
  }

  return lines;
}


/**
 * Append all types in a namespace
 */
static char **append_namespace_contents(
  char **lines, size_t *np, NamespaceInfo *data, const char *scope,
  const char *header_file, const char *module_name, const char *flags)
{
  int i;
  const char *tmpflags;
  char *line;
  char *new_scope;
  size_t n, m, maxlen;

  /* append the name to the scope */
  new_scope = 0;
  n = 0;
  m = 0;
  if (scope)
  {
    n = strlen(scope);
  }
  if (data->Name)
  {
    m = strlen(data->Name);
  }
  if (m && n)
  {
    new_scope = (char *)malloc(m + n + 3);
    strncpy(new_scope, scope, n);
    new_scope[n++] = ':';
    new_scope[n++] = ':';
    strncpy(&new_scope[n], data->Name, m + 1);
    scope = new_scope;
  }
  else if (m)
  {
    scope = data->Name;
  }

  /* start with a buffer of 15 chars and grow from there */
  maxlen = 15;
  m = 0;
  line = (char *)malloc(maxlen);

  /* add a line for each type that is found */
  for (i = 0; i < data->NumberOfItems; i++)
  {
    tmpflags = flags;
    m = 0;
    line[m] = '\0';

    if (data->Items[i].Type == VTK_CLASS_INFO ||
        data->Items[i].Type == VTK_STRUCT_INFO)
    {
      ClassInfo *class_info = data->Classes[data->Items[i].Index];
      if (class_info->IsExcluded)
      {
        tmpflags = "WRAPEXCLUDE";
      }

      line = append_scope_to_line(line, &m, &maxlen, scope);
      line = append_class_to_line(line, &m, &maxlen, class_info);
    }
    else if (data->Items[i].Type == VTK_ENUM_INFO)
    {
      EnumInfo *enum_info = data->Enums[data->Items[i].Index];
      if (enum_info->IsExcluded)
      {
        tmpflags = "WRAPEXCLUDE";
      }

      line = append_scope_to_line(line, &m, &maxlen, scope);
      line = append_enum_to_line(line, &m, &maxlen, enum_info);
    }
    else if (data->Items[i].Type == VTK_TYPEDEF_INFO)
    {
      line = append_scope_to_line(line, &m, &maxlen, scope);
      line = append_typedef_to_line(line, &m, &maxlen,
        data->Typedefs[data->Items[i].Index]);
    }
    else if (data->Items[i].Type != VTK_NAMESPACE_INFO)
    {
      /* unhandled file element */
      continue;
    }

    if (data->Items[i].Type != VTK_NAMESPACE_INFO)
    {
      /* append filename and flags */
      line = append_trailer(
        line, &m, &maxlen, header_file, module_name, tmpflags);

      /* append the line to the file */
      lines = append_unique_line(lines, line, np);
    }

    /* for classes, add all typed defined within the class */
    if ((data->Items[i].Type == VTK_CLASS_INFO ||
         data->Items[i].Type == VTK_STRUCT_INFO) &&
        data->Classes[data->Items[i].Index]->Name)
    {
      lines = append_class_contents(lines, np,
        data->Classes[data->Items[i].Index],
        scope, header_file, module_name);
    }

    /* for namespaces, add all types in the namespace */
    if (data->Items[i].Type == VTK_NAMESPACE_INFO &&
        data->Namespaces[data->Items[i].Index]->Name)
    {
      lines = append_namespace_contents(lines, np,
        data->Namespaces[data->Items[i].Index],
        scope, header_file, module_name, "WRAPEXCLUDE");
    }
  }

  free(line);

  if (new_scope != 0)
  {
    free(new_scope);
  }

  return lines;
}

/**
 * Read a header file with vtkParse.tab.c
 *
 * If "lines" is provided, the file contents
 * will be appended to them.
 */
static char **vtkWrapHierarchy_ParseHeaderFile(
  FILE *fp, const char *filename, const char *module_name,
  const char *flags, char **lines)
{
  FileInfo *data;
  const char *header_file;
  size_t k, n;

  /* start with just a single output line and grow from there */
  if (lines == NULL)
  {
    lines = (char **)malloc(sizeof(char *));
    lines[0] = NULL;
  }

  /* the "concrete" flag doesn't matter, just set to zero */
  data = vtkParse_ParseFile(filename, fp, stderr);

  if (!data)
  {
    free(lines);
    return 0;
  }

  /* find the last line in "lines" */
  n = 0;
  while (lines[n] != NULL)
  {
    n++;
  }

  k = strlen(data->FileName) - 1;
  while (k > 0 && data->FileName[k-1] != '/' && data->FileName[k-1] != '\\')
  {
    k--;
  }
  header_file = &data->FileName[k];

  /* append the file contents to the output */
  lines = append_namespace_contents(
    lines, &n, data->Contents, 0,
    header_file, module_name, flags);

  vtkParse_Free(data);

  return lines;
}

/**
 * Read a hierarchy file into "lines" without duplicating lines
 */
static char **vtkWrapHierarchy_ReadHierarchyFile(FILE *fp, char **lines)
{
  char *line;
  size_t maxlen = 15;
  size_t i, n;

  line = (char *)malloc(maxlen);

  if (lines == NULL)
  {
    lines = (char **)malloc(sizeof(char *));
    lines[0] = NULL;
  }

  while (fgets(line, (int)maxlen, fp))
  {
    n = strlen(line);

    /* if buffer not long enough, increase it */
    while (n == maxlen-1 && line[n-1] != '\n' && !feof(fp))
    {
      char *oldline = line;
      maxlen *= 2;
      line = (char *)realloc(line, maxlen);
      if (!line)
      {
        free(oldline);
        return NULL;
      }
      if (!fgets(&line[n], (int)(maxlen-n), fp)) { break; }
      n += strlen(&line[n]);
    }

    while (n > 0 && isspace(line[n-1]))
    {
      n--;
    }
    line[n] = '\0';

    if (line[0] == '\0')
    {
      continue;
    }

    for (i = 0; lines[i] != NULL; i++)
    {
      if (strcmp(line, lines[i]) == 0)
      {
        break;
      }
    }

    if (lines[i] == NULL)
    {
      /* allocate more memory if n+1 is a power of two */
      if (((i+1) & i) == 0)
      {
        lines = (char **)realloc(lines, (i+1)*2*sizeof(char *));
      }

      lines[i] = (char *)malloc(n+1);
      strcpy(lines[i], line);
      lines[i+1] = NULL;
    }
  }

  free(line);

  if (!feof(fp))
  {
    free(lines);
    return 0;
  }

  return lines;
}

/**
 * Compare a file to "lines", return 0 if they are different
 */
static int vtkWrapHierarchy_CompareHierarchyFile(FILE *fp, char *lines[])
{
  unsigned char *matched;
  char *line;
  size_t maxlen = 15;
  size_t i, n;

  line = (char *)malloc(maxlen);

  for (i = 0; lines[i] != NULL; i++) { ; };
  matched = (unsigned char *)malloc(i);
  memset(matched, 0, i);

  while (fgets(line, (int)maxlen, fp))
  {
    n = strlen(line);

    /* if buffer not long enough, increase it */
    while (n == maxlen-1 && line[n-1] != '\n' && !feof(fp))
    {
      char *oldline = line;
      maxlen *= 2;
      line = (char *)realloc(line, maxlen);
      if (!line)
      {
        free(oldline);
        free(matched);
        return 0;
      }
      if (!fgets(&line[n], (int)(maxlen-n), fp)) { break; }
      n += strlen(&line[n]);
    }

    while (n > 0 && isspace(line[n-1]))
    {
      n--;
    }
    line[n] = '\0';

    if (line[0] == '\0')
    {
      continue;
    }

    for (i = 0; lines[i] != NULL; i++)
    {
      if (strcmp(line, lines[i]) == 0)
      {
        break;
      }
    }

    if (lines[i] == NULL)
    {
      free(line);
      free(matched);
      return 0;
    }

    matched[i] = 1;
  }

  for (i = 0; lines[i] != NULL; i++)
  {
    if (matched[i] == 0)
    {
      free(line);
      free(matched);
      return 0;
    }
  }

  free(line);
  free(matched);

  if (!feof(fp))
  {
    return 0;
  }

  return 1;
}

/**
 * Write "lines" to a hierarchy file
 */
static int vtkWrapHierarchy_WriteHierarchyFile(FILE *fp, char *lines[])
{
  size_t i;

  for (i = 0; lines[i] != NULL; i++)
  {
    if (fprintf(fp, "%s\n", lines[i]) < 0)
    {
      return 0;
    }
  }

  return 1;
}

/**
 * Try to parse a header file, print error and exit if fail
 */
static char **vtkWrapHierarchy_TryParseHeaderFile(
  const char *file_name, const char *module_name,
  const char *flags, char **lines)
{
  FILE *input_file;

  input_file = fopen(file_name, "r");

  if (!input_file)
  {
    fprintf(stderr, "vtkWrapHierarchy: couldn't open file %s\n",
            file_name);
    exit(1);
  }

  lines = vtkWrapHierarchy_ParseHeaderFile(
                 input_file, file_name, module_name, flags, lines);

  if (!lines)
  {
    fclose(input_file);
    exit(1);
  }
  fclose(input_file);

  return lines;
}

/**
 * Try to read a file, print error and exit if fail
 */
static char **vtkWrapHierarchy_TryReadHierarchyFile(
  const char *file_name, char **lines)
{
  FILE *input_file;

  input_file = fopen(file_name, "r");
  if (!input_file)
  {
    fprintf(stderr, "vtkWrapHierarchy: couldn't open file %s\n",
            file_name);
    exit(1);
  }

  lines = vtkWrapHierarchy_ReadHierarchyFile(input_file, lines);
  if (!lines)
  {
    fclose(input_file);
    fprintf(stderr, "vtkWrapHierarchy: error reading file %s\n",
            file_name);
    exit(1);
  }
  fclose(input_file);

  return lines;
}

/**
 * Try to write a file, print error and exit if fail
 */
static int vtkWrapHierarchy_TryWriteHierarchyFile(
  const char *file_name, char *lines[])
{
  FILE *output_file;
  int matched = 0;

  output_file = fopen(file_name, "r");
  if (output_file && vtkWrapHierarchy_CompareHierarchyFile(output_file, lines))
  {
    matched = 1;
  }
  if (output_file)
  {
    fclose(output_file);
  }

  if (!matched)
  {
    int tries = 1;
    output_file = fopen(file_name, "w");
    while (!output_file && tries < 5)
    {
      /* There are two CMAKE_CUSTOM_COMMANDS for vtkWrapHierarchy,
       * make sure they do not collide. */
      tries++;
#ifdef _WIN32
      Sleep(1000);
#else
      sleep(1);
#endif
      output_file = fopen(file_name, "r+");
      if (output_file &&
          vtkWrapHierarchy_CompareHierarchyFile(output_file, lines))
      {
        /* if the contents match, no need to write it */
        fclose(output_file);
        return 0;
      }
      if (output_file)
      {
        /* close and open in order to truncate the file */
        fclose(output_file);
        output_file = fopen(file_name, "w");
      }
    }
    if (!output_file)
    {
      fprintf(stderr, "vtkWrapHierarchy: tried %i times to write %s\n",
              tries, file_name);
      exit(1);
    }
    if (!vtkWrapHierarchy_WriteHierarchyFile(output_file, lines))
    {
      fclose(output_file);
      fprintf(stderr, "vtkWrapHierarchy: error writing file %s\n",
              file_name);
      exit(1);
    }
    fclose(output_file);
  }

  return 0;
}

static int string_compare(const void *vp1, const void *vp2)
{
  return strcmp(*(const char **)vp1, *(const char **)vp2);
}

int main(int argc, char *argv[])
{
  OptionInfo *options;
  int i;
  size_t j, n;
  char **lines = 0;
  char **files = 0;
  char *flags;
  char *module_name;

  /* pre-define a macro to identify the language */
  vtkParse_DefineMacro("__VTK_WRAP_HIERARCHY__", 0);

  /* parse command-line options */
  StringCache *string_cache = vtkParse_MainMulti(argc, argv);
  options = vtkParse_GetCommandLineOptions();

  /* make sure than an output file was given on the command line */
  if (options->OutputFileName == NULL)
  {
    fprintf(stderr, "No output file was specified\n");
    exit(1);
  }

  /* read the data file */
  files = vtkWrapHierarchy_TryReadHierarchyFile(
    options->InputFileName, files);

  /* read in all the prior files */
  for (i = 1; i < options->NumberOfFiles; i++)
  {
    lines = vtkWrapHierarchy_TryReadHierarchyFile(
      options->Files[i], lines);
  }

  /* merge the files listed in the data file */
  for (i = 0; files[i] != NULL; i++)
  {
    /* look for semicolon that marks the module name */
    module_name = files[i];
    while(*module_name != ';' && *module_name != '\0') { module_name++; };
    if (*module_name == ';') { *module_name++ = '\0'; }

    /* look for semicolon that marks start of flags */
    flags = module_name;
    while(*flags != ';' && *flags != '\0') { flags++; };
    if (*flags == ';') { *flags++ = '\0'; }

    lines = vtkWrapHierarchy_TryParseHeaderFile(
      files[i], module_name, flags, lines);
  }

  /* sort the lines to ease lookups in the file */
  for (n = 0; lines[n]; n++) { ; };
  qsort(lines, n, sizeof(char *), &string_compare);

  /* write the file, if it has changed */
  vtkWrapHierarchy_TryWriteHierarchyFile(options->OutputFileName, lines);

  for (j = 0; j < n; j++)
  {
    free(lines[j]);
  }

  for(j = 0; files[j] != NULL; j++)
  {
    free(files[j]);
  }

  vtkParse_FreeStringCache(string_cache);
  free(string_cache);
  free(files);
  free(lines);
  return 0;
}
