/*
 * Copyright(C) 1999-2021 National Technology & Engineering Solutions
 * of Sandia, LLC (NTESS).  Under the terms of Contract DE-NA0003525 with
 * NTESS, the U.S. Government retains certain rights in this software.
 *
 * See packages/seacas/LICENSE for details
 */

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

#include "exodusII.h"

/*
//  Read and EXODUSII database and return a TECPLOT file
*/
void tec(int exoid, const char *filename)
{
  void teczone(int, int, int, char *, int, int, int *, int, double **, int, double **, FILE *);

  /*
   * FIRST, READ THE EXODUS DATA BASE
   */

  /*
   *  Open the output file, if we can
   */
  FILE *tecfile = fopen(filename, "w");
  if (tecfile == NULL) {
    printf("\nCannot open file %s for writing\n\n", filename);
    exit(1);
  }

  /*
   * Determine max name size used in database...
   */
  int name_size = ex_inquire_int(exoid, EX_INQ_DB_MAX_USED_NAME_LENGTH);
  ex_set_max_name_length(exoid, name_size);

  /*
   *  Read database size, get coordinates and connectivity
   */
  char title[MAX_LINE_LENGTH + 1];
  memset(title, 0, MAX_LINE_LENGTH + 1);
  int ndim, nnode, nelem, nblk, nnset, neset;
  ex_get_init(exoid, title, &ndim, &nnode, &nelem, &nblk, &nnset, &neset);

  double *x[3];
  x[0] = x[1] = x[2] = NULL;

  char *nameco[3];
  for (int i = 0; i < ndim; i++) {
    nameco[i] = (char *)malloc((name_size + 1) * sizeof(char));
    x[i]      = (double *)malloc(nnode * sizeof(double));
  }
  ex_get_coord_names(exoid, nameco);
  if (strlen(nameco[0]) == 0)
    strcpy(nameco[0], "X");
  if (strlen(nameco[1]) == 0)
    strcpy(nameco[1], "Y");
  if (ndim > 2)
    if (strlen(nameco[2]) == 0)
      strcpy(nameco[2], "Z");
  ex_get_coord(exoid, x[0], x[1], x[2]);

  int *  elem_id       = (int *)malloc(nblk * sizeof(int));
  int *  node_per_elem = (int *)malloc(nblk * sizeof(int));
  int *  elem_per_blk  = (int *)malloc(nblk * sizeof(int));
  int *  attr_per_blk  = (int *)malloc(nblk * sizeof(int));
  char **elem_type     = (char **)malloc(nblk * sizeof(char *));
  int ** icon          = (int **)malloc(nblk * sizeof(int *));
  for (int i = 0; i < nblk; i++)
    elem_type[i] = (char *)malloc((name_size + 1) * sizeof(char));
  ex_get_ids(exoid, EX_ELEM_BLOCK, elem_id);
  for (int i = 0; i < nblk; i++) {
    ex_get_block(exoid, EX_ELEM_BLOCK, elem_id[i], elem_type[i], &elem_per_blk[i],
                 &node_per_elem[i], NULL, NULL, &attr_per_blk[i]);

    icon[i] = (int *)malloc(elem_per_blk[i] * node_per_elem[i] * sizeof(int));
    ex_get_conn(exoid, EX_ELEM_BLOCK, elem_id[i], icon[i], NULL, NULL);
  }

  /*
   *  Read time step information
   */
  double *time  = NULL;
  int     ntime = ex_inquire_int(exoid, EX_INQ_TIME);
  if (ntime > 0) {
    time = (double *)malloc(ntime * sizeof(double));
    ex_get_all_times(exoid, time);
  }

  /*
   *  Read number of nodal variables and save space
   */
  int nvar = 0;
  ex_get_variable_param(exoid, EX_NODAL, &nvar);

  char **  varnames = NULL;
  double **q        = NULL;
  if (nvar > 0) {
    varnames = (char **)malloc(nvar * sizeof(char *));
    q        = (double **)malloc(nvar * sizeof(double *));
    for (int i = 0; i < nvar; i++) {
      varnames[i] = (char *)malloc((name_size + 1) * sizeof(char));
      q[i]        = (double *)malloc(nnode * sizeof(double));
    }
    ex_get_variable_names(exoid, EX_NODAL, nvar, varnames);
  }

  /* /////////////////////////////////////////////////////////////////////
  //  PROMPT USER FOR INFO AND WRITE TECPLOT FILE
  /////////////////////////////////////////////////////////////////////
  */

  /*
   *  Write the TECPLOT header information
   */

  assert(strlen(title) < (MAX_LINE_LENGTH + 1));
  fprintf(tecfile, "TITLE = \"%s\"\n", title);
  fprintf(tecfile, "VARIABLES = ");
  for (int i = 0; i < ndim; i++) {
    fprintf(tecfile, "\"%s\"", nameco[i]);
    if (i < (ndim - 1))
      fprintf(tecfile, ", ");
  }
  if (nvar == 0)
    fprintf(tecfile, "\n");
  else
    fprintf(tecfile, ",\n            ");

  int idum = 0;
  for (int i = 0; i < nvar; i++) {
    idum += strlen(varnames[i]);
    assert(idum < 1022);

    fprintf(tecfile, "\"%s\"", varnames[i]);
    if (i < (nvar - 1)) {
      if ((i + 1) % 4 == 0) {
        idum = 0;
        fprintf(tecfile, ",\n            ");
      }
      else
        fprintf(tecfile, ", ");
    }
  }
  fprintf(tecfile, "\n");

  /*
   *  Select a time step
   */
  int izone = 0;
  int itime = 0;
  if (ntime == 0) {
    printf("\nNo solution variables available, saving mesh only\n\n");
    izone = 1;
  }
  else {
    printf("\nTime step information:\n\n");
    for (int i = 0; i < ntime; i++)
      printf("   Time step %5d, time = %e\n", i + 1, time[i]);
    do {
      printf("\nSelect time step number to save,\n");
      printf("  or 0 for zone animation of all time steps: ");
      scanf("%d", &itime);
      printf("\n");
    } while (itime < 0 || itime > ntime);
    printf("\n");
    if (itime == 0)
      izone = 0;
    else
      izone = 1;
  }

  /*
   *  Write time steps
   */

  if (izone == 0) {

    /*
     *  Collapse the zones into one
     */

    /*
     *  Make sure we are using all the same element types
     *  Create one master connectivity array
     */
    for (int i = 1; i < nblk; i++)
      if (strcmp(elem_type[0], elem_type[i]) != 0) {
        printf("\nCannot create zone animation because\n");
        ;
        printf("\n  there are multiple element types.");
        exit(1);
      }
    int *ic = (int *)malloc(nelem * node_per_elem[0] * sizeof(int));
    int  k  = 0;
    for (int j = 0; j < nblk; j++)
      for (int i = 0; i < node_per_elem[j] * elem_per_blk[j]; i++)
        ic[k++] = icon[j][i];
    assert(k == nelem * node_per_elem[0]);

    if (itime == 0) {
      for (int j = 0; j < ntime; j++) {
        for (int i = 0; i < nvar; i++) {
          ex_get_var(exoid, j + 1, EX_NODAL, i + 1, 1, nnode, q[i]);
        }

        int i = 0;
        teczone(1, nnode, j + 1, elem_type[i], node_per_elem[i], nelem, ic, ndim, x, nvar, q,
                tecfile);
      }
      printf("\n");
    }

    free(ic);
  }
  else if (izone == 1) {

    /*
      ||  Write out each zone individually
    */
    for (int i = 0; i < nvar; i++)
      ex_get_var(exoid, itime, EX_NODAL, i + 1, 1, nnode, q[i]);

    for (int i = 0; i < nblk; i++)
      teczone(nblk, nnode, elem_id[i], elem_type[i], node_per_elem[i], elem_per_blk[i], icon[i],
              ndim, x, nvar, q, tecfile);
    printf("\n");
  }

  /* /////////////////////////////////////////////////////////////////////
  //  CLEAN UP
  /////////////////////////////////////////////////////////////////////
  */

  fclose(tecfile);

  /*
   *  Free up allocated memory
   */
  for (int i = 0; i < ndim; i++) {
    free(nameco[i]);
    free(x[i]);
  }
  free(elem_id);
  free(node_per_elem);
  free(elem_per_blk);
  free(attr_per_blk);
  if (elem_type != NULL) {
    for (int i = 0; i < nblk; i++) {
      free(elem_type[i]);
    }
    free(elem_type);
  }
  if (icon != NULL) {
    for (int i = 0; i < nblk; i++) {
      free(icon[i]);
    }
    free(icon);
  }
  if (nvar > 0) {
    if (varnames != NULL) {
      for (int i = 0; i < nvar; i++) {
        free(varnames[i]);
      }
      free(varnames);
    }
    if (q != NULL) {
      for (int i = 0; i < nvar; i++) {
        free(q[i]);
      }
      free(q);
    }
  }
}

