//   Course Number: COSC 650 Data Comm/Networks
//    Program Name: ShamirServer.java
//         Authors: Brian Hoffman
//                  Derek Waby
//                  Kypros Ioannou
//                  Harsha V. Reddy
//     Description: This is the main class of the ShamirServer and as such it 
//                  provides the GUI and major control methods. The purpose of 
//                  the program itself is to provide a networked server that 
//                  allows clients to obtain shares or combine shares together
//                  to return the key. In the case of obtaining a share, the
//                  server simply accepts a request message and returns a unique
//                  share. There is no identification required as this is beyond 
//                  the scope of the project, but one could readily make this 
//                  secure by using passwords or the like. The recovery of shares
//                  is accomplished by each client sending one share and if it 
//                  is valid, that client is added to a wait list. Once enough 
//                  clients with valid shares have joind the wait list, they are
//                  sent the key and the list is cleared. The key is sent simply 
//                  to show that the program worked. In a more practical situation
//                  the server could be setup to perform an action such as 
//                  unlocking a vault or lanuching a missle.
//
//                  The program begins by displaying a GUI that asks for the secret
//                  to be shared, a prime number, and the number of shares needed
//                  to recostruct the key. Upon hitting submit on the GUI, the 
//                  program creates an array containing the coefficients of a 
//                  polynomial used to generate shares by Shamir's algorithm,
//                  displays a output window, and launches a ServerExecThread. 
//                  The ServerExecThread runs the server and accepts clients. A
//                  ClientThread is created by the ServerExecThread to handle the
//                  requests for each client. In order to handle these requests,
//                  the threads all call upon the methods in this class to get
//                  information about the actual shares. Available methods include
//                  getShare() and getShareMsg() for generating shares, validate
//                  for checking shares and adding the client to the list of share
//                  holders, and checkComplete() for seeing if enough clients 
//                  have submitted shares that the key can be release.

// Java Core Packages
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.net.*;
import java.util.Random;
import java.util.Vector;
import java.math.BigInteger;

// Jave Extension Packages 
import javax.swing.*;
import javax.swing.border.*;

public class ShamirServer extends JFrame
{ 
    // private class variable
    private Container container;
    private int numRebuild;
    private int lastX = 0; 
    private BigInteger primeNum;
    private BigInteger key;
    private BigInteger [] sCoeff;
    private File outputDir = new File(".");
    private Vector clientThreads = new Vector();
    private Vector clientsWithShares = new Vector();
    private ServerExecThread serverThread;
    
    // GUI Components for inputPanel
    private GriddedPanel inputPanel;
    private JLabel keyLabel;
    private JTextArea keyArea;
    private JLabel primeLabel;
    private JTextArea primeArea;
    private JLabel numRebuildLabel;
    private JTextField numRebuildField;
    private JButton submitButton;
    private JButton resetButton;
    private JButton readButton;
    
    // GUI Components for Server Panel
    private GriddedPanel serverPanel;
    private JLabel serverLabel;
    private JTextArea serverOutArea;
    private JButton endServerButton;
    
