/////////////////////////////////////////////////////////////////////////////
// Handle attaching files to outgoing messages
// Bobbi July 2020
/////////////////////////////////////////////////////////////////////////////

#include <conio.h>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include "email_common.h"

#define BELL      0x07
#define BACKSPACE 0x08
#define RETURN    0x0d
#define NORMAL    0x0e
#define INVERSE   0x0f
#define DELETE    0x7f

#define NETBUFSZ  1500
#define LINEBUFSZ 1000         // According to RFC2822 Section 2.1.1 (998+CRLF)
#define READSZ    1024         // Must be less than NETBUFSZ to fit in buf[]

static unsigned char buf[NETBUFSZ+1];    // One extra byte for null terminator
static char          linebuf[LINEBUFSZ];
static char          userentry[80];
static uint8_t       quit_to_email;   // If 1, launch EMAIL.SYSTEM on quit
static char          filename[80];

#define ERR_NONFATAL 0
#define ERR_FATAL    1

/*
 * Show error messages
 */
void error(uint8_t fatal, const char *fmt, ...) {
  va_list v;
  if (fatal) {
    videomode(VIDEOMODE_80COL);
    clrscr();
    printf("\n\n%cFATAL ERROR:%c\n\n", INVERSE, NORMAL);
    va_start(v, fmt);
    vprintf(fmt, v);
    va_end(v);
    printf("\n\n\n\n[Press Any Key To Quit]");
    cgetc();
    exit(1);
  } else {
    putchar('\r');
    va_start(v, fmt);
    vprintf(fmt, v);
    va_end(v);
    printf(" - [Press Any Key]");
    cgetc();
    putchar('\r');
  }
}

/*
 * Spinner while encoding attachments
 */
void spinner(uint32_t sz, uint8_t final) {
  static char chars[] = "|/-\\";
  static char buf[10] = "";
  static uint8_t i = 0;
  uint8_t j;
  for (j = 0; j < strlen(buf); ++j)
    putchar(BACKSPACE);
  if (final) {
    sprintf(buf, " [%lu]\n", sz);
    printf("%s", buf);
    strcpy(buf, "");
  }
  else {
    sprintf(buf, "%c %lu", chars[(i++) % 4], sz);
    printf("%s", buf);
  }
}

/*
 * Read parms from EMAIL.CFG
 */
void readconfigfile(void) {
  FILE *fp = fopen("EMAIL.CFG", "r");
  if (!fp)
    error(ERR_FATAL, "Can't open config file EMAIL.CFG");
  fscanf(fp, "%s", cfg_server);
  fscanf(fp, "%s", cfg_user);
  fscanf(fp, "%s", cfg_pass);
  fscanf(fp, "%s", cfg_pop_delete);
  fscanf(fp, "%s", cfg_smtp_server);
  fscanf(fp, "%s", cfg_smtp_domain);
  fscanf(fp, "%s", cfg_instdir);
  fscanf(fp, "%s", cfg_emaildir);
  fscanf(fp, "%s", cfg_emailaddr);
  fclose(fp);
}

/*
 * Read a text file a line at a time
 * Returns number of chars in the line, or -1 if EOF.
 * Expects Apple ][ style line endings (CR) and does no conversion
 * fp - file to read from
 * writep - Pointer to buffer into which line will be written
 */
int16_t get_line(FILE *fp, char *writep) {
  static uint16_t rd = 0; // Read
  static uint16_t end = 0; // End of valid data in buf
  uint16_t i = 0;
  while (1) {
    if (rd == end) {
      end = fread(buf, 1, READSZ, fp);
      rd = 0;
    }
    if (end == 0)
      return -1; // EOF
    writep[i++] = buf[rd++];
    if (writep[i - 1] == '\r') {
      writep[i] = '\0';
      return i;
    }
  }
}

/*
 * Base64 encode table
 */
static const char b64enc[] =
  "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

/*
 * Encode Base64 format
 * p - Pointer to source buffer
 * q - Pointer to destination buffer
 * len - Length of buffer to encode
 * Returns length of encoded data
 */
uint16_t encode_base64(char *p, char *q, uint16_t len) {
  uint16_t j = 0;
  uint16_t i, ii;
  for (i = 0; i < len / 3; ++i) {
    ii = 3 * i;
    q[j++] = b64enc[(p[ii] & 0xfc) >> 2];
    q[j++] = b64enc[((p[ii] & 0x03) << 4) | ((p[ii + 1] & 0xf0) >> 4)];
    q[j++] = b64enc[((p[ii + 1] & 0x0f) << 2) | ((p[ii + 2] & 0xc0) >> 6)];
    q[j++] = b64enc[(p[ii + 2] & 0x3f)];
    if (((i + 1) % 18) == 0)
      q[j++] = '\r';
  }
  ii += 3;
  i = len - ii; // Bytes remaining to encode
  switch (i) {
  case 1:
    q[j++] = b64enc[(p[ii] & 0xfc) >> 2];
    q[j++] = b64enc[(p[ii] & 0x03) << 4];
    q[j++] = '=';
    q[j++] = '=';
    break;
  case 2:
    q[j++] = b64enc[(p[ii] & 0xfc) >> 2];
    q[j++] = b64enc[((p[ii] & 0x03) << 4) | ((p[ii + 1] & 0xf0) >> 4)];
    q[j++] = b64enc[(p[ii + 1] & 0x0f) << 2];
    q[j++] = '=';
    break;
  }
  q[j] = '\0';
  return j;
}

