/****************************************************************** 
   Substitution cipher tool

   To compile: cc subst.c -lcurses -o subst

   Usage: subst infile outfile

********************************************************************/


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

#define MAX_SIZE 2000
#define emsg(a)  center(a,A_REVERSE+A_STANDOUT,FALSE,' ',LINES-4);       
#define rm_msg() move(LINES-4,0);clrtoeol()

typedef struct{
  float f;
  char c;
}f_type;

typedef char substype[2];

char sub_table[127];


void menu(FILE *,char *,char *,int[]);
void solve_mode(char *,int [],int,int,int);
void center(char*,int,bool,char,int);
void freq_count(char *,bool,f_type[]);
int comp_f(const void *a,const void *b); 
int punct(char c);
void disp_menu(bool ALPHA);
void disp_freq(f_type[],int,int,bool);
bool mesg_y_n(char *msg);    
void disp_subst(int,int);
int getrepc(void);
void replace(char *,int[],char,char,int);
void unreplace(char*,int[],char,int);
void print_ch(int,int,char*,int[],bool);
int text_len(char *);
void decide(int);




bool CHANGED=FALSE,SAVED=FALSE,F_ALPH=TRUE,O_ON, 
  A_ON,S_ON=TRUE,FAC=FALSE,FLC=FALSE;

int O_START,F_START,F_LINES,S_START,S_LINES,N_LINES,A_START,O_LINES; 

char a_reminder[]="Letters in descending order: ETAOINSHRDLCUMWFGYPBVKJQZ"; 



main(int argc,char *argv[])
{
  char cipher_text[MAX_SIZE],new_text[MAX_SIZE];
  int i=0,nch,*mod_array,j;
  FILE *ctext,*output;


  /* Check arguments and open files */

  if(argc!=3){
    fprintf(stderr,"Usage: %s inputfile outputfile\n",argv[0]);
    exit(1);
  }


  if((ctext=fopen((char*)argv[1],"r"))==NULL){
    fprintf(stderr,"Error opening file %s.\n",argv[1]);
    exit(1);
  }

  if((output=fopen((char*)argv[2],"w"))==NULL){
    fprintf(stderr,"Error opening file %s.\n",argv[2]);
    exit(1);
  }


  /* Read input file */


  while((nch=getc(ctext))!=EOF&&i<MAX_SIZE-1){
    cipher_text[i]=nch;
    new_text[i++]=nch;
  }
  cipher_text[i]='\0';
  new_text[i]='\0';
  mod_array=(int*)calloc(i,sizeof(int));
  for(j=0;j<i;j++) mod_array[j]=0;
  for(i=0;i<127;i++) sub_table[i]=0;
  fclose(ctext);

  menu(output,cipher_text,new_text,mod_array);
}