    // ------------------------------------------------------------------------
    // constructor - sets up the GUI
    //-------------------------------------------------------------------------
    public ShamirServer()
    {
        // title the window 
        super("Shamir Server");
        
        // get content pane and set layout
        container = getContentPane();
        container.setLayout(new BorderLayout());
        
        // construct the input panel components
        inputPanel = new GriddedPanel();
        inputPanel.setBorder(BorderFactory.createTitledBorder(
                             BorderFactory.createEtchedBorder(), "Input Area",
                             TitledBorder.CENTER, TitledBorder.TOP ));
        keyLabel = new JLabel("Enter key to be shared: ");        
        keyArea = new JTextArea(5, 20);
        keyArea.setLineWrap(true);
        primeLabel = new JLabel("Enter a prime number (Optional): ");
        primeArea = new JTextArea(5,20);
        primeArea.setLineWrap(true);
        numRebuildLabel = new JLabel("Enter number of shares needed to rebuild key:");
        numRebuildField = new JTextField(10);
        submitButton = new JButton("Submit");
        submitButton.addActionListener(new SubmitButtonHandler());
        resetButton = new JButton("Reset"); 
        resetButton.addActionListener(new ResetButtonHandler());
        readButton = new JButton("Read from File");
        readButton.addActionListener(new ReadButtonHandler(this));
        
        // add input components to the panel and then add it to the frame
        inputPanel.addComponent(keyLabel, 1, 1, 3, 1);
        inputPanel.addFilledComponent(new JScrollPane(keyArea), 2, 1, 3, 2,
                                      GridBagConstraints.BOTH);
        inputPanel.addComponent(primeLabel, 4, 1, 3, 1);
        inputPanel.addFilledComponent(new JScrollPane(primeArea), 5, 1, 3, 2,
                                      GridBagConstraints.BOTH);
        inputPanel.addComponent(numRebuildLabel, 7, 1, 3, 1);
        inputPanel.addComponent(numRebuildField, 8, 1, 3, 1);
        inputPanel.addComponent(submitButton, 9, 1, 1, 1);
        inputPanel.addComponent(resetButton, 9, 2, 1, 1);
        inputPanel.addComponent(readButton, 9, 3, 1, 1);
        container.add(inputPanel, BorderLayout.CENTER);
       
        // display the initial GUI
        pack();
        setVisible(true);   
    }
    
    // ------------------------------------------------------------------------
    // submitButtonHandler - computes the coefficients in the Shamir polynomial 
    //                       and calls the runServer() method
    //-------------------------------------------------------------------------
    private class SubmitButtonHandler implements ActionListener
    {
        public void actionPerformed(ActionEvent event)
        {
            try
            {
                // get all of the needed values from the GUI
                key = new BigInteger(keyArea.getText());
                numRebuild = Integer.parseInt(numRebuildField.getText());
                sCoeff = new BigInteger[numRebuild];
                
                // compute the prime number or use the one provided.
                if(primeArea.getText().length() == 0)
                {
                    do {
                        primeNum = new BigInteger(key.bitLength()+4, 10,
                                                  new Random());
                    }
                    while (primeNum.compareTo(key) <= 0);
                }
                else
                {
                    primeNum = new BigInteger(primeArea.getText());
                } 
                
                // build the shamir polynomial by generating coefficients as 
                // random BigInteger's
                sCoeff[0] = key;
                for (int i=1; i<numRebuild; i++)
                {
                    sCoeff[i] = new BigInteger(128, new Random()).mod(primeNum);
                }
                
                // now that we have all the necessaey values, run the server 
                runServer();
            }
            catch(NumberFormatException e)
            {
                JOptionPane.showMessageDialog(null,"Check your number format!",
                        "Number Format Error", JOptionPane.ERROR_MESSAGE);
                return;
            }       
        } // end actionPerformed
    } // end SubmitButtonHandler
    
    // ------------------------------------------------------------------------
    // resetButtonHandler - clears all entry fields
    //-------------------------------------------------------------------------
    private class ResetButtonHandler implements ActionListener
    {
        public void actionPerformed(ActionEvent event)
        {
            keyArea.setText("");
            primeArea.setText("");
            numRebuildField.setText("");
        }
    } 

    // ------------------------------------------------------------------------
    // ReadButtonHandler - reads the key and prime number from a file
    //-------------------------------------------------------------------------
    private class ReadButtonHandler implements ActionListener
    {
        private JFrame parent;
        
        public ReadButtonHandler(JFrame inFrame)
        {
            parent = inFrame;
        }
        
