/**
   * Remove tag from file
   *
   * @param mp3tag
   * @throws FileNotFoundException
   * @throws IOException
   */
  public void delete(AbstractTag mp3tag) throws FileNotFoundException, IOException {
    RandomAccessFile raf = new RandomAccessFile(this.file, "rw");
    mp3tag.delete(raf);
    raf.close();
    if (mp3tag instanceof ID3v1Tag) {
      id3v1tag = null;
    }

    if (mp3tag instanceof AbstractID3v2Tag) {
      id3v2tag = null;
    }
  }
  /**
   * Creates a new MP3File dataType and parse the tag from the given file Object, files can be
   * opened read only if required.
   *
   * @param file MP3 file
   * @param loadOptions decide what tags to load
   * @param readOnly causes the files to be opened readonly
   * @throws IOException on any I/O error
   * @throws TagException on any exception generated by this library.
   * @throws org.jaudiotagger.audio.exceptions.ReadOnlyFileException
   * @throws org.jaudiotagger.audio.exceptions.InvalidAudioFrameException
   */
  public MP3File(File file, int loadOptions, boolean readOnly)
      throws IOException, TagException, ReadOnlyFileException, InvalidAudioFrameException {
    RandomAccessFile newFile = null;
    try {
      this.file = file;

      // Check File accessibility
      newFile = checkFilePermissions(file, readOnly);

      // Read ID3v2 tag size (if tag exists) to allow audioHeader parsing to skip over tag
      long tagSizeReportedByHeader = AbstractID3v2Tag.getV2TagSizeIfExists(file);
      logger.config("TagHeaderSize:" + Hex.asHex(tagSizeReportedByHeader));
      audioHeader = new MP3AudioHeader(file, tagSizeReportedByHeader);

      // If the audio header is not straight after the end of the tag then search from start of file
      if (tagSizeReportedByHeader != ((MP3AudioHeader) audioHeader).getMp3StartByte()) {
        logger.config("First header found after tag:" + audioHeader);
        audioHeader = checkAudioStart(tagSizeReportedByHeader, (MP3AudioHeader) audioHeader);
      }

      // Read v1 tags (if any)
      readV1Tag(file, newFile, loadOptions);

      // Read v2 tags (if any)
      readV2Tag(file, loadOptions, (int) ((MP3AudioHeader) audioHeader).getMp3StartByte());

      // If we have a v2 tag use that, if we do not but have v1 tag use that
      // otherwise use nothing
      // TODO:if have both should we merge
      // rather than just returning specific ID3v22 tag, would it be better to return v24 version ?
      if (this.getID3v2Tag() != null) {
        tag = this.getID3v2Tag();
      } else if (id3v1tag != null) {
        tag = id3v1tag;
      }
    } finally {
      if (newFile != null) {
        newFile.close();
      }
    }
  }
  /**
   * 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();
      }
    }
  }