/* MONO.C                             */
/* Mono-alphabetic substitution aid   */
/* by J.W.Stumpel, June 1989          */
/* Linux/ncurses version, Nov. 2000   */

#include <curses.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

#define THEIGHT	14
#define LEFT 0
#define RIGHT 1
#define UP 0
#define DOWN 1

#define BUFMAX 		15000

/*global variables */
char plain[27], cipher[27], fr[27];
char *normalf = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
char *blank = "                          ";
int myindex, mode, display_startrow = 0;
WINDOW *textbox, *textwin, *panel;

void newcipher (char newchar, char *cbuf, int letters, WINDOW * w1, WINDOW * w2);
void movindex (int k, WINDOW * w);
void frcount (char *text, int letters);
void newmode (WINDOW * win);
void set_markers (int updown, int onoff);
void dispmonotext (char *text, int letters, WINDOW * win);
void myscroll (int updown, int letters);


static void
finish (int sig)
{
  clear ();
  touchwin (stdscr);
  refresh ();
  endwin ();
  exit (sig != 0);
}


void
main (int argc, char *argv[])
{
  char *cbuf, *p;
  char filename[64];
  int k, letters;
  FILE *CIPHER;

  initscr ();
  start_color ();
  refresh ();

  signal (SIGINT, finish);	/* arrange interrupts to terminate */
  keypad (stdscr, TRUE);	/* enable keyboard mapping */
  cbreak ();			/* take input chars one at a time, 
				   no wait for \n */
  noecho ();			/* don't echo input */
  textbox = newwin (THEIGHT, COLS, 0, 0);
  textwin = derwin (textbox, THEIGHT - 2, COLS - 2, 1, 1);
  panel = newwin (4, 36, THEIGHT + 6, 18);
  scrollok (textwin, FALSE);
  init_pair (1, COLOR_YELLOW, COLOR_BLUE);
  init_pair (2, COLOR_YELLOW, COLOR_GREEN);
  init_pair (3, COLOR_RED, COLOR_WHITE);
  init_pair (4, COLOR_WHITE, COLOR_BLUE);
  init_pair (5, COLOR_GREEN, COLOR_BLUE);
  wattrset (textbox, A_BOLD | COLOR_PAIR (4));
  wattrset (textwin, A_ALTCHARSET | A_BOLD | COLOR_PAIR (1));
/*  wattrset (panel, A_BOLD | COLOR_PAIR (2)); */

  box (textbox, 0, 0);
/*  wbkgdset (panel, COLOR_PAIR (2)); 
   werase (panel);
   wrefresh (panel); */

  plain[26] *= 0, cipher[26] = 0, fr[26] = 0;
  mvaddstr (16, 16, "M O N O  -  A L P H A B E T I C    A N A L Y S I S");

  cbuf = (char *) malloc (BUFMAX);

  box (textbox, 0, 0);
  mvwaddstr (textbox, 0, COLS / 2 - 2, "TEXT");

  do
    {
      int notfound = 1, endprog = 1;
      wbkgdset (textwin, ' ' | COLOR_PAIR (1));
      werase (textwin);
      wrefresh (textbox);
      mvaddstr (18, 7, "Enter filename: ");
      curs_set (1);
      do
	{
	  refresh ();
	  mvaddstr (18, 23, "                          "
		    "                              ");
	  echo ();
	  if (mvgetstr (18, 24, filename) == ERR)
	    break;
	  curs_set (0);
	  endprog = 0;
	  CIPHER = fopen (filename, "r");
	  if (CIPHER == NULL)
	    {
	      notfound = 1;
	      mvaddstr (23, 18, "File not found. Press ESC.");
	      noecho ();
	      while (getch () != 27);
	      mvaddstr (23, 18, "                          ");
	    }
	  else
	    notfound = 0;
	}
      while (notfound);
      if (endprog)
	break;

      /* Get the ciphertext into memory and display */
      p = cbuf;
      letters = 0;
      while ((k = getc (CIPHER)) != EOF)
	{
	  if (k == '\n')
	    k = ' ';
	  if (isprint (k))
	    {
	      if (isalpha (k))
		k = toupper (k);
	      *p = k;
	      p++;
	      letters++;
	      if (letters >= BUFMAX)
		break;
	    }
	}
      fclose (CIPHER);
      *p = 0;
      strcpy (plain, blank);
      frcount (cbuf, letters);
      mode = 0;
      myindex = 12;
      newmode (panel);
      movindex (LEFT, panel);
      display_startrow = 0;
      dispmonotext (cbuf, letters, textwin);

      do
	{
	  noecho ();
	  k = getch ();
	  if ((k == ',') || (k == '<') || (k == KEY_LEFT))
	    movindex (LEFT, panel);
	  else if ((k == '.') || (k == '>') | (k == KEY_RIGHT))
	    movindex (RIGHT, panel);
	  else if (k == KEY_UP)
	    {
	      myscroll (UP, letters);
	      dispmonotext (cbuf, letters, textwin);
	    }
	  else if (k == KEY_DOWN)
	    {
	      myscroll (DOWN, letters);
	      dispmonotext (cbuf, letters, textwin);
	    }
	  else if (isalpha (k))
	    newcipher (toupper (k), cbuf, letters, panel, textwin);
	  else if (k == 32)
	    {
	      newmode (panel);
	      movindex (LEFT, panel);
	    }
	}
      while (k != 27);
    }
  while (1);
  finish (0);
}