/*
 *   Write TECPLOT zonal information
 */
void teczone(int nblk, int nnode, int elem_id, char *elem_type, int node_per_elem, int elem_per_blk,
             int *icon, int ndim, double **x, int nvar, double **q, FILE *tecfile)
{
  int *    ic = NULL;
  double * xx[3];
  double **qq    = NULL;
  int *    isort = NULL;
  int      inode;

  void internal_heapsort(int *, int);
  int  hunt(int, int, int *, int *);

  int ifac = 1;
  if (strcmp(elem_type, "SHELL") == 0) {
    ifac = 2;
  }
  if (strcmp(elem_type, "BAR") == 0) {
    ifac = 2;
  }
  if (strcmp(elem_type, "TRUSS") == 0) {
    ifac = 4;
  }

#ifdef DEBUG
  printf("...in teczone\n");
  printf("......elem_type %s\n", elem_type);
  printf("......elem_per_blk %d\n", elem_per_blk);
#endif
  if (nblk > 1) {
    /*
     *  Create local block connectivity
     */
    ic    = (int *)malloc(ifac * node_per_elem * elem_per_blk * sizeof(int));
    isort = (int *)malloc(node_per_elem * elem_per_blk * sizeof(int));

    for (int j = 0; j < node_per_elem * elem_per_blk; j++)
      isort[j] = icon[j];

#ifdef DEBUG
    printf("...sorting global connectivity\n");
    printf("......total nodes %d\n", elem_per_blk * node_per_elem);
#endif
    /*
     *  Sort nodes in this element block
     */

    internal_heapsort(isort, node_per_elem * elem_per_blk);

#ifdef DEBUG
    printf("...compressing global connectivity\n");
#endif
    /*
     *  Compress node list
     */

    inode = 0;
    for (int j = 0; j < node_per_elem * elem_per_blk - 1; j++)
      if (isort[j] != isort[j + 1])
        isort[++inode] = isort[j + 1];
    inode++;

#ifdef DEBUG
    printf("......found %d unique nodes\n", inode);
    printf("...sorting local connectivity\n");
#endif
    /*
     *  Sort local connectivity
     */
    int i = (inode + 1) / 2;
    for (int k = 0; k < elem_per_blk; k++) {
      for (int j = 0; j < node_per_elem; j++) {
        int n  = k * node_per_elem + j;
        int nn = k * ifac * node_per_elem + j;

        ic[nn] = hunt(icon[n], inode, isort, &i);
      }
      if (ifac == 2)
        for (int j = 0; j < node_per_elem; j++) {
          int nn                 = k * ifac * node_per_elem + j;
          ic[nn + node_per_elem] = ic[nn];
        }
      else if (ifac == 4)
        for (int j = 0; j < node_per_elem; j++) {
          int nn                     = k * ifac * node_per_elem + j;
          ic[nn + node_per_elem]     = ic[nn + node_per_elem - 1 - j];
          ic[nn + 2 * node_per_elem] = ic[nn];
          ic[nn + 3 * node_per_elem] = ic[nn + node_per_elem - 1 - j];
        }
    }

#ifdef DEBUG
    printf("...copy local data\n");
#endif
    /*
     *  Copy local data
     */
    for (int i = 0; i < ndim; i++)
      xx[i] = (double *)malloc(inode * sizeof(double));

    for (int j = 0; j < ndim; j++)
      for (int i = 0; i < inode; i++)
        xx[j][i] = x[j][isort[i] - 1];

    if (nvar > 0)
      qq = (double **)malloc(nvar * sizeof(double *));
    for (int i = 0; i < nvar; i++)
      qq[i] = (double *)malloc(inode * sizeof(double));

    for (int j = 0; j < nvar; j++)
      for (int i = 0; i < inode; i++)
        qq[j][i] = q[j][isort[i] - 1];
  }
  else {
    /*
     *  Copy local block connectivity
     */

    if (ifac > 1) {
      printf("\nCannot perform zone animation for degenerate elements\n\n");
      exit(1);
    }

    inode = nnode;
    ic    = icon;

    for (int i = 0; i < ndim; i++)
      xx[i] = x[i];

    if (nvar > 0)
      qq = (double **)malloc(nvar * sizeof(double *));
    for (int i = 0; i < nvar; i++)
      qq[i] = q[i];
  }

#ifdef DEBUG
  printf("...writing zone data\n");
#endif
  /*
   *  Write zone data
   */
  printf("    Writing Zone %4d, %8s, %6d elements, %6d nodes\n", elem_id, elem_type, elem_per_blk,
         inode);
  char zname[80];
  char eltype[16];
  sprintf(zname, "Zone %s_%d", elem_type, elem_id);

  if (ndim == 3 && (node_per_elem == 8 || (node_per_elem == 4 && ifac == 2) ||
                    (node_per_elem == 2 && ifac == 4)))
    sprintf(eltype, "BRICK");
  else if (ndim == 3 && node_per_elem == 4)
    sprintf(eltype, "TETRAHEDRON");
  else if (ndim == 2 && (node_per_elem == 4 || (node_per_elem == 2 && ifac == 2)))
    sprintf(eltype, "QUADRILATERAL");
  else if (ndim == 2 && node_per_elem == 3)
    sprintf(eltype, "TRIANGLE");
  else {
    printf("\nBad element type found in teczone\n");
    printf("   Dimensions = %d\n", ndim);
    printf("   Nodes      = %d\n", node_per_elem);
    exit(1);
  }

  fprintf(tecfile, "ZONE T=\"%s\", F=FEBLOCK, ET=%s,\n", zname, eltype);
  fprintf(tecfile, "     N=%d, E=%d\n", inode, elem_per_blk);

#define CHUNK 8

  for (int j = 0; j < ndim; j++) {
    for (int i = 0; i < inode / CHUNK; i++) {
      for (int k = 0; k < CHUNK; k++)
        fprintf(tecfile, "%.16f ", xx[j][i * CHUNK + k]);
      fprintf(tecfile, "\n");
    }
    if (inode % CHUNK != 0) {
      for (int i = CHUNK * (inode / CHUNK); i < inode; i++)
        fprintf(tecfile, "%.16f ", xx[j][i]);
      fprintf(tecfile, "\n");
    }
  }

  for (int j = 0; j < nvar; j++) {
    for (int i = 0; i < inode / CHUNK; i++) {
      for (int k = 0; k < CHUNK; k++)
        fprintf(tecfile, "%.16f ", qq[j][i * CHUNK + k]);
      fprintf(tecfile, "\n");
    }
    if (inode % CHUNK != 0) {
      for (int i = CHUNK * (inode / CHUNK); i < inode; i++)
        fprintf(tecfile, "%.16f ", qq[j][i]);
      fprintf(tecfile, "\n");
    }
  }

  for (int i = 0; i < elem_per_blk; i++) {
    for (int j = 0; j < ifac * node_per_elem; j++) {
      int k = ic[i * ifac * node_per_elem + j];
      fprintf(tecfile, "%d ", k);
    }
    fprintf(tecfile, "\n");
  }

#ifdef DEBUG
  printf("...freeing allocated space\n");
#endif
  if (nblk > 1) {
    free(ic);
    free(isort);
    for (int i = 0; i < ndim; i++)
      free(xx[i]);
    if (qq != NULL) {
      for (int i = 0; i < nvar; i++)
        free(qq[i]);
      if (nvar > 0)
        free(qq);
    }
  }
  else {
    if (nvar > 0)
      free(qq);
  }
}

