content: package com.ammar.sharing.network;
  
  import android.graphics.Bitmap;
  import android.util.Log;
  
  import com.ammar.sharing.custom.io.ProgressManager;
  import com.ammar.sharing.custom.io.ProgressOutputStream;
  import com.ammar.sharing.models.Sharable;
  import com.ammar.sharing.models.SharableApp;
  import com.ammar.sharing.models.User;
  
  import java.io.ByteArrayOutputStream;
  import java.io.FileInputStream;
  import java.io.IOException;
  import java.io.InputStream;
  import java.io.OutputStream;
  import java.net.Socket;
  import java.util.Arrays;
  import java.util.Locale;
  import java.util.Map;
  import java.util.TreeMap;
  import java.util.zip.ZipEntry;
  import java.util.zip.ZipOutputStream;
  
  public class Response {
      private final Socket clientSocket;
  
      public Response(Socket clientSocket) {
          this.clientSocket = clientSocket;
          statusCode = 200;
      }
  
      public void sendFileResponse(Sharable file, User user) {
          sendFileResponse(file, true, user);
      }
  
  
      public void sendFileResponse(Sharable file, boolean progress, User user) {
          ProgressManager progressManager = null;
          if (progress) {
              progressManager = new ProgressManager(file, clientSocket, file.getSize(), user, ProgressManager.OP.DOWNLOAD);
              progressManager.setDisplayName(file.getName());
              progressManager.setFileUUID(file.getUUID());
          }
  
          try {
              OutputStream out;
  
              if (progress)
                  out = new ProgressOutputStream(clientSocket.getOutputStream(), progressManager);
              else
                  out = clientSocket.getOutputStream();
  
              long size = file.getSize();
  
              setHeader("Content-Length", String.valueOf(size));
              setHeader("Content-Disposition", String.format("attachment; filename=\"%s\"", file.getFileName()));
              setHeader("Content-Type", file.getMimeType());
              setHeader("Accept-Ranges", "bytes");
              writeHeaders(out);
              try (InputStream input = file.openInputStream()) {
                  int bytesRead;
                  byte[] buffer = new byte[2048];
                  while ((bytesRead = input.read(buffer)) != -1) {
                      out.write(buffer, 0, bytesRead);
                  }
                  out.flush();
              }
  
              if (progress)
                  progressManager.reportCompleted();
  
          } catch (IOException e) {
              if (progress)
                  progressManager.reportStopped();
              Log.e("MYLOG", "SendFileResponse(). Exception: " + e.getMessage());
          }
      }
  
      public void resumePausedFileResponse(Sharable file, long start, User user) {
          // get the stopped progress manager
          ProgressManager progressManager = null;
          for (ProgressManager i : ProgressManager.progresses) {
              if (file.getUUID().equals(i.getFileUUID())) {
                  progressManager = i;
                  break;
              }
          }
          try (InputStream input = file.openInputStream()) {
              long n = input.skip(start);
              if (progressManager == null || n != start) {
                  setStatusCode(400);
                  sendResponse();
                  return;
              }
              setStatusCode(206);
              setHeader("Content-Length", String.valueOf(file.getSize() - start));
              setHeader("Content-Disposition", String.format("attachment; filename=\"%s\"", file.getFileName()));
              setHeader("Content-Type", file.getMimeType());
              setHeader("Accept-Ranges", "bytes");
              setHeader("Content-Range", String.format(Locale.ENGLISH, "bytes %d-%d/%d", start, file.getSize() - 1, file.getSize()));
  
  
              OutputStream out = new ProgressOutputStream(clientSocket.getOutputStream(), progressManager);
              writeHeaders(out);
              progressManager.setLoaded(start);
  
              int bytesRead;
              byte[] buffer = new byte[2048];
              while ((bytesRead = input.read(buffer)) != -1) {
                  out.write(buffer, 0, bytesRead);
              }
              out.flush();
              progressManager.reportCompleted();
          } catch (IOException e) {
              if (progressManager != null)
                  progressManager.reportStopped();
  
              Log.e("MYLOG", "Response.resumePausedFileResponse. IOException: " + e.getMessage());
          }
  
      }
  
      public void sendZippedFilesResponse(Sharable[] files, String filename, User user) {
          long totalSize = 0;
          for(Sharable i : files) {
              if( i instanceof SharableApp && ((SharableApp) i).hasSplits() ) {
                  Sharable[] splits = ((SharableApp) i).getSplits();
                  for (Sharable j : splits) {
                      totalSize += j.getSize();
                  }
              }
              totalSize += i.getSize();
          }
  
          ProgressManager progressManager = new ProgressManager(files[0], clientSocket, totalSize, user, ProgressManager.OP.DOWNLOAD);
          progressManager.setFileUUID(files[0].getUUID());
          progressManager.setDisplayName(filename);
          try {
              OutputStream out = clientSocket.getOutputStream();
              ByteArrayOutputStream bout = new ByteArrayOutputStream(2048);
              ZipOutputStream zout = new ZipOutputStream(bout);
  
              setHeader("Content-Type", "application/octet-stream");
  
              filename = filename.replace("\"", "\\\"");
              setHeader("Content-Disposition", String.format("attachment; filename=\"%s\"", filename));
              setHeader("Transfer-Encoding", "chunked");
              setHeader("Accept-Ranges", "none");
  
              writeHeaders(out);
              zout.setMethod(ZipOutputStream.DEFLATED);
              for (Sharable i : files) {
  
                  if(i instanceof SharableApp && ((SharableApp) i).hasSplits()) {
                      // in case zipped file response has an app with splits in it
                      // we include the apks file in the zip response
                      SharableApp app = (SharableApp) i;
                      ZipEntry zipEntry = new ZipEntry(i.getName() + ".apks");
                      zout.putNextEntry(zipEntry);
  
                      Sharable[] appApkFiles = new Sharable[app.getSplits().length + 1];
                      appApkFiles[0] = app;
                      System.arraycopy(app.getSplits(), 0, appApkFiles, 1, app.getSplits().length);
  
                      ZipOutputStream apksZout = new ZipOutputStream(zout);
                      for( Sharable apk : appApkFiles ) {
                          ZipEntry apkEntry = new ZipEntry(apk.getFileName());
                          apksZout.putNextEntry(apkEntry);
                          __writeFileZippedToSocket(apk, out, apksZout, bout, progressManager);
                          apksZout.closeEntry();
                      }
                      apksZout.finish();
                  } else {
                      // this is the normal case where Sharable is one file
                      ZipEntry zipEntry = new ZipEntry(i.getFileName());
                      zout.putNextEntry(zipEntry);
                      __writeFileZippedToSocket(i, out, zout, bout, progressManager);
                  }
                  zout.closeEntry();
              }
  
              // write the end of the zip file and the last chunk
              zout.finish();
              out.write(String.format("%X\r\n", bout.size()).getBytes());
              out.write(bout.toByteArray());
              out.write("\r\n0\r\n\r\n".getBytes());
  
              progressManager.reportCompleted();
  
          } catch (IOException e) {
              progressManager.reportStopped();
              Log.e("MYLOG", "sendZippedFilesResponse(). Exception: " + e.getMessage());
          }
      }
  
      public void sendApksFileResponse(Sharable[] files, User user) {
          long totalSize = 0;
          for(Sharable i : files) {
              totalSize += i.getSize();
          }
  
          ProgressManager progressManager = new ProgressManager(files[0], clientSocket, totalSize, user, ProgressManager.OP.DOWNLOAD);
          progressManager.setFileUUID(files[0].getUUID());
          progressManager.setDisplayName(files[0].getName());
          try {
              OutputStream out = clientSocket.getOutputStream();
              ByteArrayOutputStream bout = new ByteArrayOutputStream(2048);
              ZipOutputStream zout = new ZipOutputStream(bout);
  
              setHeader("Content-Type", "application/octet-stream");
              setHeader("Content-Disposition", String.format("attachment; filename=\"%s\"", files[0].getName() + ".apks"));
              setHeader("Transfer-Encoding", "chunked");
              setHeader("Accept-Ranges", "none");
  
              writeHeaders(out);
  
              zout.setMethod(ZipOutputStream.DEFLATED);
              for (Sharable i : files) {
                  ZipEntry zipEntry = new ZipEntry(i.getFileName());
                  zout.putNextEntry(zipEntry);
  
                  FileInputStream fin = new FileInputStream(i.getFilePath());
                  byte[] buffer = new byte[8192];
                  int bytesRead;
                  while ((bytesRead = fin.read(buffer)) != -1) {
                      zout.write(buffer, 0, bytesRead);
                      byte[] buf = bout.toByteArray();
                      // write chunk to client socket
                      if (buf.length != 0) {
                          out.write(String.format("%x\r\n", buf.length).getBytes());
                          out.write(buf);
                          out.write("\r\n".getBytes());
                      }
                      Log.d("HTTP", String.format("%x\\r\\n", buf.length) + new String(buf) + "\\r\\n");
                      bout.reset();
                      progressManager.accumulateLoaded(bytesRead);
                  }
                  fin.close();
                  zout.closeEntry();
              }
  
              // write the end of the zip file and the last chunk
              zout.finish();
              out.write(String.format("%X\r\n", bout.size()).getBytes());
              out.write(bout.toByteArray());
              out.write("\r\n0\r\n\r\n".getBytes());
  
              progressManager.reportCompleted();
  
          } catch (IOException e) {
              progressManager.reportStopped();
              Log.e("MYLOG", "sendZippedFilesResponse(). Exception: " + e.getMessage());
          }
      }
  
      // this function is used for sendZippedFilesResponse
      // don't ask me about how it works.
      private static void __writeFileZippedToSocket(Sharable file, OutputStream clientOut, ZipOutputStream zout, ByteArrayOutputStream bout, ProgressManager progressManager) throws IOException {
          InputStream fin = file.openInputStream();
          byte[] buffer = new byte[2048];
          int bytesRead;
  
          while ((bytesRead = fin.read(buffer)) != -1) {
              zout.write(buffer, 0, bytesRead);
              byte[] buf = bout.toByteArray();
  
              // write chunk length to client socket
              if (buf.length != 0) {
                  clientOut.write(String.format("%x\r\n", buf.length).getBytes());
                  clientOut.write(buf);
                  clientOut.write("\r\n".getBytes());
              }
              bout.reset();
              progressManager.accumulateLoaded(bytesRead);
          }
          fin.close();
      }
  
      public void sendResponse(byte[] content) {
          try {
              OutputStream out = clientSocket.getOutputStream();
              setHeader("Content-Length", String.valueOf(content.length));
              writeHeaders(out);
              out.write(content);
              out.flush();
  
          } catch (IOException e) {
              Log.e("MYLOG", "Response.sendResponse(byte[])" + e.getMessage());
          }
      }
  
      public void sendResponse() {
          try {
              OutputStream out = clientSocket.getOutputStream();
              writeHeaders(out);
              out.flush();
          } catch (IOException e) {
              Log.e("MYLOG", "Response.sendResponse()" + e.getMessage());
          }
      }
  
      public void sendBitmapResponse(Bitmap bitmap) {
          ByteArrayOutputStream buffer = new ByteArrayOutputStream(4096);
          bitmap.compress(Bitmap.CompressFormat.PNG, 100, buffer);
          Log.d("MYLOG", "Bitmap buffer size: " + buffer.size());
          setContentType("image/png");
          sendResponse(buffer.toByteArray());
      }
  
      public void setHeader(String key, String value) {
          headers.put(key, value);
      }
  
      public void setContentType(String contentType) {
          setHeader("Content-Type", contentType);
      }
  
      private void writeTopHeader(OutputStream out, int statusCode) throws IOException {
          String readableStatus = "";
          switch (statusCode) {
              case 101:
                  readableStatus = "Switching Protocols";
                  break;
              case 200:
                  readableStatus = "OK";
                  break;
              case 206:
                  readableStatus = "Partial Content";
                  break;
              case 404:
                  readableStatus = "Not found";
                  break;
              case 400:
                  readableStatus = "Bad request";
                  break;
          }
          byte[] buffer = String.format(Locale.ENGLISH, "HTTP/1.1 %d %s\r\n", statusCode, readableStatus).getBytes();
          out.write(buffer);
      }
  
      private void writeHeaders(OutputStream out) throws IOException {
          writeTopHeader(out, statusCode);
          for (Map.Entry<String, String> i : headers.entrySet()) {
              byte[] header = String.format(Locale.ENGLISH, "%s: %s\r\n", i.getKey(), i.getValue()).getBytes();
              out.write(header);
          }
          out.write("\r\n".getBytes());
      }
  
  
      private int statusCode;
  
      public void setStatusCode(int statusCode) {
          this.statusCode = statusCode;
      }
  
      private final Map<String, String> headers = new TreeMap<>();
  
  
      public void close() throws IOException {
          clientSocket.close();
      }
  }
  