void
newmode (WINDOW * win)
{
  int j;
  char *p1, *p2;
  char x;
  mode++;
  if (mode == 4)
    mode = 1;
  switch (mode)
    {
    case 1:
      mvwaddstr (win, 0, 0, "CIPHER");
      mvwaddstr (win, 2, 0, "PLAIN ");
      p1 = normalf;
      p2 = plain;
      myindex = fr[myindex] - 'A';
      break;
    case 2:
      mvwaddstr (win, 0, 0, "PLAIN ");
      mvwaddstr (win, 2, 0, "CIPHER");
      for (j = 0; j < 26; j++)
	cipher[j] = 32;
      for (j = 0; j < 26; j++)
	if (plain[j] != 32)
	  cipher[plain[j] - 'A'] = j + 'A';
      p1 = normalf;
      p2 = cipher;
      myindex = (plain[myindex] == 32) ? myindex : plain[myindex] - 'A';
      break;
    case 3:
      mvwaddstr (win, 0, 0, "C/FREQ");
      mvwaddstr (win, 2, 0, "PLAIN ");
      if ((x = cipher[myindex]) != 32)
	{
	  j = 0;
	  while (fr[j] != x)
	    j++;
	  myindex = j;
	}
      for (j = 0; j < 26; j++)
	cipher[j] = 32;
      for (j = 0; j < 26; j++)
	if (plain[fr[j] - 'A'] != 32)
	  cipher[j] = plain[fr[j] - 'A'];
      p1 = fr;
      p2 = cipher;
    }
  mvwaddstr (win, 0, 10, p1);
  mvwaddstr (win, 2, 10, p2);
}


void
newcipher (char k, char *cbuf, int letters, WINDOW * w1, WINDOW * w2)
{
  int x, j;
  switch (mode)
    {
    case 1:
      for (j = 0; j < 26; j++)
	if (plain[j] == k)
	  plain[j] = 32;
      plain[myindex] = k;
      mvwaddstr (w1, 2, 10, plain);
      break;
    case 2:
      for (j = 0; j < 26; j++)
	if (cipher[j] == k)
	  cipher[j] = 32;
      x = cipher[myindex] - 'A';
      if (x >= 0)
	plain[x] = 32;
      cipher[myindex] = k;
      mvwaddstr (w1, 2, 10, cipher);
      plain[k - 'A'] = myindex + 'A';
      break;
    case 3:
      for (j = 0; j < 26; j++)
	if (plain[j] == k)
	  plain[j] = 32;
      for (j = 0; j < 26; j++)
	if (cipher[j] == k)
	  cipher[j] = 32;
      plain[fr[myindex] - 'A'] = k;
      cipher[myindex] = k;
      mvwaddstr (w1, 2, 10, cipher);
    }
  wrefresh (w1);
  dispmonotext (cbuf, letters, w2);
}				/*end newcipher */