        public void actionPerformed(ActionEvent event)
        {
            // open a file chooser dialog and get the input file
            JFileChooser chooser = new JFileChooser();
            chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
            chooser.setCurrentDirectory(new File("."));
            if(chooser.showOpenDialog(parent) != JFileChooser.APPROVE_OPTION)
                return;
            File fileName = chooser.getSelectedFile();  
            
            // setup a buffered reader for the file
            FileReader theFile;
            BufferedReader inFile = null;
            try
            {
                 theFile = new FileReader(fileName);
                 inFile = new BufferedReader(theFile);
            
                // read in key from the first line 
                String temp = inFile.readLine();
                if (temp == null)
                {
                    JOptionPane.showMessageDialog(null, "File is Empty", 
                                       "Empty File", JOptionPane.ERROR_MESSAGE);
                }
                else // read the prime from the second line if it exists
                {
                    keyArea.setText(temp);
                    temp = inFile.readLine();
                    if (temp != null) primeArea.setText(temp);
                }
            }
            catch(IOException ioException)
            {
                JOptionPane.showMessageDialog(null, "Error reading from file!", 
                                  "File Read Error", JOptionPane.ERROR_MESSAGE);
            }
            finally  // close the file stream
            {
                try
                { if (inFile != null) inFile.close(); }
                catch(IOException e)
                {} // do nothing                     
            }   
        } // end ActionPerformed
    } // end ReadButtonHandler

    // ------------------------------------------------------------------------
    // EndServerButtonHandler - ends the ServerExecThread and redisplays the 
    //                          original GUI
    //-------------------------------------------------------------------------
    private class EndServerButtonHandler implements ActionListener
    {
        public void actionPerformed(ActionEvent event)
        {
            // interrupt the server thread
            serverThread.interrupt();  
             
            // restore inputPanel
            lastX=0;
            keyArea.setText("");
            primeArea.setText("");
            numRebuildField.setText("");
            container.remove(serverPanel);
            container.add(inputPanel, BorderLayout.CENTER);
            container.validate();
            container.repaint();   
        }        
    } 
    // ------------------------------------------------------------------------
    // runServer() - runs the server side of the application
    //-------------------------------------------------------------------------
    public void runServer()
    {
        // place up a server running panel in place of the inputPanel
        serverPanel = new GriddedPanel();
        serverPanel.setBorder(BorderFactory.createTitledBorder(
                              BorderFactory.createEtchedBorder(), "Server Output",
                              TitledBorder.CENTER, TitledBorder.TOP ));
        serverLabel = new JLabel("Server is running");
        serverOutArea = new JTextArea(5,20);
        serverOutArea.setText("");
        endServerButton = new JButton("End Server");
        endServerButton.addActionListener(new EndServerButtonHandler());
        serverPanel.addComponent(serverLabel,1,1);
        serverPanel.addFilledComponent(new JScrollPane(serverOutArea), 2, 1, 2, 2,
                                      GridBagConstraints.BOTH);
        serverPanel.addComponent(endServerButton, 4, 1);
        
        container.remove(inputPanel);
        container.add(serverPanel, BorderLayout.CENTER);
        container.validate();
        container.repaint();
        
        // spawn a thread to handle running of server
        serverThread = new ServerExecThread(this);
        serverThread.start();
    }
    
    // ------------------------------------------------------------------------
    // getShare - returns a secret share based on the passed in value of shareX
    //-------------------------------------------------------------------------
    public BigInteger getShare(BigInteger shareX)
    {
        BigInteger powx = shareX;
        BigInteger value = sCoeff[0];
        
        for (int n=1; n<numRebuild; n++)
        {
            BigInteger term = powx;
            term = term.multiply(sCoeff[n]);
            value = value.add(term);
            value = value.mod(primeNum);
            powx = powx.multiply(powx).mod(primeNum);
        }
        return value;
    }

    // ------------------------------------------------------------------------
    // getShareMsg - returns a secret share to the client 
    //-------------------------------------------------------------------------
    public synchronized ShamirMsg getShareMsg()
    {
        lastX++;
        return new ShamirMsg("share", new BigInteger(Integer.toString(lastX)),
                              getShare(new BigInteger(Integer.toString(lastX))),
                              primeNum);
    }

