  #include <stdio.h>
  #include <stdlib.h>
  #include <string.h>
  #include <math.h>
  #include <ctype.h>

  static void recordvalue(char *p);
  static void header(void);

  char *names[] = { "Interest", "Months", "Principal", "Payment" };

  /* How many values you got.  Given any 3, program can calculate the 4th */
  char ngot;

  /* The values, or a NULL pointer if no value gotten. */
  char *values[4];

  static short lineno = 0;
  static char *descrip = 0;
  static short payment = 0;
  static double principal = 0, payamt = 0, rate = 0, months = 0;

  static int from_hex(char c) {
    if (c >= '0' && c <= '9') return c - '0';
    if (c >= 'a' && c <= 'f') return c - 'a' + 10;
    if (c >= 'A' && c <= 'F') return c - 'A' + 10;
    return -1;
  }

  static void url_decode(char *s) {
    char *src = s;
    char *dst = s;
    while (*src) {
      if (*src == '+') {
        *dst++ = ' ';
        src++;
      } else if (*src == '%' && isxdigit((unsigned char)src[1]) && isxdigit((unsigned char)src[2])) {
        int hi = from_hex(src[1]);
        int lo = from_hex(src[2]);
        *dst++ = (char)((hi << 4) | lo);
        src += 3;
      } else {
        *dst++ = *src++;
      }
    }
    *dst = '\0';
  }

