// gcc -Wall -o ccollatz ccollatz.c
// ./ccollatz 837799
// ./ccollatz 8400511
#include <stdio.h>
#include <stdlib.h>
#include <setjmp.h>

// https://www.reddit.com/r/C_Programming/comments/1eoc0v3/does_any_c_compiler_other_than_gcc_support_nested/

// See the accompanying collatz.imp file for an example of a language where
// nested procedures and mutual recursion are a normal part of the language.


// Although not frequently used in C, nested subroutines were once common
// in early programming languages, especially those in the Algol family.
// Translations of that old source code into readable and maintainable C read
// much better if use is made of gcc's extension to support nested subroutines.

// clang also has a related extension but in clang's case, the lambda-expression
// anonymous subroutines, inspired by Apple's blocks extension, have a major
// limitation - they cannot support mutual recursion because there is no
// syntax to allow forward references to the anonymous blocks that take the
// place of nested subroutines.

// This program demonstrates mutual recursion of a nested subroutine.

// If anyone can show me this code working on some compiler other than gcc,
// and without restructuring to avoid the use of nested procedures, while
// maintaining the mutual-recursion, I would very much like to know which
// compiler you used and what changes to the syntax if any were necessary.
// (you can email me at gtoal@gtoal.com)

// Obviously this extremely artificial example could be easily restructured to avoid
// this problem, but the real life programs which make use of this are not so
// trivially fixed, but unfortunately they are too large to be used as an example here.

// The example also demonstrates the use of variables from parent scopes (although
// again, very artificially and avoidably).  By the way there is no check here for
// overflow, which can easily occur.  The old Algol-style languages would routinely
// have caught overflows, array bounds exceeded, unassigned variables, etc., plus
// would have given a backtrace showing the variables in each stack frame incarnation!
// I've thrown in an exception generation and trap using setjmp/longjmp to simulate
// the old-school behaviour.

// Hopefully this example can inspire writers of C compilers other than gcc to add
// gcc's nested subroutine extension to their compilers - preferably using the same
// syntax as gcc.

unsigned long odd1 = 1UL, odd2 = 2UL;
static const unsigned long final_value = 0UL, MAX_FACTOR = (((unsigned long)-1L)-1UL)/3UL;
int main(int argc, char **argv) {
  static const unsigned long terminating_value = 1UL;

  void show_collatz_length(unsigned long i, unsigned long *max) {
    // Returns 0 for the length if there was an integer overflow.
    jmp_buf errbuf;
    unsigned long base_value = final_value + 1UL;
    auto int odd_collatz(unsigned long i); // mutual recursion requires a forward declaration!

    int even_collatz(unsigned long i) {
      if (i < terminating_value) return final_value;
      if (i > *max) *max = i;
      if (i&odd2) { // 'optimization' :-)
        return base_value+odd_collatz(i>>1UL); //  (really just an excuse to justify this use of mutual recursion)
      } else {
        return base_value+even_collatz(i>>1UL);
      }
    }

    int odd_collatz(unsigned long i) {
      if (i <= terminating_value) return final_value;
      if (i > *max) *max = i;
      if (i > MAX_FACTOR) longjmp(errbuf, 1); // next call would cause integer overflow...
      return base_value+even_collatz((i<<1)+i+1UL);
    }

    fprintf(stdout, "The Collatz sequence for %lu takes ", i);
    if (setjmp(errbuf)) {
      fprintf(stdout, "an unknown number of steps (we hit an integer overflow)\n");
      return;
    }

    fprintf(stdout, "%d steps", i&odd1 ? odd_collatz(i) : even_collatz(i));
    fprintf(stdout, " and the largest value reached was %lu\n", *max);
  }

  unsigned long highest;

  if (argc != 2) exit(EXIT_FAILURE);

  if ((unsigned long)atol(argv[1]) < 0) {
    fprintf(stderr, "Parameter must be a positive integer.\n");
    exit(1);
  }
  
  highest = 0UL; show_collatz_length((unsigned long)atol(argv[1])-1UL, &highest);
  highest = 0UL; show_collatz_length((unsigned long)atol(argv[1]),     &highest);
  highest = 0UL; show_collatz_length((unsigned long)atol(argv[1])+1UL, &highest);

  exit(EXIT_SUCCESS);
  return(EXIT_FAILURE);
}