void menu(FILE *output,char *cipher_text,char *new_text,int mod_array[])
{
  char ch;
  int i,nch,sp,x,y,opt,tl;
  WINDOW *scrn;
  f_type alph_arr[96],all_arr[96];
  char sstr[40],ostr[40];

  scrn=initscr();                       /* initialize screen */
  cbreak();
  noecho();
  nonl();
  intrflush(stdscr,FALSE);
  keypad(stdscr,TRUE);

  tl=text_len(cipher_text);
  
  decide(tl);                          /*  determine screen layout */




  sprintf(sstr,"Substitution Cipher    Total lines: %d  Lines shown: %d",tl,
	  N_LINES);
  
  sprintf(ostr,"Unmodified Text     Lines shown: %d",O_LINES);

  center(sstr,A_REVERSE+A_STANDOUT,TRUE,' ',0);

  print_ch(1,N_LINES,new_text,mod_array,FALSE);
  if(O_ON){
    center(ostr,A_REVERSE+A_STANDOUT,TRUE,' ',O_START);
    print_ch(O_START+1,O_LINES,cipher_text,mod_array,FALSE);
  }

  freq_count(new_text,TRUE,alph_arr);
  disp_freq(alph_arr,F_LINES,F_START,F_ALPH);
  freq_count(new_text,FALSE,all_arr);
  if(S_ON)
    disp_subst(S_START,S_LINES);
  
  if(A_ON){
    move(A_START,0);
    attron(A_BOLD);
    printw("%s",a_reminder);
    attroff(A_BOLD);
  }

  refresh();  
  
  disp_menu(F_ALPH);
  move(30,0);  
  
  
  while (TRUE){                     /* Loop until user exits */
    fflush(stdin);
    nch=getch();
    opt=tolower(nch);

    if(opt=='s'){                   /* Save file  */        
      if(SAVED) rewind(output);
      fprintf(output,"%s",new_text);
      emsg("Output saved");
      CHANGED=FALSE;
      SAVED=TRUE;
    }
    else if(opt=='x'){             /* Exit */
      if(CHANGED)
	if(mesg_y_n("Text has been changed, save before exiting (y/n)?")){
	  if(SAVED) rewind(output);      
	  fprintf(output,"%s",new_text);
	}
      break;
    }
    else if(opt=='r'){            /* Start over */
  
      if(mesg_y_n("Start over- all changes will be lost (y/n)")){
	CHANGED=FALSE;
	strcpy(new_text,cipher_text);
	print_ch(1,N_LINES,new_text,mod_array,FALSE);
	for(i=0;i<strlen(new_text);i++) mod_array[i]=0;
      }
      rm_msg();
    }
    else if(opt=='t'){           /* enter solving mode */
 
      solve_mode(new_text,mod_array,N_LINES,S_START,S_LINES);
      rm_msg();
      move(LINES-3,0);
      clrtoeol();
    }
    else if(opt=='f'){           /* change frequency display */    
      F_ALPH^=TRUE;
      disp_menu(F_ALPH);
      if(F_ALPH)
	disp_freq(alph_arr,F_LINES,F_START,F_ALPH);   
      else disp_freq(all_arr,F_LINES,F_START,F_ALPH); 
    }
    refresh();
  }
  

  fclose(output);

  endwin();
}



/* solve_mode prompts the user for the character to replace and the one
   to replace it with.  Changes can be reversed by pressing the left
   arrow key */


void solve_mode(char *new_text,int mod_array[],int N_LINES,
		int S_START,int S_LINES)
{
  int sp=0,s_size=0,i,n=COLS/2;
  substype sub_stack[100];
  int c1,c2;

  center("(\047Enter\047 to exit, \047Left Arrow\047 to undo changes)     ",
	 A_BOLD,FALSE,' ',LINES-3);

  while(TRUE){
    move(LINES-4,n-25);
    attron(A_REVERSE+A_STANDOUT);
    printw("Character to replace:");
    attroff(A_REVERSE+A_STANDOUT);
    move(LINES-4,n-3);
    if(!(c1=getrepc())) return;        /* get first character, if enter */
    if(c1==KEY_LEFT){                  /* is pressed, exit  */  
      if(s_size!=0){
	sp--;                          /* undo last change if left arrow*/  
	if(sp==-1) sp=99;              /* pressed */ 
	s_size--;
	sub_table[sub_stack[sp][0]]=0;
	unreplace(new_text,mod_array,sub_stack[sp][0],sp+1);
	if(S_ON) disp_subst(S_START,S_LINES);   
	move(1,0);
	print_ch(1,N_LINES,new_text,mod_array,TRUE);
	refresh();
     }
      continue;
    }
    printw("%c",c1);
    attron(A_REVERSE+A_STANDOUT);
    move(LINES-4,n);
    printw("with character:");
    attroff(A_REVERSE+A_STANDOUT);
    move(LINES-4,n+17);
    refresh();
    while(!(c2=getrepc()));
    if(c2==KEY_LEFT){
      continue;
    }
    printw("%c",c2);
    CHANGED=TRUE;
    sub_stack[sp][0]=c1;             /* add substitution to stack */
    sub_stack[sp][1]=c2;
    sub_table[c1]=c2;
    sp++;
    sp%=100;
    s_size++;
    replace(new_text,mod_array,c1,c2,sp);
    if(S_ON) disp_subst(S_START,S_LINES);
    move(1,0);
    print_ch(1,N_LINES,new_text,mod_array,TRUE);
    refresh();
  }  



}


/* centers text. int attr is the attribute for the text, pad determines
   whether a character pch is used to fill the rest of the line.  
   */

       
 
void center(char *text,int attr,bool pad,char pch,int row)
{
  int i,sl=strlen(text),sp=COLS/2-sl/2;


  move(row,0);
  clrtoeol();
  attron(attr);

  if(pad) 
    for(i=0;i<sp;i++)
      printw("%c",pch);
  else
    move(row,sp);

  printw("%s",text);

  if(pad) 
    for(i=sp+sl;i<COLS;i++)
      printw("%c",pch);

  

  attroff(attr);
     
}


