@Override
 public String toString() {
   return "FlashTarget [target ID="
       + TargetTypes.toString(mTarget.getId())
       + ", data.length="
       + mData.length
       + ", type="
       + mType
       + ", startPage="
       + mStartPage
       + "]";
 }
  // def _internal_flash(self, target, current_file_number=1, total_files=1):
  public void internalFlash(FlashTarget flashTarget, int currentFileNo, int totalFiles) {
    Target t_data = flashTarget.getTarget();
    byte[] image = flashTarget.getData();
    int pageSize = t_data.getPageSize();
    int startPage = flashTarget.getStartPage();

    String flashingTo =
        "Flashing to " + TargetTypes.toString(t_data.getId()) + " (" + flashTarget.getType() + ")";
    mLogger.info(flashingTo);
    notifyUpdateStatus(flashingTo);

    // if len(image) > ((t_data.flash_pages - start_page) * t_data.page_size):
    if (image.length > ((t_data.getFlashPages() - startPage) * pageSize)) {
      mLogger.error("Error: Not enough space to flash the image file.");
      // raise Exception()
      return;
    }

    int noOfPages = (image.length / pageSize) + 1;
    mLogger.info(image.length - 1 + " bytes (" + noOfPages + " pages) ");

    // For each page
    int bufferCounter = 0; // Buffer counter
    int i = 0;
    for (i = 0; i < ((image.length - 1) / pageSize) + 1; i++) {
      // Load the buffer
      int end = 0;
      if (((i + 1) * pageSize) > image.length) {
        // buff = image[i * t_data.page_size:]
        end = image.length;
      } else {
        // buff = image[i * t_data.page_size:(i + 1) * t_data.page_size])
        end = (i + 1) * pageSize;
      }
      byte[] buffer = Arrays.copyOfRange(image, i * pageSize, end);

      notifyUpdateProgress(i + 1, noOfPages);

      if (isCancelled()) {
        break;
      }

      this.mCload.uploadBuffer(t_data.getId(), bufferCounter, 0, buffer);

      bufferCounter++;

      // Flash when the complete buffers are full
      if (bufferCounter >= t_data.getBufferPages()) {
        String buffersFull = "Flashing page " + (i + 1) + "...";
        mLogger.info(buffersFull);
        notifyUpdateStatus(buffersFull);
        notifyUpdateProgress(i + 1, noOfPages);
        if (!this.mCload.writeFlash(
            t_data.getId(), 0, startPage + i - (bufferCounter - 1), bufferCounter)) {
          handleFlashError();
          // raise Exception()
          return;
        }
        bufferCounter = 0;
      }
    }
    if (isCancelled()) {
      mLogger.info("Flashing cancelled!");
      return;
    }
    if (bufferCounter > 0) {
      mLogger.info("BufferCounter: " + bufferCounter);
      notifyUpdateProgress(i, noOfPages);
      if (!this.mCload.writeFlash(
          t_data.getId(),
          0,
          (startPage + ((image.length - 1) / pageSize)) - (bufferCounter - 1),
          bufferCounter)) {
        handleFlashError();
        // raise Exception()
        return;
      }
    }
    mLogger.info("Flashing done!");
    notifyUpdateStatus("Flashing done!");
  }
  // package private for tests
  List<FlashTarget> getFlashTargets(File file, String... targetNames) throws IOException {
    List<FlashTarget> filesToFlash = new ArrayList<FlashTarget>();

    if (!file.exists()) {
      mLogger.error(file + " not found.");
      return filesToFlash;
    }

    // check if supplied targetNames are known TargetTypes, if so, continue, else return

    if (isZipFile(file)) {
      // unzip
      unzip(file);

      // read manifest.json
      File basePath =
          new File(file.getAbsoluteFile().getParent() + "/" + getFileNameWithoutExtension(file));
      File manifestFile = new File(basePath.getAbsolutePath(), MANIFEST_FILENAME);
      if (basePath.exists() && manifestFile.exists()) {
        Manifest mf = null;
        try {
          mf = readManifest(manifestFile);
        } catch (IOException ioe) {
          mLogger.error("Error while trying to read manifest file:\n" + ioe.getMessage());
        }
        // TODO: improve error handling
        if (mf == null) {
          return filesToFlash;
        }
        Set<String> files = mf.getFiles().keySet();

        // iterate over file names in manifest.json
        for (String fileName : files) {
          FirmwareDetails firmwareDetails = mf.getFiles().get(fileName);
          Target t =
              this.mCload.getTargets().get(TargetTypes.fromString(firmwareDetails.getTarget()));
          if (t != null) {
            // use path to extracted file
            // File flashFile = new File(file.getParent() + "/" + file.getName() + "/" + fileName);
            File flashFile = new File(basePath.getAbsolutePath(), fileName);
            FlashTarget ft =
                new FlashTarget(
                    t,
                    readFile(flashFile),
                    firmwareDetails.getType(),
                    t.getStartPage()); // TODO: does startPage HAVE to be an extra argument!?
            // (it's already included in Target)
            // add flash target
            // if no target names are specified, flash everything
            if (targetNames == null || targetNames.length == 0 || targetNames[0].isEmpty()) {
              // deal with different platforms (CF1, CF2)
              // TODO: simplify
              if (t.getFlashPages() == 128
                  && "cf1".equalsIgnoreCase(firmwareDetails.getPlatform())) { // 128 = CF 1.0
                filesToFlash.add(ft);
                // deal with STM32 and NRF51 for CF2 (different no of flash pages)
              } else if ((t.getFlashPages() == 1024 || t.getFlashPages() == 232)
                  && "cf2".equalsIgnoreCase(firmwareDetails.getPlatform())) { // 1024 = CF 2.0
                filesToFlash.add(ft);
              }
            } else {
              // else flash only files whose targets are contained in targetNames
              if (Arrays.asList(targetNames).contains(firmwareDetails.getTarget())) {
                filesToFlash.add(ft);
              }
            }
          } else {
            mLogger.error("No target found for " + firmwareDetails.getTarget());
          }
        }
      } else {
        mLogger.error("Zip file " + file.getName() + " does not include a " + MANIFEST_FILENAME);
      }
    } else { // File is not a Zip file
      // add single flash target
      if (targetNames == null || targetNames.length != 1) {
        mLogger.error("Not an archive, must supply ONE target to flash.");
      } else {
        for (String tn : targetNames) {
          if (!tn.isEmpty()) {
            Target target = this.mCload.getTargets().get(TargetTypes.fromString(tn));
            FlashTarget ft =
                new FlashTarget(target, readFile(file), "binary", target.getStartPage());
            filesToFlash.add(ft);
          }
        }
      }
    }
    return filesToFlash;
  }