// steganography class -- Williams
import java.io.*;

public class Steg extends FilterWriter {
    protected Reader plainmessage;  // template
    protected String plainmsgfile;  // file name for template
    protected boolean newline;      // need a newline
    protected boolean encoding;     // true if hiding
    protected boolean decphase=false;  // decoding phase
    protected int decodech=0;       // decoding character
    protected int decodectr=1;     // current bit decoding
    // when phase is false, 1 space is a 0
    // and 2 spaces is a 1
    // otherwise, invert
    protected boolean phase=false;


    // This is the encoding constructor
    public Steg(String plainmsg,Writer out) throws IOException {
      super(out);
      plainmsgfile=plainmsg;
      plainmessage=new BufferedReader(new FileReader(plainmsgfile));
      encoding=true;
    }

    // This is the decoding constructor
    public Steg(Writer out) {
      super(out);
      encoding=false;
    }

    // Read a character
    // if EOF, return a space and rewind
    protected char readChar() throws IOException {
       int c;
       c=plainmessage.read();
       if (c==-1) {
          plainmessage.close();
          plainmessage=new FileReader(plainmsgfile);
          c=' ';
          }
       return (char)c;
    }

    // Read a word. Skip leading blanks and set newline
    // if you encounter a newline
    protected String readWord() throws IOException {
      StringBuffer wordBuf = new StringBuffer();
      char c;
      newline=false;
      do {
         c=readChar();
         if (c=='\n') newline=true;
      } while (Character.isWhitespace(c));  // skip lead blanks
      do {
        wordBuf.append(c);
        c=readChar();
        if (c=='\n') newline=true;
        } while (!Character.isWhitespace(c)); // read to blank
       return wordBuf.toString();
    }


    // Encrypt or decrypt according to flag
    public void write(int c) throws IOException {
     // do the "encryption"
       if (encoding) {
       String word,sep;
       boolean bit;
       for (int i=0;i<16;i++) { // unicode is 16 bits
         word=readWord();
         out.write(word);
         if ((c&(1<<i))==0) bit=false; else bit=true;
         if (phase) 
            if (bit) sep=" "; else sep="  ";
         else 
            if (bit) sep="  "; else sep=" ";
         phase=!phase;
         out.write(sep);
         if (newline) out.write('\n');
         }
      }
      else {
    // decoding
        if (!decphase) {
          if (c==' ') decphase=true;
          }
        else {
          if (c=='\n'||c=='\r') return; // ignore eol
          decphase=false;
          if (c==' '&&!phase || c!=' ' && phase) {
              decodech|=decodectr;
          }
          decodectr<<=1;
          phase=!phase;
          if (decodectr==0x10000) {
              out.write(decodech);
              decodectr=1;
              decodech=0;
          }

       }
     }
    }

    // This write simply delagates to the first write
    public void write(char[] cbuf,int off, int len) throws IOException {
      while (len--!=0) {
        write((int)cbuf[off++]);
      }
    }

    // This write simply delagates to the first write
    public void write(String str,int off, int len) throws IOException {
       while (len--!=0) {
         write((int)str.charAt(off++));
         }
      }

    // Support for the test main
    static void encodetest(String encfile,String inpf) throws Exception {
     FileReader inp=new FileReader(inpf);
     OutputStreamWriter writer = new OutputStreamWriter(System.out);
     Steg stegout=new Steg(encfile,writer);
     do {
         int c=inp.read();
         if (c==-1) break;
         stegout.write(c);
     } while (true);
     stegout.flush();
    }

    // Support for the test main
    static void decodetest(String fn) throws Exception  {
     OutputStreamWriter writer = new OutputStreamWriter(System.out);
     Steg stegin=new Steg(writer);
     FileReader fread=new FileReader(fn);
     do {
         int c=fread.read();
         if (c==-1) break;
         stegin.write(c);
     } while (true);
     stegin.flush();
    }

    // Test main -- if two arguments, encode
    // if 1 argument, decode
    // anything else prints help message
    public static void main(String args[]) throws Exception {
      if (args.length==2){
         encodetest(args[0],args[1]);
         System.exit(0);
         }
      if (args.length==1) {
         decodetest(args[0]);
         System.exit(0);
         }
      System.out.println("Usage: Steg template file " +
           "(to encode)\nSteg file (to decode");
    }

}
