/**
   * Rename Episode (PLUS all Episodes having the same MediaFile!!!).
   *
   * @param episode the Episode
   */
  public static void renameEpisode(TvShowEpisode episode) {
    // test for valid season/episode number
    if (episode.getSeason() < 0 || episode.getEpisode() < 0) {
      LOGGER.warn(
          "failed to rename episode "
              + episode.getTitle()
              + " (TV show "
              + episode.getTvShow().getTitle()
              + ") - invalid season/episode number");
      MessageManager.instance.pushMessage(
          new Message(
              MessageLevel.ERROR,
              episode.getTvShow().getTitle(),
              "tvshow.renamer.failedrename",
              new String[] {episode.getTitle()}));
      return;
    }

    LOGGER.info(
        "Renaming TvShow '" + episode.getTvShow().getTitle() + "' Episode " + episode.getEpisode());
    for (MediaFile mf : new ArrayList<MediaFile>(episode.getMediaFiles())) {
      renameMediaFile(mf, episode.getTvShow());
    }
  }
  /**
   * Find additional episode files.<br>
   * adds everything which starts with "videoFile name"<br>
   * scans subs/subtitle directories aswell
   *
   * @param episode the episode
   * @param videoFile the video file
   * @param directoryContents the directory contents
   * @return indicator whether something new has been found or not
   */
  private boolean findAdditionalEpisodeFiles(
      TvShowEpisode episode, File videoFile, File[] directoryContents) {
    boolean newFileFound = false;
    List<MediaFile> existingMediaFiles = episode.getMediaFiles();

    for (File file : directoryContents) {
      if (file.isFile()) {
        MediaFile mf = new MediaFile(file);
        if (existingMediaFiles.contains(mf)) {
          continue;
        }
        if (mf.getType().equals(MediaFileType.VIDEO)
            || !mf.getBasename().startsWith(FilenameUtils.getBaseName(videoFile.getName()))
            || file.getName().startsWith(skipFilesStartingWith)) { // MacOS ignore)
          continue;
        }
        if (mf.getType() == MediaFileType.SUBTITLE) {
          episode.setSubtitles(true);
        }
        // check if it is a poster
        if (mf.getType() == MediaFileType.GRAPHIC) {
          LOGGER.debug("parsing unknown graphic " + mf.getFilename());
          String vfilename = FilenameUtils.getBaseName(videoFile.getName());
          if (vfilename.equals(FilenameUtils.getBaseName(mf.getFilename())) // basename match
              || Utils.cleanStackingMarkers(vfilename)
                  .trim()
                  .equals(FilenameUtils.getBaseName(mf.getFilename())) // basename w/o stacking
              || episode
                  .getTitle()
                  .equals(FilenameUtils.getBaseName(mf.getFilename()))) { // title match
            mf.setType(MediaFileType.THUMB);
          }
        }

        episode.addToMediaFiles(mf);
        newFileFound = true;
      } else {
        if (file.getName().equalsIgnoreCase("subs")
            || file.getName().equalsIgnoreCase("subtitle")) {
          File[] subDirContent = file.listFiles();
          if (subDirContent == null) {
            LOGGER.error("Whops. Cannot access directory: " + file.getName());
          } else {
            for (File subDirFile : subDirContent) {
              if (FilenameUtils.getBaseName(subDirFile.getName())
                  .startsWith(FilenameUtils.getBaseName(videoFile.getName()))) {
                MediaFile mf = new MediaFile(subDirFile);
                if (existingMediaFiles.contains(mf)) {
                  continue;
                }
                if (mf.getType() == MediaFileType.SUBTITLE) {
                  episode.setSubtitles(true);
                }
                episode.addToMediaFiles(mf);
                newFileFound = true;
              }
            }
          }
        }
      }
    }

    return newFileFound;
  }
  /**
   * Creates the new file/folder name according to template string
   *
   * @param template the template
   * @param show the TV show
   * @param episodes the TV show episodes; nullable for TV show root foldername
   * @return the string
   */
  public static String createDestination(
      String template, TvShow show, List<TvShowEpisode> episodes) {
    String newDestination = template;
    TvShowEpisode firstEp = null;

    // replace token show title ($N)
    if (newDestination.contains("$N")) {
      newDestination = replaceToken(newDestination, "$N", show.getTitle());
    }

    // parse out episode depended tokens - for multi EP naming
    if (!episodes.isEmpty()) {
      Matcher matcher = multiEpisodeTokenPattern.matcher(template);
      String episodeTokens = "";

      if (matcher.find()) {
        episodeTokens = matcher.group(0);
      }

      String combinedEpisodeParts = "";
      for (TvShowEpisode episode : episodes) {
        String episodePart = episodeTokens;

        // remember first episode for media file tokens
        if (firstEp == null) {
          firstEp = episode;
        }

        // Season w/o leading zeros ($1)
        if (episodePart.contains("$1")) {
          episodePart = replaceToken(episodePart, "$1", String.valueOf(episode.getSeason()));
        }

        // Season leading zeros ($2)
        if (episodePart.contains("$2")) {
          episodePart = replaceToken(episodePart, "$2", lz(episode.getSeason()));
        }

        // DVD-Season w/o leading zeros ($3)
        if (episodePart.contains("$3")) {
          episodePart = replaceToken(episodePart, "$3", String.valueOf(episode.getDvdSeason()));
        }

        // DVD-Season leading zeros ($4)
        if (episodePart.contains("$4")) {
          episodePart = replaceToken(episodePart, "$4", lz(episode.getDvdSeason()));
        }

        // episode number
        if (episodePart.contains("$E")) {
          episodePart = replaceToken(episodePart, "$E", lz(episode.getEpisode()));
        }

        // DVD-episode number
        if (episodePart.contains("$D")) {
          episodePart = replaceToken(episodePart, "$D", lz(episode.getDvdEpisode()));
        }

        // episode title
        if (episodePart.contains("$T")) {
          episodePart = replaceToken(episodePart, "$T", episode.getTitle());
        }

        combinedEpisodeParts += episodePart + " ";
      }

      // and now fill in the (multiple) episode parts
      if (StringUtils.isNotBlank(episodeTokens)) {
        newDestination = newDestination.replace(episodeTokens, combinedEpisodeParts);
      }
    } else {
      // we're in either TV show folder or season folder generation;
      // strip out episode tokens
      newDestination = newDestination.replace("$E", "");
      newDestination = newDestination.replace("$T", "");
    }

    // replace token year ($Y)
    if (newDestination.contains("$Y")) {
      if (show.getYear().equals("0")) {
        newDestination = newDestination.replace("$Y", "");
      } else {
        newDestination = replaceToken(newDestination, "$Y", show.getYear());
      }
    }

    if (firstEp != null && firstEp.getMediaFiles(MediaFileType.VIDEO).size() > 0) {
      MediaFile mf = firstEp.getMediaFiles(MediaFileType.VIDEO).get(0);
      // replace token resolution ($R)
      if (newDestination.contains("$R")) {
        newDestination = replaceToken(newDestination, "$R", mf.getVideoResolution());
      }

      // replace token audio codec + channels ($A)
      if (newDestination.contains("$A")) {
        newDestination =
            replaceToken(
                newDestination,
                "$A",
                mf.getAudioCodec()
                    + (mf.getAudioCodec().isEmpty() ? "" : "-")
                    + mf.getAudioChannels());
      }

      // replace token video codec + format ($V)
      if (newDestination.contains("$V")) {
        newDestination =
            replaceToken(
                newDestination,
                "$V",
                mf.getVideoCodec()
                    + (mf.getVideoCodec().isEmpty() ? "" : "-")
                    + mf.getVideoFormat());
      }

      // replace token video format ($F)
      if (newDestination.contains("$F")) {
        newDestination = replaceToken(newDestination, "$F", mf.getVideoFormat());
      }
    } else {
      // no mediafiles; remove at least token (if available)
      newDestination = newDestination.replace("$R", "");
      newDestination = newDestination.replace("$A", "");
      newDestination = newDestination.replace("$V", "");
      newDestination = newDestination.replace("$F", "");
    }

    // replace empty brackets
    newDestination = newDestination.replaceAll("\\(\\)", "");
    newDestination = newDestination.replaceAll("\\[\\]", "");

    // if there are multiple file separators in a row - strip them out
    if (SystemUtils.IS_OS_WINDOWS) {
      // we need to mask it in windows
      newDestination = newDestination.replaceAll("\\\\{2,}", "\\\\");
      newDestination = newDestination.replaceAll("^\\\\", "");
    } else {
      newDestination = newDestination.replaceAll(File.separator + "{2,}", File.separator);
      newDestination = newDestination.replaceAll("^" + File.separator, "");
    }

    // ASCII replacement
    if (SETTINGS.isAsciiReplacement()) {
      newDestination = StrgUtils.convertToAscii(newDestination, false);
    }

    // trim out unnecessary whitespaces
    newDestination = newDestination.trim();

    // any whitespace replacements?
    if (SETTINGS.isRenamerSpaceSubstitution()) {
      newDestination = newDestination.replaceAll(" ", SETTINGS.getRenamerSpaceReplacement());
    }

    // replace trailing dots and spaces
    newDestination = newDestination.replaceAll("[ \\.]+$", "");

    return newDestination.trim();
  }