static char *read_input(void) {
    char *cl = getenv("CONTENT_LENGTH");
    char *qs = getenv("QUERY_STRING");
    int len = cl ? atoi(cl) : 0;

    if (len > 0) {
      char *buf = malloc(len + 1);
      size_t total = 0;
      while (total < (size_t)len) {
	size_t n = fread(buf + total, 1, (size_t)len - total, stdin);
	if (n == 0) break; /* EOF or error */
	total += n;
      }
      buf[total] = '\0';
      return buf;
    }

    if (qs && *qs) {
      return strdup(qs);
    }

    /* CLI/piped input fallback */
    {
      char line[4096];
      if (fgets(line, sizeof(line), stdin)) {
        line[strcspn(line, "\r\n")] = '\0';
        return strdup(line);
      }
    }

    return strdup("");
  }



  int main(int argc, char **argv) {
    int i;
    char *input;
    char *p, *lastp;
    double endbal, intpay, prinpay, begbal;
    double testpay;
    double totpay = 0, totint = 0, totprin = 0;
    short day, month, startmonth = 1, year = 0;
    char *reply;
    char unknowns = 0;
    char *form = "%14.2f";
    char *end;

    printf("Content-type: text/plain\n\n");

    input = read_input();
    if (!input) {
      printf("Error: unable to read input.\n");
      return 1;
    }

    lastp = input;
    for (p = input;; p++) {
      if (*p == '&') {
        *p = 0;
        recordvalue(lastp);
        lastp = p + 1;
      } else if (*p == '\0') {
        recordvalue(lastp);
        break;
      }
    }


  #define RATE 0
  #define MONTHS 1
  #define PRINCIPAL 2
  #define PAYAMT 3

    if (values[RATE]) {
      rate = strtod(values[0], &end);
    }
    if (values[MONTHS]) {
      months = strtod(values[1], &end);
    }
    if (values[PRINCIPAL]) {
      principal = strtod(values[2], &end);
    }
    if (values[PAYAMT]) {
      payamt = strtod(values[3], &end);
    }

    /* I couldn't find a closed formula for the rate, so
     * this uses binary search on the payment amount, in
     * terms of a guessed rate.
     */
    if (values[RATE] == NULL) {
      double limit[2];
      unsigned char which;

      /* You cannot pay off a loan if the total payments do
       * not at least equal the principal.
       */
      if (payamt * months < principal) {
        printf("Error: the monthly payment times the\n");
        printf("number of months is less than the principal.\n");
        printf("%.2f * %.2f = %.2f\n", payamt, months, payamt * months);
        exit(0);
      }

      if (unknowns++) goto toomany;
      limit[1] = 5;        /* 500% per year max rate */
      limit[0] = 0;
      for (;;) {
        rate = (limit[1] + limit[0]) / 2;
        testpay = (principal * rate / 12) /
            (1 - 1 / pow(1 + rate / 12, months));

        which = (payamt < testpay);

        if (limit[0] >= limit[1] || limit[which] == rate) {
          rate = floor(rate * 1e+8 + .5) / 1e+8;
          break;
        }

        limit[which] = rate;
      }
    } else rate /= 100;

    if (values[MONTHS] == NULL) {
      unknowns++;
      months = log(payamt/(payamt-principal*rate/12))/log(1+rate/12);
      months = -floor(-months);
      printf("Number of payments = %u\n", (unsigned)months);
    }

    if (values[PRINCIPAL] == NULL) {
      if (unknowns++) goto toomany;
      principal = (payamt - payamt / pow(1 + rate / 12, months)) /
          (rate / 12);
      principal = floor(principal * 100)/100;
      printf("Amount borrowed = %.2f\n", principal);
    }

    if (values[PAYAMT] == NULL) {
      if (unknowns++) goto toomany;
      payamt = (principal * rate / 12) /
          (1 - 1 / pow(1 + rate / 12, months));
      payamt = floor(payamt * 100 + 0.5) / 100;
      printf("Payment Amount = %.2f\n", payamt);
    }

    lineno = 999;
    header();

    endbal = principal;     /* Principal balance */

  repeat:
  for (month = startmonth; month <= 12; month++) {
      payment++;
      begbal = endbal;		/* Beginning bal = last pd ending bal */
      intpay = begbal * rate / 12;
      intpay = floor(intpay * 100.0 + .5) / 100.0;	/* interest for month */

      if (payamt > begbal + intpay && payment == months && begbal > 0)
	  payamt = begbal + intpay;	/* payamt too big? */
      prinpay = payamt - intpay;	/* Calculate principal payamt */
      endbal=begbal-prinpay;		/* Calculate new principal balance */
      if (-.005 < endbal && endbal < .005)  endbal = 0;
      totpay	+= payamt;
      totprin += prinpay;
      totint	+= intpay;
      printf("%+02u-%+02u  ", year, month);
      printf("%14.2f%14.2f%14.2f%14.2f%14.2f\n",
	     begbal,	 payamt,  prinpay,   intpay,   endbal);
      if (payment == months) goto done;
      }

    year++;
    startmonth = 1;

  done:
    for (i = 0; i < 80; i++)
      putchar('=');
    putchar('\n');
    printf("Totals               %14.2f%14.2f%14.2f\n\n\n",
        totpay, totprin, totint);

    if (payment != months) {
      header();
      goto repeat;
    }
    sleep(2);
    fflush(stdout);
    exit(0);

  toomany:
    printf("Too many unknowns.  You must supply at least 3 values.\n");
  }

  /* Take, for example, a string of the form "Interest=9" or whatever and
   * save the "9" in values[0].
   */
  static void recordvalue(char *p) {
    int i;
    char *eq;
    char *v;

    if (!p || !*p) return;

    eq = strchr(p, '=');
    if (!eq) return;

    for (i = 0; i < (int)(sizeof(names) / sizeof(names[0])); i++) {
      int l = (int)strlen(names[i]);
      if (!memcmp(names[i], p, l) && p + l == eq) {  /* parameter name match */
        v = eq + 1;                                  /* isolate value */
        if (v[0]) {
          if (values[i]) {
            printf("got 2 values for %s\n", names[i]);
            return;
          }
          url_decode(v);
          values[i] = v;
          ngot++;
        }
      }
    }
  }

  /* Output the header */
  static void header(void) {
    int i;
    lineno += 15;
    if (lineno <= 40) return;

    printf("Loan amount: %12.2f    Payment: %11.2f\n", principal, payamt);
    printf("Interest rate: %11.6f    Term:    %5f\n\n", rate * 100, months);

    printf("Pmt Date      Beg Bal       Payment      Prin Red      Interest       End Bal\n");
    lineno = 0;
    for (i = 0; i < 80; i++)
       putchar('-');
    printf("\n");
  }

