/*
   This client implements only PUT.

   This client takes

      fileName path hostname port

   as optional command-line arguments. The
   contents of "fileName" are uploaded to
   the server, by the PUT method, to the
   resource named "path".

   NOTE: The HTTP PUT and DELETE methods are not supported by HTML forms.
         https://alexanderpetros.com/triptych/form-http-methods
   To test our implementations of the PUT and DELETE methods we need to
   either write a Java client or embed some JavaScript code into a web
   page.
*/

import java.io.File;
import java.io.BufferedInputStream; // read binary file from file system
import java.io.FileInputStream;
import java.io.BufferedReader; // read text input from socket
import java.io.InputStreamReader;
import java.io.DataOutputStream; // write binary output to socket
import java.io.BufferedOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.UnknownHostException;
import java.net.InetAddress;
import java.net.Socket;

public class HttpClient_PUT
{
   private static final int SERVER_PORT = 8080; // Should be above 1023.

   public static void main (String[] args)
   {
      final String fileName;
      if (args.length > 0)
      {
         fileName = args[0];
      }
      else
      {
         fileName = "HttpClient_PUT.java";
      }

      final String path;
      if (args.length > 1)
      {
         path = args[1];
      }
      else
      {
         path = "upLoad_1";
      }

      final String hostName;
      if (args.length > 2)
      {
         hostName = args[2];
      }
      else
      {
         hostName = "localhost";
      }

      final int portNumber;
      if (args.length > 3)
      {
         portNumber = Integer.parseInt(args[3]);
      }
      else
      {
         portNumber = SERVER_PORT;
      }

      // Check for the existence of the file to PUT.
      final File file = new File(fileName);
      if (! file.exists())
      {
         System.out.println("CLIENT: File, " + fileName + ", not found.");
         System.exit(-1);
      }
      if (! file.canRead())
      {
         System.out.println("CLIENT: File, " + fileName + ", is not accessible.");
         System.exit(-1);
      }
      final int numOfBytes = (int)file.length();

      // Read the file from the file system.
      final byte[] fileInBytes = new byte[numOfBytes];
      try (final var inFile = new BufferedInputStream(
                                 new FileInputStream(file)))
      {
         // https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/io/FileInputStream.html#read(byte%5B%5D)
         inFile.read(fileInBytes);
      }
      catch (FileNotFoundException e)
      {
         System.out.println("CLIENT: Unable to find file:" + fileName);
         System.exit(-1);
      }
      catch (IOException e)
      {
         System.out.println("CLIENT: Unable to read file:" + fileName);
         System.exit(-1);
      }

      // Get this client's process id number (PID). This helps
      // to identify the client in TaskManager or TCPView.
      final ProcessHandle handle = ProcessHandle.current();
      final long pid = handle.pid();
      System.out.println("CLIENT: Process ID number (PID): " + pid );

      // Get the name and IP address of the local host and
      // print them on the console for information purposes.
      try
      {
         final InetAddress address = InetAddress.getLocalHost();
         System.out.println("CLIENT: Hostname: " + address.getCanonicalHostName() );
         System.out.println("CLIENT: IP address: " +address.getHostAddress() );
      }
      catch (UnknownHostException e)
      {
         System.out.println("Unable to determine this client's address.");
         System.out.println( e );
      }

      // Make a connection to the server.
      System.out.println("CLIENT: Connecting to server: " + hostName
                       + " on port " + portNumber );

      try (final Socket socket = new Socket(InetAddress.getByName(hostName),
                                            portNumber);
           final var in = new BufferedReader(
                             new InputStreamReader(
                                new BufferedInputStream( // not needed?
                                   socket.getInputStream())));
           final var out = new DataOutputStream(
                              new BufferedOutputStream(
                                 socket.getOutputStream())))
         {
         System.out.println("CLIENT: Connected to server.");
         // Get this client's local port number and log it to the console.
         // This helps to identify this client in the server's transcript.
         final int port = socket.getLocalPort();
         System.out.println("CLIENT: Local Port: " + port);


         // Implement an HTTP PUT transaction.
         // Send the server the request headers.
         try
         {
                   out.writeBytes("PUT " + path + " HTTP/1.1\r\n");
            System.out.println("<PUT " + path + " HTTP/1.1");
                   out.writeBytes("Host: " + hostName + ":" + portNumber + "\r\n");
            System.out.println("<Host: " + hostName + ":" + portNumber);
                   out.writeBytes("Connection: close\r\n");
            System.out.println("<Connection: close");
            // Determine the resource's content type.
            final String contentType = mimeType(fileName);
            out.writeBytes("Content-Type: " + contentType + "\r\n");
            System.out.println("<Content-Type: " + contentType);
            out.writeBytes("Content-Length: " + numOfBytes + "\r\n");
            System.out.println("<Content-Length: " + numOfBytes);
            // Add a blank line to denote end of request headers.
            out.writeBytes("\r\n");
            System.out.println("<");
                   out.flush();
         }
         catch (IOException e)
         {
            System.out.println("CLIENT: Cannot communicate with server.");
            e.printStackTrace();
            System.exit(-1);
         }

         // Send the server the request body (the file to PUT on the server).
         // https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/io/FilterOutputStream.html#write(byte%5B%5D)
         out.write(fileInBytes);
         out.flush();
         System.out.println("<=== File " + fileName + " sent to Server");

         // Receive the response from the server.
         try
         {
            String oneLine;
            // Read response line and response headers from the server.
            // https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/io/BufferedReader.html#readLine()
            while ( (oneLine = in.readLine()) != null ) // read up to eof or a blank line
            {
               System.out.println(">" + oneLine);
               if ( oneLine.isEmpty() )
               {
                  break;
               }
            }
            // Read the entity body (if there is one).
            while ( (oneLine = in.readLine()) != null ) // read up to eof
            {
               System.out.println(oneLine);
            }
         }
         catch (IOException e)
         {
            System.out.println("CLIENT: Cannot receive response from server.");
            System.out.println( e );
         }
      }
      catch (IOException e)
      {
         System.out.println("CLIENT: Cannot connect to server.");
         e.printStackTrace();
         System.exit(-1);
      }
      System.out.println("CLIENT: Closed socket.");
   }