void
frcount (char *text, int letters)
/* counts letter frequencies, and arranges letters in
   character array fr in decreasing frequency order */
{
  int ind = 0, j, notready = 1, temp, count[26];
  char k;
  strcpy (fr, normalf);
  for (j = 0; j < 26; j++)
    count[j] = 0;
  while (ind < letters)
    {
      k = *(text + ind);
      if (isalpha (k))
	count[toupper (k) - 'A']++;
      ind++;
    }

/* bubble sort, sorry about that */
  while (notready)
    {
      notready = 0;
      for (j = 0; j < 25; j++)
	if (count[j] < count[j + 1])
	  {
	    temp = count[j];
	    count[j] = count[j + 1];
	    count[j + 1] = temp;
	    temp = fr[j];
	    fr[j] = fr[j + 1];
	    fr[j + 1] = temp;
	    notready = 1;
	  }
    }
}


void
movindex (int k, WINDOW * w)
{
  static int oldindex;
  switch (k)
    {
    case LEFT:
      myindex--;
      if (myindex < 0)
	myindex = 25;
      break;
    case RIGHT:
      myindex++;
      if (myindex > 25)
	myindex = 0;
    }
  mvwaddch (w, 1, 10 + oldindex, 32);
  mvwaddch (w, 1, 10 + myindex, '|');
  oldindex = myindex;
  wrefresh (w);
}


void
dispmonotext (char *text, int letters, WINDOW * win)
{
  char k, kp;
  int ind = 0, C = 1, displayed = 0;
  wattrset (win, A_BOLD | COLOR_PAIR (1));
  if (display_startrow)
    set_markers (UP, 1);
  else
    set_markers (UP, 0);
  wmove (win, 0, 0);
  ind = (COLS - 2) * display_startrow;
  while (ind < letters)
    {
      k = *(text + ind);
      if (isalpha (k))
	{
	  kp = plain[toupper (k) - 'A'];
	  if (C)
	    {
	      if (kp == 32)
		waddch (win, k);
	      else
		{
		  wattrset (win, COLOR_PAIR (3));
		  waddch (win, kp);
		  C = 0;
		}
	    }
	  else
	    {
	      if (kp == 32)
		{
		  wattrset (win, A_BOLD | COLOR_PAIR (1));
		  waddch (win, k);
		  C = 1;
		}
	      else
		waddch (win, kp);
	    }
	}
      else
	waddch (win, k);
      ind++;
      displayed++;
      if (displayed == (THEIGHT - 2) * (COLS - 2))
	{
	  set_markers (DOWN, 1);
	  break;
	}
    }
  if (ind == letters)
    {
    wclrtoeol (win);
    set_markers (DOWN, 0);
    }
  wrefresh (win);
}

void
myscroll (int updown, int letters)
{
  int max_startrow = letters / (COLS - 2);
  if (letters % (COLS - 2))
    max_startrow++;
  max_startrow -= THEIGHT - 2;

  if ((updown == DOWN) && (display_startrow < max_startrow))
    display_startrow++;
  else if ((updown == UP) && display_startrow)
    display_startrow--;
}


void
set_markers (int updown, int on)
{
  int h = 1 + (THEIGHT - 3) * updown, k = updown ? 'v' : '^';
  if (on)
    /* set markers */
    {
      wattrset (textbox, A_BOLD | COLOR_PAIR (5));
      mvwaddch (textbox, h, 0, k);
      mvwaddch (textbox, h, COLS - 1, k);
    }
  else
    /* remove markers */
    {
      wattrset (textbox, A_BOLD | COLOR_PAIR (4));
      mvwaddch (textbox, h, 0, ACS_VLINE);
      mvwaddch (textbox, h, COLS - 1, ACS_VLINE);
    }
  wrefresh (textbox);
}
