/****************************************************************
  homophonic cipher decrypting/encrypting program

  to compile: cc hmph.c -o hmph
  usage: hmph <-e|d> keyfile infile outfile

  e or d is for encrypt or decrypt

  keyfile is a file which contains the numbers that replace the letters
  in infile.


  There should be 26 lines in the keyfile, each starting with an
  integer which specifies how many numbers are on that line. The rest of 
  the line should be a list of numbers to replace that letter with.
  The keyfile is assumed to be correct.

  The program uses a pseudo-random number generator to pick from the list
  of numbers.  The encryption results should be different each time
  it is used.

  If a digit is encrypted it will be bracketed -{digit}.
  New lines will be replaced with -1
  If a number is found that does not eist in the keyfile it is replaced
  with <not found >
 
  ******************************************************************
  */




#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <time.h>

#define MAX_NUM_LEN 20                /* maximum length of numbers */


typedef char num[MAX_NUM_LEN];

typedef struct{
  int n;
  num *nums;
}key_type;

typedef key_type key[26];


void usage(char *);
void read_key(FILE *,key);
char *get_num(char,key);
char get_letter(char *,key);  
void encrypt(FILE*, FILE*,key);
void decrypt(FILE*, FILE*,key);


main(int argc,char *argv[])
{
  key key_array;
  int i,j,ENC=1,SWITCH=0;
  FILE *keyfile, *infile, *outfile;




  /* Check arguments and open files */


  if((argc!=4)&&(argc!=5))
    usage(argv[0]);
  else{
    if((argv[1])[0]=='-'){
      SWITCH=1;
      if(strlen(argv[1])!=2)
	usage(argv[0]);
      else {
	if(tolower((argv[1])[1])=='e');
	else if(tolower((argv[1])[1])=='d')
	  ENC=0;
	else usage(argv[0]);
      }
    }
    if(SWITCH&argc!=5) usage(argv[0]);
    
    if((keyfile=fopen(argv[1+SWITCH],"r"))==NULL){  
      fprintf(stderr,"Error opening key file.\n");
      exit(1);
    }

    if((infile=fopen(argv[2+SWITCH],"r"))==NULL){    
      fprintf(stderr,"Error opening input file.\n");
      exit(1);
    }
    
    if((outfile=fopen(argv[3+SWITCH],"w"))==NULL){   
      fprintf(stderr,"Error opening output file.\n");
      exit(1);
    }
  }

  read_key(keyfile,key_array);
  fclose(keyfile);

  /*
  for(i=0;i<26;i++){
    printf("%d ",key_array[i].n);
    for(j=0;j<key_array[i].n;j++)
      printf("%s ",key_array[i].nums[j]);
    printf("\n");
  }
  */
  srandom(time(NULL));                     /* seed random number generator
					      with time */

  if(ENC) encrypt(infile,outfile,key_array);
  else decrypt(infile,outfile,key_array);

  fclose(infile);
  fclose(outfile);
}



/* usage prints the correct usage and exits */


void usage(char *name)
{
  
  printf("Usage: %s <-e|d> keyfile infile outfile\n",name);
  printf("\n<e|d> specifies en/decrypt - Default mode is encrypt.\n");
  printf("\nkeyfile should contain 26 lines, one for each letter,where the first\n");
  printf("number on each line is the number of entries on the line, and the\n");
  printf("rest of the numbers are the numbers to substitute for that particular\n");
  printf("letter.\n");
  exit(1);
}




/* read_key reads from keyfile and places the results in key_array
   it does not check to see the keyfile is correctly constructed
   */


void read_key(FILE *keyfile,key key_array)
{
  int i,j,n;

  for(i=0;i<26;i++){
    fscanf(keyfile,"%d",&n);
    key_array[i].n=n;
    key_array[i].nums=calloc(n,sizeof(num));
    for(j=0;j<n;j++)
      fscanf(keyfile,"%s",key_array[i].nums[j]);
  }
}


/* get_num returns a pointer to a string of numbers that is used
   to replace a certian letter.  The string is picked randomly 
   from a list for that letter
   */

char *get_num(char letter,key key_array)
{
  int r;
  char *result;
  
  r=random();

  r%=key_array[letter-'a'].n;
  result=(char*)key_array[letter-'a'].nums[r];
  return(result);
}



/* get_letter returns the corresponding letter for a numeric
   string.  It performs a linear search.  If the number is not found
   it returns zero
   */

char get_letter(char *number,key key_array)
{
  int i,j;
  
  for(i=0;i<26;i++)
    for(j=0;j<key_array[i].n;j++)
      if(!(strcmp(number,key_array[i].nums[j])))
	return(i+'a');
	
  return('\0');
  
}



/* encrypt encrypts infile and writes it to outfile */


void encrypt(FILE *infile, FILE *outfile, key key_array)
{
  int nch;


  while((nch=getc(infile))!=EOF){
    if(isalpha(nch))
      fprintf(outfile,"%s ",get_num(tolower(nch),key_array));
    else if(isdigit(nch))
      fprintf(outfile,"{%c}",nch);
    else if(nch=='\n')
      fprintf(outfile," -1\n");
    else
      fprintf(outfile,"%c",nch);
  }
}



/* decrypt decrypts infile and writes it to outfile */


void decrypt(FILE *infile, FILE *outfile, key key_array)
{
  int nch,pos,lch,xch;
  char block[100],pch;
  
  while((nch=getc(infile))!=EOF){
    pos=0;
    if(isdigit(nch)){                       /* decrypt sequence of digits */
      block[pos++]=nch;
      nch=getc(infile);
      while((isdigit(nch))&&(nch!=EOF)){
	block[pos++]=nch;
	nch=getc(infile);
      }
      if(nch!=' ') ungetc(nch,infile);
      block[pos]='\0';
      pch=get_letter(block,key_array);
      if(pch) putc(pch,outfile);
      else fprintf(outfile,"<not found>");
      
    }
    
    else if(nch=='-'){                      /* convert '-1' to linefeed */
      lch=getc(infile);
      if(lch=='1'){
	xch=getc(infile);
	if(xch=='\n'){
	  fprintf(outfile,"\n");
	  continue;
	}
	else {
	  ungetc(xch,infile);
	  ungetc(lch,infile);
	}
      }
      else{
	ungetc(lch,infile);
	putc(nch,outfile);
	continue;
      }
      putc(nch,outfile);
    }
    
    else if(nch=='{'){                /* convert {digit} to digit */
      lch=getc(infile);
      if(isdigit(lch)){
	xch=getc(infile);
	if(xch=='}'){
	  putc(lch,outfile);
	  continue;
	}
	else{
	  ungetc(xch,infile);
	  ungetc(lch,infile);
	}
      }
      else {
	ungetc(lch,infile);
	putc(nch,outfile);
	continue;
      }
      putc(nch,outfile);
    }

    else putc(nch,outfile);
  }

  
}