/* freq_count counts the occurence of either alphabetic characters,
   or if ALPHA=FALSE, all printable, non-punctuation characters.
   Then the sorted frequency is stored in freq_arr[]
   */


void freq_count(char *text,bool ALPHA,f_type freq_arr[])
{
  int i,pos=0,num=0;
  char ch,f_array[127];

  for(i=0;i<127;i++) f_array[i]=0;

  while(ch=text[pos++]){             /* count character occurences */
    if(ch>32&&ch<128){
      if(isalpha(ch)){
	f_array[tolower(ch)]++;
	num++;
      }
      else if(!ALPHA&&!punct(ch)){
	num++;
	f_array[ch]++;
      }
    }
  }

  pos=0;

  for(i=32;i<127;i++){            /* determine frequencies */
    if(f_array[i]){
      freq_arr[pos].f=(float)f_array[i]/(float)num;
      freq_arr[pos++].c=i;
    }
  }
  for(i=pos;i<96;i++){
    freq_arr[i].f=0;
    freq_arr[i].c=0;
  }

 
  /* sort in descending order */
 
  qsort(freq_arr,pos,sizeof(f_type),comp_f);
  
  /*
  for(i=0;i<pos;i++)
    printw("%c %.3f ",freq_arr[i].c,freq_arr[i].f);

    
  
  printw("\n%d\n",num);
  */
}


/* comp_f is used by freq_count to compare two f_types
   by qsort
   */

int comp_f(const void *a,const void *b)
{
  f_type *x=(f_type*)a,*y=(f_type *)b;
  if(x->f>y->f) return(-1);
  else if(x->f<y->f) return(1);
  else return(0);
}



/* int punct returns 1 is char c is punctuation */

int punct(char c)
{
  char p[]={33,34,39,40,41,44,45,46,58,59,63,0};

  if((strchr(p,c))!=NULL)
    return(1);
  return(0);
}


/* void disp_menu displys the menu at the bottom of the screen */

void disp_menu(bool ALPHA)
{

   move(LINES-2,0); 
 
  addch('T'+A_REVERSE+A_STANDOUT);
  printw(" Substitute\t");
  addch('F'+A_REVERSE+A_STANDOUT);
  printw(" Freq %s\t", ALPHA ? "ALL":"ALPHA");
  addch('S'+A_REVERSE+A_STANDOUT);
  printw(" Save changes\t");
  addch('R'+A_REVERSE+A_STANDOUT);
  printw(" Restart\n");
  addch('X'+A_REVERSE+A_STANDOUT);
  printw(" Exit program\t");
 

}



/* disp_freq displays the information in f_array to the screen
   num_lines determines how many lines will be output
   */

void disp_freq(f_type f_array[],int num_lines,int F_START,bool F_ALPH)
{
  int n=COLS/8,nline=0,pos=0,i,j,pnl=COLS%8;


  char ftype[100];

  sprintf(ftype,"Frequency Table: %s",F_ALPH ? "Alphabetic Characters Only"
	  : "Printable Non-punctuation Characters");

 
  center(ftype,A_REVERSE+A_STANDOUT,TRUE,' ',F_START);

  for(i=0;i<num_lines;i++){
    for(j=0;j<n;j++){
      if(f_array[pos].f==0){
	clrtoeol();
	return;
      }
      addch(f_array[pos].c+A_BOLD);
      printw(" %.3f ",f_array[pos++].f);
    }
    if(pnl) printw("\n");
  }

}



/* bool mesg_y_n prints a message and then asks for a yes or no response
 */

bool mesg_y_n(char *msg)
{
  int nch;

  center(msg,A_REVERSE+A_STANDOUT,FALSE,' ',LINES-4);
  while(TRUE){
    nch=getch();
    if(tolower(nch)=='y') return(TRUE);
    else if(tolower(nch)=='n') return(FALSE);
  }
}



/* void disp_subst displays the substitution table */