    // ------------------------------------------------------------------------
    // validateShareMsg - process a share that has arrived from the client 
    //-------------------------------------------------------------------------
    public synchronized boolean validateShareMsg(ShamirMsg inMsg, ClientThread inCT)
    {
        if( !(inMsg.getPrime().equals(primeNum)) ) return false;
        if( !(getShare(inMsg.getX()).equals(inMsg.getY())) ) return false;
        
        // check if share has already been entered
        boolean duplicate = false;
        for (int i=0; i<clientsWithShares.size(); i++)
        {
            if( ((ClientThread) clientsWithShares.get(i)).getX().equals(inMsg.getX()) )
            {
                duplicate = true;
            }
        }
        if (!duplicate) clientsWithShares.add(inCT);
        return !duplicate;
    }
    
    // ------------------------------------------------------------------------
    // checkComplete  - checks if enough clients have sent in valid shares and 
    //                  broadcasts a countdown of the shares needed or the key
    //                  when the countdown hits zero.
    //-------------------------------------------------------------------------
    public synchronized void checkComplete()
    {
        if (clientsWithShares.size() == numRebuild)
        {
            postOutput("Key successfully recovered! \n");
            postOutput("Informing " + Integer.toString(clientsWithShares.size())
                       + " clients.");
            for (int i=0; i<clientsWithShares.size(); i++)
            {
                // broadcast success to those involved
                // System.out.println(Integer.toString(i) + " , " + Integer.toString(clientsWithShares.size()));
                ((ClientThread) clientsWithShares.get(i)).writeMsg( new ShamirMsg(
                                   "key", key, BigInteger.ONE, BigInteger.ONE));
            }
            // clear the clientsWithShares array, I assume they will properly 
            // close the connection themselves with a termination message
            clientsWithShares.clear();
        }
        else
        {
            for (int i=0; i<clientsWithShares.size(); i++)
            {
                BigInteger numRemain = new BigInteger(Integer.toString(numRebuild 
                                       - clientsWithShares.size()));
                ((ClientThread) clientsWithShares.get(i)).writeMsg( new ShamirMsg(
                               "numRemain", new BigInteger(numRemain.toString()),
                               BigInteger.ONE, BigInteger.ONE));
            }
        }
    }
  
    // ------------------------------------------------------------------------
    // postOutput - displays a message to the GUI's output panel
    //-------------------------------------------------------------------------
    public void postOutput(String inStr)
    {
        serverOutArea.append(inStr + "\n");
    }
    
    // ------------------------------------------------------------------------
    // addConnection - adds a connection to the master Vector
    //-------------------------------------------------------------------------
    public synchronized void addClient(ClientThread client)
    {
        clientThreads.add(client);
    }

    // ------------------------------------------------------------------------
    // removeConnection - removes a connection from the masterVector
    //------------------------------------------------------------------------
    public synchronized void removeClient(ClientThread client)
    {
        // if the client has a valid share, remove it from the clientsWithShares
        // vector first
        if (client.hasShare())
        {
            clientsWithShares.remove(client);
        }
        clientThreads.remove(client);
    }
    
    // ------------------------------------------------------------------------
    // cleanClients - clears all clients 
    //------------------------------------------------------------------------
    public void cleanClients()
    {
        // interrupt each one so it stops and then remove it from the vector
        for (int i=0; i<clientThreads.size(); i++)
        {
            ClientThread temp = (ClientThread) clientThreads.get(i);
            temp.interrupt();
            removeClient(temp);
        }
    }    
    
    // ------------------------------------------------------------------------
    // main - executes application
    //------------------------------------------------------------------------- 
    public static void main (String args[])
    {
        ShamirServer application = new ShamirServer();
        application.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }       
}