/**
   * Update the progress bar of the currently downloaded file in main window. Only update if progess
   * has at least increased by one percent of the total file size of the downloaded file.
   *
   * @param downloadedBytes The current amount of downloaded bytes
   * @param lastProgBarUpdate The byte count at the last progress bar update
   * @param file The download file
   * @return The byte count at the last progress bar update
   */
  private int updateProgressBar(int downloadedBytes, int lastProgBarUpdate, DownloadFile file) {
    int totalSize = (int) file.getTotalFileSize();

    // only update progess bar if progess has at least increased by one percent
    int diff = downloadedBytes - lastProgBarUpdate;
    int onePercent = (int) totalSize / 100;
    if (diff >= onePercent) {
      final String filename = file.getFilename();
      final int db = downloadedBytes;
      SwingUtilities.invokeLater(
          new Runnable() {
            public void run() {
              mainApp.updateDownloadQueue(filename, db);
            }
          });

      return downloadedBytes; // prog bar updated, so return the new byte count
    }

    return lastProgBarUpdate; // no update, so return the previous byte count
  }
  /** This method starts the thread and begins to download the file. */
  public void run() {
    int maxThreads = Integer.parseInt(mainApp.getPrefValue("ServerSettingsThreadCount"));
    int runningThreads = 0;
    HashMap<String, Integer> downloadedBytes = new HashMap<String, Integer>();
    HashMap<String, Integer> lastProgBarUpdate = new HashMap<String, Integer>();

    // loop at all segments of the download file
    while (!shutdown && (segQueue.hasMoreSegments() || runningThreads > 0)) {
      // more segments to go?
      while (segQueue.hasMoreSegments()
          && runningThreads < maxThreads
          && !pause
          && nioClient.hasFreeSlot()) {
        // get next download segment of the download file
        DownloadFileSegment seg = segQueue.nextSegment();
        if (seg == null) break;
        String filename = seg.getDlFile().getFilename();
        logger.msg("Downloading next segment of file: " + filename, MyLogger.SEV_DEBUG);

        // create new response handler
        RspHandler newHandler = new RspHandler(seg);
        activeRspHandlers.add(newHandler);

        // map the new response handler to the download file
        Vector<RspHandler> tmpVector = dlFileRspHandlerMap.get(seg.getDlFile());
        if (tmpVector == null) tmpVector = new Vector<RspHandler>();
        tmpVector.add(newHandler);
        dlFileRspHandlerMap.put(seg.getDlFile(), tmpVector);

        // start data download
        nioClient.fetchArticleData(seg.getGroups().firstElement(), seg.getArticleId(), newHandler);

        // increase thread counter
        runningThreads++;
      }

      // check if the next element of the result set is already finished
      Vector<RspHandler> toRemoveVector = new Vector<RspHandler>();
      for (int i = 0; i < activeRspHandlers.size(); i++) {
        RspHandler handler = activeRspHandlers.get(i);

        // handle error response from NNTP server
        if (handler.getError() == RspHandler.ERR_NONE) {
          // no error, do nothing
        } else if (handler.getError() == RspHandler.ERR_AUTH) {
          // do nothing for this error (?)
        } else if (handler.getError() == RspHandler.ERR_FETCH) {
          // TODO: handle "430 no such article" error (?)
          String msg =
              "no such article found: <"
                  + handler.dlFileSeg().getArticleId()
                  + "> ("
                  + handler.getErrorMsg()
                  + ")";
          logger.msg(msg, MyLogger.SEV_WARNING);
        } else {
          // all other errors
          shutdown = true;
        }

        // update downloaded byte counter ...
        DownloadFile dlFile = handler.dlFileSeg().getDlFile();
        String filename = dlFile.getFilename();
        int bytes = 0;
        Integer bytesInt = downloadedBytes.get(filename);
        if (bytesInt != null) bytes = bytesInt;
        bytes += handler.newByteCount();
        downloadedBytes.put(filename, bytes);

        // ... and progres bar in main window
        int last = 0;
        Integer lastInt = lastProgBarUpdate.get(filename);
        if (lastInt != null) last = lastInt;
        last = updateProgressBar(bytes, last, dlFile);
        lastProgBarUpdate.put(filename, last);

        // all data downloaded?
        if (handler.isFinished()) {
          toRemoveVector.add(handler);
          runningThreads--;
          decrSegCount(filename); // decrease main window segment
          // counter

          // segment done, so check if whole download file is finished
          // now
          dlFile.removeSegment(handler.dlFileSeg().getIndex());
          if (!dlFile.hasMoreSegments()) {
            try {
              handleFinishedDlFile(dlFile);
            } catch (Exception e) {
              logger.printStackTrace(e);
            }
          }
        }
      }
      activeRspHandlers.removeAll(toRemoveVector);
      toRemoveVector.removeAllElements();

      // all tasks done?
      if (!segQueue.hasMoreSegments() && runningThreads == 0) {
        break;
      }

      try {
        // let the thread sleep a bit
        Thread.sleep(10);
      } catch (InterruptedException e) {
        // shutdown if interrupted
        shutdown = true;
      }
    } // end of main loop

    logger.msg("FileDownloader has finished downloading all files", MyLogger.SEV_DEBUG);
  }
  /**
   * This method is called when a whole download file has been finished downloading. It updates main
   * application window and starts the decoding thread.
   *
   * @param dlFile The DownloadFile object that is finished
   */
  private void handleFinishedDlFile(final DownloadFile dlFile) {
    final String filename = dlFile.getFilename();
    logger.msg("File downloading finished: " + filename, MyLogger.SEV_INFO);

    // notify application that download has finished
    SwingUtilities.invokeLater(
        new Runnable() {
          public void run() {
            mainApp.fileDownloadFinished(filename);
            mainApp.setProgBarToDecoding(filename, dlFile.getSegCount());
          }
        });

    // create result vector
    Vector<byte[]> articleData = new Vector<byte[]>();
    Vector<RspHandler> rspHandlers = dlFileRspHandlerMap.get(dlFile);
    for (int i = 0; i < rspHandlers.size(); i++) {
      byte[] tmpArray = removeFirstLine(rspHandlers.get(i).getData(true));
      articleData.add(tmpArray);
      rspHandlers.set(i, null); // free some memory
    }

    // call garbage collector
    rspHandlers = null;
    dlFileRspHandlerMap.remove(dlFile);
    Runtime.getRuntime().gc();

    logger.msg(
        "First line(s) dump:\n" + HelloNzbToolkit.firstLineFromByteData(articleData.get(0), 2),
        MyLogger.SEV_DEBUG);

    // determine data encoding (yenc or UU)
    String encoding = null;
    boolean bHasData = false;
    for (int i = 0; i < articleData.size(); i++) {
      byte[] abyteHelp = articleData.get(i);
      if (abyteHelp.length > 0) {
        bHasData = true;
        if (bytesEqualsString(abyteHelp, "=ybegin")) {
          encoding = "yenc";
          break;
        } else if (bytesEqualsString(abyteHelp, "begin ")) {
          encoding = "uu";
          break;
        }
      }
    }
    if (encoding == null) {
      if (bHasData) {
        encoding = "yenc";
        logger.msg(
            "No suitable decoder (no data) found for downloaded file: "
                + dlFile.getFilename()
                + " -- Assuming yenc.",
            MyLogger.SEV_WARNING);
      } else {
        // too bad, no decoder found for this file :(
        logger.msg(
            "No suitable decoder found for downloaded file (no data): " + dlFile.getFilename(),
            MyLogger.SEV_ERROR);

        // update main application window
        SwingUtilities.invokeLater(
            new Runnable() {
              public void run() {
                mainApp.fileDecodingFinished(dlFile.getFilename());
              }
            });

        return;
      }
    }

    /*
     * // determine data encoding String encoding = null;
     * if(bytesEqualsString(articleData.get(0), "=ybegin")) encoding =
     * "yenc"; else if(bytesEqualsString(articleData.get(0), "begin "))
     * encoding = "uu"; else { // too bad, no decoder found for this file :(
     * logger.msg("No suitable decoder found for downloaded file: " +
     * dlFile.getFilename(), MyLogger.SEV_ERROR);
     *
     * // update main application window SwingUtilities.invokeLater(new
     * Runnable() { public void run() {
     * mainApp.fileDecodingFinished(dlFile.getFilename()); } } );
     *
     * return; }
     */

    // start data decoding background thread
    FileDecoder fileDecoder = new FileDecoder(mainApp, dlDir, dlFile, articleData, encoding);
    Thread t = new Thread(fileDecoder);
    t.start();
  }