package massim;

import massim.util.Log;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.TransformerFactoryConfigurationError;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * Responsible for network actions.
 * Created in 2017.
 * @author ta10
 */
class LoginManager {

    private boolean stopped = false;
    private ServerSocket serverSocket;
    private Thread thread;
    private AgentManager agentManager;


    /**
     * Creates a new listener waiting for incoming connections.
     * @param port the port on which to listen
     * @param backlog the backlog of the socket
     * @param agentMng the agent connection manager
     * @throws IOException if socket with the given data cannot be opened
     */
    LoginManager(AgentManager agentMng, int port, int backlog) throws IOException {
        agentManager = agentMng;
        serverSocket = new ServerSocket(port, backlog, null);
        thread = new Thread(() -> {
            while (!stopped) {
                try {
                    Log.log(Log.Level.DEBUG, "Waiting for connection...");
                    Socket s = serverSocket.accept();
                    Log.log(Log.Level.DEBUG,"Got a connection.");
                    Thread t = new Thread(() -> handleSocket(s));
                    t.start();
                } catch (IOException e) {
                    Log.log(Log.Level.DEBUG,"Stop listening");
                }
            }
        });
    }

    /**
     * Starts listening on the socket.
     */
    void start() {
        thread.start();
    }

    /**
     * Stops listening.
     */
    void stop() {
        try {
            stopped = true;
            serverSocket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * Creates and sends an auth-response message on the given socket.
     * @param s the socket to send on
     * @param result whether the authentication was successful
     */
    private void sendAuthResponse(Socket s, boolean result) {
        try {
            OutputStream out = s.getOutputStream();
            Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
            Element elRoot = doc.createElement("message");
            elRoot.setAttribute("type","auth-response");
            elRoot.setAttribute("timestamp", String.valueOf(System.currentTimeMillis()));
            doc.appendChild(elRoot);
            Element elAuth = doc.createElement("authentication");
            elAuth.setAttribute("result", result? "ok" : "fail");
            elRoot.appendChild(elAuth);
            TransformerFactory.newInstance().newTransformer().transform(new DOMSource(doc), new StreamResult(out));
            out.write(0);
        } catch (IOException | TransformerException | TransformerFactoryConfigurationError | ParserConfigurationException e) {
            Log.log(Log.Level.CRITICAL, "Auth response could not be sent.");
            e.printStackTrace();
        }
    }

    /**
     * Tries to perform agent authentication on the new socket.
     * @param s the socket to use
     */
    private void handleSocket(Socket s) {

        Log.log(Log.Level.DEBUG, "Retrieve authentication from client.");
        DocumentBuilder docBuilder;
        try {
            docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
        } catch (ParserConfigurationException e) {
            e.printStackTrace();
            return;
        }
        String user = "";
        String pass = "";
        try {
            String inString = "";
            InputStream is = s.getInputStream();
            byte b[] = new byte[1];
            while (true) {
                b[0] = (byte)is.read();
                if (b[0] == 0) break;
                else inString += (new String(b,0,1,"UTF-8"));
            }
            Log.log(Log.Level.DEBUG, inString);
            Document authDoc = docBuilder.parse(new ByteArrayInputStream(inString.getBytes()));
            NodeList nl = authDoc.getElementsByTagName("authentication");
            if (nl.getLength() == 0) {
                Log.log(Log.Level.ERROR, "Error while parsing authentication");
                return;
            }
            Element root = (Element)nl.item(0);
            user = root.getAttribute("username");
            pass = root.getAttribute("password");
        } catch (IOException e) {
            Log.log(Log.Level.ERROR, "IO error while receiving authentication");
            e.printStackTrace();
        } catch (SAXException e) {
            Log.log(Log.Level.ERROR, "Error while parsing authentication");
            e.printStackTrace();
        }
        Log.log(Log.Level.NORMAL, "got authentication: username=" + user + " password=" + pass
                + " address=" + s.getInetAddress().getHostAddress());

        // check credentials and act accordingly
        if (agentManager.auth(user, pass)) {
            sendAuthResponse(s, true);
            agentManager.setSocket(s, user);
        }
        else{
            Log.log(Log.Level.CRITICAL,"Got invalid authentication from: " + s.getInetAddress().getHostAddress());
            sendAuthResponse(s,false);
            try { s.close(); } catch (IOException ignored) {}
        }
    }
}

@@ -10,16 +10,100 @@
 assim;%0A%0A
+import massim.protocol.Message;%0Aimport massim.protocol.messagecontent.AuthResponse;%0A
 import m
@@ -2630,23 +2630,49 @@
 cket s, 
-boolean
+AuthResponse.AuthenticationResult
  result)
@@ -2771,493 +2771,80 @@
 c = 
-DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();%0A            Element elRoot = doc.createElement(%22message%22);%0A            elRoot.setAttribute(%22type%22,%22auth-response%22);%0A            elRoot.setAttribute(%22timestamp%22, String.valueOf(System.currentTimeMillis()));%0A            doc.appendChild(elRoot);%0A            Element elAuth = doc.createElement(%22authentication%22);%0A            elAuth.setAttribute(%22result%22, result? %22ok%22 : %22fail%22);%0A            elRoot.appendChild(elAuth
+new Message(System.currentTimeMillis(), new AuthResponse(result)).toXML(
 );%0A 
@@ -3079,39 +3079,8 @@
 ror 
-%7C ParserConfigurationException 
 e) %7B
@@ -5169,20 +5169,52 @@
 onse(s, 
-true
+AuthResponse.AuthenticationResult.OK
 );%0A     
@@ -5423,21 +5423,57 @@
 ponse(s,
-false
+ AuthResponse.AuthenticationResult.FAILED
 );%0A     

use authResponse object for sending authResponse message