void siftDown(int *a, int start, int count);

#define SWAP(r, s)                                                                                 \
  do {                                                                                             \
    int t = r;                                                                                     \
    r     = s;                                                                                     \
    s     = t;                                                                                     \
  } while (0)

void internal_heapsort(int *a, int count)
{
  /* heapify */
  for (int start = (count - 2) / 2; start >= 0; start--) {
    siftDown(a, start, count);
  }

  for (int end = count - 1; end > 0; end--) {
    SWAP(a[end], a[0]);
    siftDown(a, 0, end);
  }
}

void siftDown(int *a, int start, int end)
{
  int root = start;

  while (root * 2 + 1 < end) {
    int child = 2 * root + 1;
    if ((child + 1 < end) && (a[child] < a[child + 1])) {
      child += 1;
    }
    if (a[root] < a[child]) {
      SWAP(a[child], a[root]);
      root = child;
    }
    else
      return;
  }
}

/*
 *   Bisection search with binary hunt,
 *      from Numerical Recipes
 */
int hunt(int node, int n, int *key, int *iguess)
{
  if (node == key[*iguess - 1])
    return *iguess;
  int i = *iguess;

  int inc = 1;
  int j   = 0;

  if (node > key[i - 1]) { /*  hunt up  */

    if (i == n) {
      printf("\nError in HUNT: node is outside range of table\n");
      printf("      requested node = %d\n", node);
      printf("      last in table  = %d\n", key[n - 1]);
      exit(1);
    }

    j = i + 1;
    while (node > key[j - 1]) {
      i = j;
      inc += inc;
      j = i + inc;
      if (j > n) {
        j = n;
        break;
      }
    }
    if (node == key[j - 1]) {
      *iguess = j;
      return j;
    }
  }
  else if (node < key[i - 1]) { /*  hunt down  */

    if (i == 1) {
      printf("\nError in HUNT: node is outside range of table\n");
      printf("      requested node  = %d\n", node);
      printf("      first in table  = %d\n", key[0]);
      exit(1);
    }

    j = i;
    i -= 1;
    while (node < key[i - 1]) {
      j = i;
      inc += inc;
      i = j - inc;
      if (i < 1) {
        i = 1;
        break;
      }
    }
    if (node == key[i - 1]) {
      *iguess = i;
      return i;
    }
  }

  int k = (i + j) / 2;
  while (node != key[k - 1]) {
    if (key[k - 1] > node) {
      j = k;
      k = (i + j) / 2;
    }
    else {
      i = k;
      k = (i + j + 1) / 2;
    }
  }

  *iguess = k;
  return k;
}
