//package IM;

import javax.crypto.Cipher;
import javax.crypto.SealedObject;
import javax.crypto.spec.SecretKeySpec;
import javax.crypto.spec.IvParameterSpec;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.SignedObject;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.math.BigInteger;

class Receive {
    
    public static void main(String args[]) {
        try {
            String CAaddress = "localhost";
            int port;
            int CAport;

            FileReader fr = new FileReader("config");
            BufferedReader in = new BufferedReader(fr);
            in.readLine();
            port = Integer.parseInt(in.readLine());
            CAport = Integer.parseInt(in.readLine());
            in.close();
            fr.close();

            if (args.length < 1) {
                System.out.println("Usage: receive <username>");
                return;
            }

            if (args.length > 1) {
                port = Integer.parseInt(args[1]);
            }
            if (args.length > 2) {
                CAport = Integer.parseInt(args[2]);
            }

            String receiver = args[0];

            //The user's private key file is: username + ".priv"

            //String badwords[] = load_badwords();
            ServerSocket srvr = new ServerSocket(port);

            for (; ;) {
                Socket skt = srvr.accept();
                //System.out.print("[incoming message...]\n");

                ObjectInputStream objin = new ObjectInputStream(skt.getInputStream());

                byte[] encryptedAESKey = new byte[128];

                objin.read(encryptedAESKey);
                SealedObject sealedMsgSig = (SealedObject) objin.readObject();
                objin.close();
                skt.close();

                //TODO:
                //1. Read receiver's private key (e.g. "bob.priv")
                //2. Read sender's public key (e.g. "alice.pub")
                //3. Verify signature on revocation list
                //   before testing if sender is on list.
                //4. Decrypt and verify message.
                //   (This should be impossible if the message is intended
                //   for someone else.)
                //5. If message is banned then pass it on to the CA.
                //   Also include sender's signature as evidence
                //6. Otherwise print the timestamp, sender and message

                //Read Receiver's private key
                PrivateKey receiver_PrivateKey = Helper.readPrivateKey(receiver);


                //Decrypt AES Key
                Cipher cipherRSA = Cipher.getInstance(Helper.RSA_A);
                cipherRSA.init(Cipher.DECRYPT_MODE, receiver_PrivateKey);
                byte[] rawAESKey = cipherRSA.doFinal(encryptedAESKey);
                SecretKeySpec AESKeySpec = new SecretKeySpec(rawAESKey, "AES");


                //Decrypt Sealed Object from sender
                IvParameterSpec ivSpec = new IvParameterSpec(Helper.iv);
                Cipher cipherAES = Cipher.getInstance(Helper.AES_A);
                cipherAES.init(Cipher.DECRYPT_MODE, AESKeySpec,ivSpec);
                //cipherAES.init(Cipher.DECRYPT_MODE, AESKeySpec);
                SignedObject signedMessage = (SignedObject) sealedMsgSig.getObject(cipherAES);
                Message msgReceived = (Message) signedMessage.getObject();

                //Read Sender's Public Key
                String sender = msgReceived.getSender();
                PublicKey sender_PublicKey = Helper.readPublicKey(sender);

                //Verify Signatrure
                Signature sig = Signature.getInstance("SHA1withRSA");
                if (!signedMessage.verify(sender_PublicKey, sig)) {
                    System.out.println("Signature failed");
                    System.exit(0);
                }


                Calendar timestamp = msgReceived.getTimestamp();


                boolean is_revoked = Helper.isUserRevoked(sender);

                if (is_revoked) {
                    System.out.print("[Sender is revoked, ignoring message]\n");
                } else if (Helper.is_banned(msgReceived.getChat())) {
                    System.out.print("[Banned message received. Notifying authorities...]\n");

                    Socket CAskt = new Socket(CAaddress, CAport);
                    ObjectOutputStream objout = new ObjectOutputStream(CAskt.getOutputStream());


                    //Read Ca's public key
                    PublicKey ca_PublicKey = Helper.readCAPublicKey();

                    //Get RSA Engine
                    Cipher cipherCA = Cipher.getInstance("RSA/ECB/PKCS1Padding");
                    cipherCA.init(Cipher.ENCRYPT_MODE, ca_PublicKey);

                    //Encrypt AES Key with CA's public key
                    byte[] encryptedAESKeyForCA = cipherCA.doFinal(rawAESKey);


                    Squeal squeal = new Squeal(sealedMsgSig, sender);
                    objout.write(encryptedAESKeyForCA);
                    objout.writeObject(squeal);
                    objout.close();
                    CAskt.close();

                } else {
                    SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss ");
                    System.out.print("(" + formatter.format(timestamp.getTime()) + ") ");
                    System.out.print(sender);
                    System.out.print(": ");
                    System.out.println(msgReceived.getChat());
                }
            }

            //Note: ideally there should be a more graceful way to stop the server
            //and then we'd call srvr.close();

        } catch (BadCertificateException e) {
            System.out.println(e);
            System.out.print("Sender's UserKeyPair is bad\n");
            System.exit(0);
        } catch (Exception e) {
            System.out.println(e);
            e.printStackTrace();
            System.out.print("Error\n");
        }
    }

    private static String[] load_badwords() {
        try {
            FileInputStream fis = new FileInputStream("banned");
            ObjectInputStream ois = new ObjectInputStream(fis);
            String badwords[] = (String[]) ois.readObject();
            ois.close();
            fis.close();
            return badwords;
        } catch (Exception e) {
            System.out.print("Can't read banned words\n");
            String empty[] = new String[0];
            return empty;
        }
    }

    private static boolean is_banned(String message, String badwords[]) {
        int i;
        int n = badwords.length;
        int index;
        for (i = 0; i < n; i++) {
            index = message.indexOf(badwords[i]);
            if (index >= 0) return true;
        }
        return false;
    }


}