   /**
      Use file name extensions to determine the
      content type (mime type) of a resource file.
      See
         https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/MIME_types
   */
   private static String mimeType(final String resourceName)
   {
      final String contentType;

      // Core web technology file types.
      if ( resourceName.endsWith(".html")
        || resourceName.endsWith(".htm") )
         contentType = "text/html";
      else if ( resourceName.endsWith(".css") )
         contentType = "text/css";
      else if ( resourceName.endsWith(".js") )
         contentType = "application/javascript";
      else if ( resourceName.endsWith(".json") )
         contentType = "application/json";
      else if ( resourceName.endsWith(".php") )
         contentType = "application/x-httpd-php";

      // Important image file types.
      else if ( resourceName.endsWith(".png") )
         contentType = "image/png";
      else if ( resourceName.endsWith(".jpg")
             || resourceName.endsWith(".jpeg") )
         contentType = "image/jpeg";
      else if ( resourceName.endsWith(".svg") )
         contentType = "image/svg+xml";
      else if ( resourceName.endsWith(".wepb") )
         contentType = "image/wepb";
      else if ( resourceName.endsWith(".gif") )
         contentType = "image/gif";

      // Some document types.
      else if ( resourceName.endsWith(".md") )
         contentType = "text/markdown";
      else if ( resourceName.endsWith(".xml") )
         contentType = "text/xml";
      else if ( resourceName.endsWith(".txt") )
         contentType = "text/plain";
      else if ( resourceName.endsWith(".pdf") )
         contentType = "application/pdf";

      // Compressed file types.
      else if ( resourceName.endsWith(".zip") )
         contentType = "application/zip";

      // A default file type.
      else
         contentType = "application/octet-stream";

      return contentType;
   }
}
