/**
   * Make sure the sketch hasn't been moved or deleted by some nefarious user. If they did, try to
   * re-create it and save. Only checks to see if the main folder is still around, but not its
   * contents.
   */
  private void ensureExistence() {
    if (sketch.getFolder().exists()) return;

    Base.showWarning(
        tr("Sketch Disappeared"),
        tr(
            "The sketch folder has disappeared.\n "
                + "Will attempt to re-save in the same location,\n"
                + "but anything besides the code will be lost."),
        null);
    try {
      sketch.getFolder().mkdirs();

      for (SketchFile file : sketch.getFiles()) {
        file.save(); // this will force a save
      }
      calcModified();

    } catch (Exception e) {
      Base.showWarning(
          tr("Could not re-save sketch"),
          tr(
              "Could not properly re-save the sketch. "
                  + "You may be in trouble at this point,\n"
                  + "and it might be time to copy and paste "
                  + "your code to another text editor."),
          e);
    }
  }
  private File saveSketchInTempFolder() throws IOException {
    File tempFolder = FileUtils.createTempFolder("arduino_modified_sketch_");
    FileUtils.copy(sketch.getFolder(), tempFolder);

    for (SketchFile file :
        Stream.of(sketch.getFiles()).filter(SketchFile::isModified).collect(Collectors.toList())) {
      Files.write(
          Paths.get(tempFolder.getAbsolutePath(), file.getFileName()),
          file.getProgram().getBytes());
    }

    return Paths.get(tempFolder.getAbsolutePath(), sketch.getPrimaryFile().getFileName()).toFile();
  }
  /**
   * Returns true if this is a read-only sketch. Used for the examples directory, or when sketches
   * are loaded from read-only volumes or folders without appropriate permissions.
   */
  public boolean isReadOnly(LibraryList libraries, String examplesPath) {
    String apath = sketch.getFolder().getAbsolutePath();

    Optional<UserLibrary> libraryThatIncludesSketch =
        libraries
            .stream()
            .filter(lib -> apath.startsWith(lib.getInstalledFolder().getAbsolutePath()))
            .findFirst();
    if (libraryThatIncludesSketch.isPresent()
        && !libraryThatIncludesSketch.get().onGoingDevelopment()) {
      return true;
    }

    return sketchIsSystemExample(apath, examplesPath) || sketchFilesAreReadOnly();
  }
  /**
   * Add a file to the sketch.
   *
   * <p>Supported code files will be copied to the sketch folder. All other files will be copied to
   * the "data" folder (which is created if it does not exist yet).
   *
   * @return true if successful.
   */
  public boolean addFile(File sourceFile) {
    String filename = sourceFile.getName();
    File destFile = null;
    boolean isData = false;
    boolean replacement = false;

    if (FileUtils.hasExtension(sourceFile, Sketch.EXTENSIONS)) {
      destFile = new File(sketch.getFolder(), filename);
    } else {
      sketch.prepareDataFolder();
      destFile = new File(sketch.getDataFolder(), filename);
      isData = true;
    }

    // check whether this file already exists
    if (destFile.exists()) {
      Object[] options = {tr("OK"), tr("Cancel")};
      String prompt = I18n.format(tr("Replace the existing version of {0}?"), filename);
      int result =
          JOptionPane.showOptionDialog(
              editor,
              prompt,
              tr("Replace"),
              JOptionPane.YES_NO_OPTION,
              JOptionPane.QUESTION_MESSAGE,
              null,
              options,
              options[0]);
      if (result == JOptionPane.YES_OPTION) {
        replacement = true;
      } else {
        return false;
      }
    }

    // If it's a replacement, delete the old file first,
    // otherwise case changes will not be preserved.
    // http://dev.processing.org/bugs/show_bug.cgi?id=969
    if (replacement) {
      boolean muchSuccess = destFile.delete();
      if (!muchSuccess) {
        Base.showWarning(
            tr("Error adding file"),
            I18n.format(tr("Could not delete the existing ''{0}'' file."), filename),
            null);
        return false;
      }
    }

    // make sure they aren't the same file
    if (isData && sourceFile.equals(destFile)) {
      Base.showWarning(
          tr("You can't fool me"),
          tr(
              "This file has already been copied to the\n"
                  + "location from which where you're trying to add it.\n"
                  + "I ain't not doin nuthin'."),
          null);
      return false;
    }

    // in case the user is "adding" the code in an attempt
    // to update the sketch's tabs
    if (!sourceFile.equals(destFile)) {
      try {
        Base.copyFile(sourceFile, destFile);

      } catch (IOException e) {
        Base.showWarning(
            tr("Error adding file"),
            I18n.format(tr("Could not add ''{0}'' to the sketch."), filename),
            e);
        return false;
      }
    }

    if (!isData) {
      int tabIndex;
      if (replacement) {
        tabIndex = editor.findTabIndex(destFile);
        editor.getTabs().get(tabIndex).reload();
      } else {
        SketchFile sketchFile;
        try {
          sketchFile = sketch.addFile(destFile.getName());
          editor.addTab(sketchFile, null);
        } catch (IOException e) {
          // This does not pass on e, to prevent showing a backtrace for
          // "normal" errors.
          Base.showWarning(tr("Error"), e.getMessage(), null);
          return false;
        }
        tabIndex = editor.findTabIndex(sketchFile);
      }
      editor.selectTab(tabIndex);
    }
    return true;
  }
  /**
   * Handles 'Save As' for a sketch.
   *
   * <p>This basically just duplicates the current sketch folder to a new location, and then calls
   * 'Save'. (needs to take the current state of the open files and save them to the new folder..
   * but not save over the old versions for the old sketch..)
   *
   * <p>Also removes the previously-generated .class and .jar files, because they can cause trouble.
   */
  protected boolean saveAs() throws IOException {
    // get new name for folder
    FileDialog fd = new FileDialog(editor, tr("Save sketch folder as..."), FileDialog.SAVE);
    if (isReadOnly(BaseNoGui.librariesIndexer.getInstalledLibraries(), BaseNoGui.getExamplesPath())
        || isUntitled()) {
      // default to the sketchbook folder
      fd.setDirectory(BaseNoGui.getSketchbookFolder().getAbsolutePath());
    } else {
      // default to the parent folder of where this was
      // on macs a .getParentFile() method is required

      fd.setDirectory(sketch.getFolder().getParentFile().getAbsolutePath());
    }
    String oldName = sketch.getName();
    fd.setFile(oldName);

    fd.setVisible(true);
    String newParentDir = fd.getDirectory();
    String newName = fd.getFile();

    // user canceled selection
    if (newName == null) return false;
    newName = SketchController.checkName(newName);

    File newFolder = new File(newParentDir, newName);

    // check if the paths are identical
    if (newFolder.equals(sketch.getFolder())) {
      // just use "save" here instead, because the user will have received a
      // message (from the operating system) about "do you want to replace?"
      return save();
    }

    // check to see if the user is trying to save this sketch inside itself
    try {
      String newPath = newFolder.getCanonicalPath() + File.separator;
      String oldPath = sketch.getFolder().getCanonicalPath() + File.separator;

      if (newPath.indexOf(oldPath) == 0) {
        Base.showWarning(
            tr("How very Borges of you"),
            tr(
                "You cannot save the sketch into a folder\n"
                    + "inside itself. This would go on forever."),
            null);
        return false;
      }
    } catch (IOException e) {
      // ignore
    }

    // if the new folder already exists, then need to remove
    // its contents before copying everything over
    // (user will have already been warned)
    if (newFolder.exists()) {
      FileUtils.recursiveDelete(newFolder);
    }
    // in fact, you can't do this on windows because the file dialog
    // will instead put you inside the folder, but it happens on osx a lot.

    try {
      sketch.saveAs(newFolder);
    } catch (IOException e) {
      // This does not pass on e, to prevent showing a backtrace for "normal"
      // errors.
      Base.showWarning(tr("Error"), e.getMessage(), null);
    }
    // Name changed, rebuild the sketch menus
    // editor.sketchbook.rebuildMenusAsync();
    editor.base.rebuildSketchbookMenus();
    editor.header.rebuild();

    // Make sure that it's not an untitled sketch
    setUntitled(false);

    // let Editor know that the save was successful
    return true;
  }
  /**
   * This is called upon return from entering a new file name. (that is, from either newCode or
   * renameCode after the prompt) This code is almost identical for both the newCode and renameCode
   * cases, so they're kept merged except for right in the middle where they diverge.
   */
  protected void nameCode(String newName) {
    // make sure the user didn't hide the sketch folder
    ensureExistence();

    newName = newName.trim();
    if (newName.equals("")) return;

    if (newName.charAt(0) == '.') {
      Base.showWarning(tr("Problem with rename"), tr("The name cannot start with a period."), null);
      return;
    }

    FileUtils.SplitFile split = FileUtils.splitFilename(newName);
    if (split.extension.equals("")) split.extension = Sketch.DEFAULT_SKETCH_EXTENSION;

    if (!Sketch.EXTENSIONS.contains(split.extension)) {
      String msg = I18n.format(tr("\".{0}\" is not a valid extension."), split.extension);
      Base.showWarning(tr("Problem with rename"), msg, null);
      return;
    }

    // Sanitize name
    split.basename = BaseNoGui.sanitizeName(split.basename);
    newName = split.join();

    if (renamingCode) {
      SketchFile current = editor.getCurrentTab().getSketchFile();

      if (current.isPrimary()) {
        if (!split.extension.equals(Sketch.DEFAULT_SKETCH_EXTENSION)) {
          Base.showWarning(
              tr("Problem with rename"), tr("The main file cannot use an extension"), null);
          return;
        }

        // Primary file, rename the entire sketch
        final File parent = sketch.getFolder().getParentFile();
        File newFolder = new File(parent, split.basename);
        try {
          sketch.renameTo(newFolder);
        } catch (IOException e) {
          // This does not pass on e, to prevent showing a backtrace for
          // "normal" errors.
          Base.showWarning(tr("Error"), e.getMessage(), null);
          return;
        }

        editor.base.rebuildSketchbookMenus();
      } else {
        // Non-primary file, rename just that file
        try {
          current.renameTo(newName);
        } catch (IOException e) {
          // This does not pass on e, to prevent showing a backtrace for
          // "normal" errors.
          Base.showWarning(tr("Error"), e.getMessage(), null);
          return;
        }
      }

    } else { // creating a new file
      SketchFile file;
      try {
        file = sketch.addFile(newName);
        editor.addTab(file, "");
      } catch (IOException e) {
        // This does not pass on e, to prevent showing a backtrace for
        // "normal" errors.
        Base.showWarning(tr("Error"), e.getMessage(), null);
        return;
      }
      editor.selectTab(editor.findTabIndex(file));
    }

    // update the tabs
    editor.header.rebuild();
  }