void disp_subst(int S_START,int S_LINES)
{
 int n=COLS/4,nline=0,pos=33,i=0,j,pnl=COLS%4; 
 bool not_empty=FALSE;

 center("Substitution Table",A_REVERSE+A_STANDOUT,TRUE,' ',S_START);

   
  while(i++<S_LINES){
    clrtoeol();
    j=0;
    while(j<n&&pos<127){
      if(sub_table[pos]){
	not_empty=TRUE; 
	addch(pos+A_REVERSE);
	printw(" %c ",sub_table[pos]);
	j++;
     }
      pos++;
    }
    if(not_empty&&pnl) printw("\n");
    
  }
    if(!not_empty) printw("No substitutions");
    refresh();

}


/* int getrepc is called by solve_mode to get the characters
   it ignores punctuation but will return 0 is enter is pressed
   */


int getrepc(void)
{
  int nch;

  while(TRUE){
    fflush(stdin);
    nch=getch();
    if(nch>32&&nch<128){
      if(isalpha(nch))
	return(tolower(nch));
      else if(!punct(nch))
	return(nch);
    }
    else if((nch==KEY_ENTER)||(nch==13))
      return(0);
    else if(nch==KEY_LEFT)
      return(KEY_LEFT);
  }
}


/* void replace is called by solve_mode to find all occurences
   of char c2 in new_text and replace it with c2
   */

void replace(char *new_text,int mod_array[],char c1,char c2,int num)
{
  int pos=0;
  char ch;

  while(ch=new_text[pos]){
    if(tolower(ch)==c1&&mod_array[pos]==0){
      if(isupper(ch))
      new_text[pos]=toupper(c2);
      else new_text[pos]=c2;
      mod_array[pos]=num;
    }
    pos++;
  }
}


/* void unreplace is called by solve_mode to reverse changes */

void unreplace(char *new_text,int mod_array[],char c1,int num)
{
  int pos=0;
  char ch;

  while(ch=new_text[pos]){
    if(mod_array[pos]==num){
      mod_array[pos]=0;
      if(isupper(new_text[pos]))
	new_text[pos]=toupper(c1);
      else new_text[pos]=c1;
    }
    pos++;
  }
   
} 



/* void print_ch prints a certain number of lines of a string
   if ON=TRUE, all characters that have a corresponding positive value
   in mod_array are bold */

void print_ch(int START,int LINES,char* text,int mod_array[],bool ON)
{
  int ln=0,pos=0,i,col=0;
  char ch;
  move(START,0);

  while(ch=text[pos]){
    if(ch=='\n'||col>=COLS){
      ln++;
      col=0;
    }
    if(mod_array[pos]&&ON)
      addch(ch+A_BOLD);
    else addch(ch);
    col++;
  
  if(ln>=LINES) return;
  pos++;
  }
}


/* int text_len determines how many lines char *text will be */

int text_len(char *text)
{
  int pos=0,ln=0,col=0;
  char ch;

  while(ch=text[pos]){
    if(ch=='\n'||col>=COLS){
      ln++;
      col=0;
    }
    col++;
    pos++;
  }

  return(ln);
}

/* void decide sets up the screen based on howmany lines there are*/

void decide(int n)
{
  int total;

  if((2*(n+1)+9)<LINES-4){    /* text is short */
  N_LINES=n;
  O_ON=TRUE;
  O_START=n+1;
  O_LINES=n;
  F_START=O_START+n+1;
  F_LINES=3;
  S_ON=TRUE;
  S_START=F_START+4;
  S_LINES=3;
  A_ON=TRUE;
  A_START=S_START+4;
}

  else if(n>=LINES-10){             /* text is long */
    N_LINES=LINES-11;
    O_ON=FALSE;
    F_START=LINES-10;
    F_LINES=2;
    S_ON=TRUE;
    S_START=F_START+3;
    S_LINES=2;
    A_ON=FALSE;
  }
  

  else{                  
    total=LINES-13;
    N_LINES=n;
    total-=n;
    if(total==1){
      F_LINES=3;
      total--;
    }
    else if(total>1){
      F_LINES=4;
      total-=2;
    }
    else F_LINES=2;
    S_ON=TRUE;
    if(total>0){
      S_LINES=3;
      total--;
    }
    if(total>6){
      O_ON=TRUE;
      if(total>n){
	O_LINES=n;
	total-=n;
      }
      else{
	O_LINES=total;
	total=0;
      }
    }
    if(total>0) A_ON;
    if(O_ON){
      O_START=n+1;
      F_START=O_START+O_LINES+1;
    }
    else F_START=n+1;
    S_START=F_START+F_LINES+1;
    if(A_ON) A_START=S_START+S_LINES+1;
  }
} 
  
























