/**
   * Downloads the specified file from a peer that is hosting said file
   *
   * @param peer A Socket connected to the peer that is hosting the file
   * @param start The starting byte of the piece that will be downloaded from this peer
   * @param end The end of the piece that will be downloaded from this peer, not inclusive
   * @throws java.io.IOException if an error occurs during the download
   */
  private void downloadFromPeer(Socket peer, int start, int end) throws IOException {
    if (start >= end) {
      throw new IllegalStateException(
          "Start byte (" + start + ") must be less than the end byte (" + end + ")");
    }

    DataInputStream in = new DataInputStream(peer.getInputStream());
    PrintWriter out = new PrintWriter(peer.getOutputStream(), true);

    // Tell peer what file to download and the desired chunk size
    out.println("<GET download " + _file + " " + _chunkSize + " " + start + " " + end + ">");

    // Make sure the chunk size is valid
    String response = in.readUTF();
    switch (response) {
      case "<GET invalid>":
        System.err.println("[DownloadFromPeerRunnable] Error: Invalid chunk size");
        break;
      case "<GET begin>":
        saveFile(in, start, end);

        _callback.onFinishChunk(_file, true);
        break;
      default:
        throw new UnexpectedResponseException(response);
    }
  }
  /**
   * Reads a file from the given InputStream and saves it into the local download directory
   *
   * @param in The InputStream from which to read the file
   * @param start The starting byte of the piece that will be downloaded from this peer
   * @param end The end byte (exclusive) of the piece that will be downloaded from this peer
   * @throws java.io.IOException if an error occurs while downloading the file
   */
  private void saveFile(InputStream in, int start, int end) throws IOException {
    if (start >= end) {
      throw new IllegalStateException(
          "Start byte (" + start + ") must be less than the end byte (" + end + ")");
    }

    RandomAccessFile fileOut = null;
    try {
      File downloadFile = new File(Client.downloadDir, _file);
      // Open the file in read/write mode with synchronous content/metadata writing
      fileOut = new RandomAccessFile(downloadFile, "rws");
      fileOut.seek(start);

      byte[] bytes = new byte[_chunkSize];
      int bytesRead;
      int pos = start;
      while ((bytesRead = in.read(bytes, 0, _chunkSize)) >= 0 && pos < end) {
        fileOut.write(bytes, 0, Math.min(bytesRead, end - pos));
        pos += bytesRead;
      }
    } catch (SocketException e) {
      System.err.println(
          "[DownloadFromPeerRunnable] Disconnected from peer at " + _peer.getInetAddress());
      _callback.onFinishChunk(_file, false);
    } finally {
      if (fileOut != null) {
        try {
          fileOut.close();
        } catch (IOException e) {
          e.printStackTrace();
        }
      }
    }
  }
 /** Run the thread */
 @Override
 public void run() {
   try {
     downloadFromPeer(_peer, _start, _end);
   } catch (IOException e) {
     e.printStackTrace();
     _callback.onFinishChunk(_file, false);
   }
 }