/*
 * Prompt for a name, store it in userentry
 * Returns number of chars read.
 * prompt - Message to display before > prompt
 * is_file - if 1, restrict chars to those allowed in ProDOS filename
 * Returns number of chars read
 */
uint8_t prompt_for_name(char *prompt, uint8_t is_file) {
  uint16_t i;
  char c;
  printf("%s>", prompt);
  i = 0;
  while (1) {
    c = cgetc();
    if (is_file && !isalnum(c) &&
        (c != RETURN) && (c != BACKSPACE) && (c != DELETE) &&
        (c != '.') && (c != '/')) {
      putchar(BELL);
      continue;
    }
    switch (c) {
    case RETURN:
      goto done;
    case BACKSPACE:
    case DELETE:
      if (i > 0) {
        putchar(BACKSPACE);
        putchar(' ');
        putchar(BACKSPACE);
        --i;
      } else
        putchar(BELL);
      break;
    default:
      putchar(c);
      userentry[i++] = c;
    }
    if (i == 79)
      goto done;
  }
done:
  userentry[i] = '\0';
  return i;
}

/*
 * Optionally attach files to outgoing email.
 * filename - Name of file containing email message
 */
void attach(char *fname) {
  FILE *fp, *fp2, *destfp;
  uint16_t chars, i;
  uint32_t size;
  char *s;
  videomode(VIDEOMODE_80COL);
  printf("%c%s ATTACHER%c\n\n", 0x0f, PROGNAME, 0x0e);
  fp = fopen(fname, "rb+");
  if (!fp)
    error(ERR_FATAL, "Can't open %s", fname);
  sprintf(filename, "%s/OUTBOX/TMPFILE", cfg_emaildir);
  destfp = fopen(filename, "wb");
  if (!destfp)
    error(ERR_FATAL, "Can't open TMPFILE");

  printf("Copying email content ...  "); // Space is for spinner to eat
  size = 0;
  while ((chars = get_line(fp, linebuf)) != -1) {
    size += chars;
    if (linebuf[0] == '\r')
      break;
    fputs(linebuf, destfp);
    spinner(size, 0);
  }
  fprintf(destfp, "MIME-Version: 1.0\r");
  fprintf(destfp, "Content-Type: multipart/mixed; boundary=a2forever\r\r");
  fprintf(destfp, "This is a multi-part message in MIME format.\r");
  fprintf(destfp, "--a2forever\r");
  fprintf(destfp, "Content-Type: text/plain; charset=US-ASCII\r");
  fprintf(destfp, "Content-Transfer-Encoding: 7bit\r\r");
  while ((chars = get_line(fp, linebuf)) != -1) {
    size += chars;
    fputs(linebuf, destfp);
    spinner(size, 0);
  }
  spinner(size, 1);

  printf("\rEnter the filename or filenames to attach to the email.\n");
  printf("An empty entry means you are done attaching files.\n\n");
  while (prompt_for_name("File to attach", 1)) {
    s = strrchr(userentry, '/');
    if (!s)
      s = userentry;
    else
      s = s + 1; // Character after the slash
    if (strlen(s) == 0) {
      error(ERR_NONFATAL, "Illegal trailing /");
      continue;
    }
    fp2 = fopen(userentry, "rb");
    if (!fp2) {
      error(ERR_NONFATAL, "Can't open %s", userentry);
      continue;
    }
    fprintf(destfp, "\r--a2forever\r");
    fprintf(destfp, "Content-Type: application/octet-stream\r");
    fprintf(destfp, "Content-Transfer-Encoding: base64\r");
    fprintf(destfp, "Content-Disposition: attachment; filename=%s;\r\r", s);
    printf("\r "); // Space is for spinner to eat
    size = 0;
    do {
      i = fread(buf, 1, 72 * 3 / 4 * 5, fp2); // Multiple of 72*3/4 bytes
      size += i;
      if (i == 0)
        break;
      i = encode_base64(buf, buf + READSZ / 2, i);
      i = fwrite(buf + READSZ / 2, 1, i, destfp);
      spinner(size, 0);
    } while (!feof(fp2));
    fclose(fp2);
    spinner(size, 1);
  }
  fprintf(destfp, "\r--a2forever--\r");
  fclose(fp);
  fclose(destfp);
  if (unlink(fname))
    error(ERR_FATAL, "Can't delete %s", fname);
  if (rename(filename, fname))
    error(ERR_FATAL, "Can't rename %s to %s", filename, fname);
}

/*
 * Load EMAIL.SYSTEM to $2000 and jump to it
 * (This code is in language card space so it can't possibly be trashed)
 */
#pragma code-name (push, "LC")
void load_email(void) {
  revers(0);
  clrscr();
  exec("EMAIL.SYSTEM", NULL); // Assume it is in current directory
}
#pragma code-name (pop)

void main(int argc, char *argv[]) {
  readconfigfile();
  if (argc == 2) {
    quit_to_email = 1;
    attach(argv[1]);
  } else
    error(ERR_FATAL, "No email file specified");
  if (quit_to_email)
    load_email();
}


