/**
   * Read v1 tag
   *
   * @param file
   * @param newFile
   * @param loadOptions
   * @throws IOException
   */
  private void readV1Tag(File file, RandomAccessFile newFile, int loadOptions) throws IOException {
    if ((loadOptions & LOAD_IDV1TAG) != 0) {
      logger.finer("Attempting to read id3v1tags");
      try {
        id3v1tag = new ID3v11Tag(newFile, file.getName());
      } catch (TagNotFoundException ex) {
        logger.config("No ids3v11 tag found");
      }

      try {
        if (id3v1tag == null) {
          id3v1tag = new ID3v1Tag(newFile, file.getName());
        }
      } catch (TagNotFoundException ex) {
        logger.config("No id3v1 tag found");
      }
    }
  }
  /**
   * Check can write to file
   *
   * @param file
   * @throws IOException
   */
  public void precheck(File file) throws IOException {
    if (!file.exists()) {
      logger.severe(
          ErrorMessage.GENERAL_WRITE_FAILED_BECAUSE_FILE_NOT_FOUND.getMsg(file.getName()));
      throw new IOException(
          ErrorMessage.GENERAL_WRITE_FAILED_BECAUSE_FILE_NOT_FOUND.getMsg(file.getName()));
    }

    if (!file.canWrite()) {
      logger.severe(ErrorMessage.GENERAL_WRITE_FAILED.getMsg(file.getName()));
      throw new IOException(ErrorMessage.GENERAL_WRITE_FAILED.getMsg(file.getName()));
    }

    if (file.length() <= MINIMUM_FILESIZE) {
      logger.severe(
          ErrorMessage.GENERAL_WRITE_FAILED_BECAUSE_FILE_IS_TOO_SMALL.getMsg(file.getName()));
      throw new IOException(
          ErrorMessage.GENERAL_WRITE_FAILED_BECAUSE_FILE_IS_TOO_SMALL.getMsg(file.getName()));
    }
  }
  /**
   * Saves the tags in this dataType to the file argument. It will be saved as
   * TagConstants.MP3_FILE_SAVE_WRITE
   *
   * @param fileToSave file to save the this dataTypes tags to
   * @throws FileNotFoundException if unable to find file
   * @throws IOException on any I/O error
   */
  public void save(File fileToSave) throws IOException {
    // Ensure we are dealing with absolute filepaths not relative ones
    File file = fileToSave.getAbsoluteFile();

    logger.config("Saving  : " + file.getPath());

    // Checks before starting write
    precheck(file);

    RandomAccessFile rfile = null;
    try {
      // ID3v2 Tag
      if (TagOptionSingleton.getInstance().isId3v2Save()) {
        if (id3v2tag == null) {
          rfile = new RandomAccessFile(file, "rw");
          (new ID3v24Tag()).delete(rfile);
          (new ID3v23Tag()).delete(rfile);
          (new ID3v22Tag()).delete(rfile);
          logger.config("Deleting ID3v2 tag:" + file.getName());
          rfile.close();
        } else {
          logger.config("Writing ID3v2 tag:" + file.getName());
          final MP3AudioHeader mp3AudioHeader = (MP3AudioHeader) this.getAudioHeader();
          final long mp3StartByte = mp3AudioHeader.getMp3StartByte();
          final long newMp3StartByte = id3v2tag.write(file, mp3StartByte);
          if (mp3StartByte != newMp3StartByte) {
            logger.config("New mp3 start byte: " + newMp3StartByte);
            mp3AudioHeader.setMp3StartByte(newMp3StartByte);
          }
        }
      }
      rfile = new RandomAccessFile(file, "rw");

      // Lyrics 3 Tag
      if (TagOptionSingleton.getInstance().isLyrics3Save()) {
        if (lyrics3tag != null) {
          lyrics3tag.write(rfile);
        }
      }
      // ID3v1 tag
      if (TagOptionSingleton.getInstance().isId3v1Save()) {
        logger.config("Processing ID3v1");
        if (id3v1tag == null) {
          logger.config("Deleting ID3v1");
          (new ID3v1Tag()).delete(rfile);
        } else {
          logger.config("Saving ID3v1");
          id3v1tag.write(rfile);
        }
      }
    } catch (FileNotFoundException ex) {
      logger.log(
          Level.SEVERE,
          ErrorMessage.GENERAL_WRITE_FAILED_BECAUSE_FILE_NOT_FOUND.getMsg(file.getName()),
          ex);
      throw ex;
    } catch (IOException iex) {
      logger.log(
          Level.SEVERE,
          ErrorMessage.GENERAL_WRITE_FAILED_BECAUSE.getMsg(file.getName(), iex.getMessage()),
          iex);
      throw iex;
    } catch (RuntimeException re) {
      logger.log(
          Level.SEVERE,
          ErrorMessage.GENERAL_WRITE_FAILED_BECAUSE.getMsg(file.getName(), re.getMessage()),
          re);
      throw re;
    } finally {
      if (rfile != null) {
        rfile.close();
      }
    }
  }
  /**
   * Read V2tag if exists
   *
   * <p>TODO:shouldn't we be handing TagExceptions:when will they be thrown
   *
   * @param file
   * @param loadOptions
   * @throws IOException
   * @throws TagException
   */
  private void readV2Tag(File file, int loadOptions, int startByte)
      throws IOException, TagException {
    // We know where the actual Audio starts so load all the file from start to that point into
    // a buffer then we can read the IDv2 information without needing any more File I/O
    if (startByte >= AbstractID3v2Tag.TAG_HEADER_LENGTH) {
      logger.finer("Attempting to read id3v2tags");
      FileInputStream fis = null;
      FileChannel fc = null;
      ByteBuffer bb;
      try {
        fis = new FileInputStream(file);
        fc = fis.getChannel();
        bb = fc.map(FileChannel.MapMode.READ_ONLY, 0, startByte);
      }
      // #JAUDIOTAGGER-419:If reading networked file map can fail so just copy bytes instead
      catch (IOException ioe) {
        bb = ByteBuffer.allocate(startByte);
        fc.read(bb, 0);
      } finally {
        if (fc != null) {
          fc.close();
        }

        if (fis != null) {
          fis.close();
        }
      }

      try {
        bb.rewind();

        if ((loadOptions & LOAD_IDV2TAG) != 0) {
          logger.config("Attempting to read id3v2tags");
          try {
            this.setID3v2Tag(new ID3v24Tag(bb, file.getName()));
          } catch (TagNotFoundException ex) {
            logger.config("No id3v24 tag found");
          }

          try {
            if (id3v2tag == null) {
              this.setID3v2Tag(new ID3v23Tag(bb, file.getName()));
            }
          } catch (TagNotFoundException ex) {
            logger.config("No id3v23 tag found");
          }

          try {
            if (id3v2tag == null) {
              this.setID3v2Tag(new ID3v22Tag(bb, file.getName()));
            }
          } catch (TagNotFoundException ex) {
            logger.config("No id3v22 tag found");
          }
        }
      } finally {
        // Workaround for 4724038 on Windows
        bb.clear();
        if (bb.isDirect() && !TagOptionSingleton.getInstance().isAndroid()) {
          // Reflection substitute for following code:
          //    ((sun.nio.ch.DirectBuffer) bb).cleaner().clean();
          // which causes exception on Android - Sun NIO classes are not available
          try {
            Class<?> clazz = Class.forName("sun.nio.ch.DirectBuffer");
            Method cleanerMethod = clazz.getMethod("cleaner");
            Object cleaner = cleanerMethod.invoke(bb); // cleaner = bb.cleaner()
            if (cleaner != null) {
              Method cleanMethod = cleaner.getClass().getMethod("clean");
              cleanMethod.invoke(cleaner); // cleaner.clean()
            }
          } catch (ClassNotFoundException e) {
            logger.severe("Could not load sun.nio.ch.DirectBuffer.");
          } catch (NoSuchMethodException e) {
            logger.severe("Could not invoke DirectBuffer method - " + e.getMessage());
          } catch (InvocationTargetException e) {
            logger.severe("Could not invoke DirectBuffer method - target exception");
          } catch (IllegalAccessException e) {
            logger.severe("Could not invoke DirectBuffer method - illegal access");
          }
        }
      }
    } else {
      logger.config("Not enough room for valid id3v2 tag:" + startByte);
    }
  }