content: /*
   This file is part of Subsonic.
  
   Subsonic is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.
  
   Subsonic is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.
  
   You should have received a copy of the GNU General Public License
   along with Subsonic.  If not, see <http://www.gnu.org/licenses/>.
  
   Copyright 2009 (C) Sindre Mehus
   */
  package net.nullsum.audinaut.domain;
  
  import android.content.Context;
  import android.media.MediaMetadataRetriever;
  import android.util.Log;
  
  import net.nullsum.audinaut.service.DownloadService;
  import net.nullsum.audinaut.util.Constants;
  import net.nullsum.audinaut.util.UpdateHelper;
  import net.nullsum.audinaut.util.Util;
  
  import java.io.File;
  import java.io.Serializable;
  import java.text.Collator;
  import java.util.ArrayList;
  import java.util.Collections;
  import java.util.Comparator;
  import java.util.Iterator;
  import java.util.List;
  import java.util.Locale;
  import java.util.Objects;
  
  /**
   * @author Sindre Mehus
   */
  public class MusicDirectory implements Serializable {
      private static final String TAG = MusicDirectory.class.getSimpleName();
  
      private String name;
      private String id;
      private String parent;
      private List<Entry> children;
  
      public MusicDirectory() {
          children = new ArrayList<>();
      }
  
      public MusicDirectory(List<Entry> children) {
          this.children = children;
      }
  
      public String getName() {
          return name;
      }
  
      public void setName(String name) {
          this.name = name;
      }
  
      public String getId() {
          return id;
      }
  
      public void setId(String id) {
          this.id = id;
      }
  
      public String getParent() {
          return parent;
      }
  
      public void setParent(String parent) {
          this.parent = parent;
      }
  
      public void addChild(Entry child) {
          if (child != null) {
              children.add(child);
          }
      }
  
      public void addChildren(List<Entry> children) {
          this.children.addAll(children);
      }
  
      public void replaceChildren(List<Entry> children) {
          this.children = children;
      }
  
      public synchronized List<Entry> getChildren() {
          return getChildren(true, true);
      }
  
      public synchronized List<Entry> getChildren(boolean includeDirs, boolean includeFiles) {
          if (includeDirs && includeFiles) {
              return children;
          }
  
          List<Entry> result = new ArrayList<>(children.size());
          for (Entry child : children) {
              if (child != null && child.isDirectory() && includeDirs || !child.isDirectory() && includeFiles) {
                  result.add(child);
              }
          }
          return result;
      }
  
      public synchronized List<Entry> getSongs() {
          List<Entry> result = new ArrayList<>();
          for (Entry child : children) {
              if (child != null && !child.isDirectory()) {
                  result.add(child);
              }
          }
          return result;
      }
  
      public synchronized int getChildrenSize() {
          return children.size();
      }
  
      public void sortChildren(Context context) {
          sortChildren(Util.getPreferences(context).getBoolean(Constants.PREFERENCES_KEY_CUSTOM_SORT_ENABLED, true));
      }
  
      public void sortChildren(boolean byYear) {
          EntryComparator.sort(children, byYear);
      }
  
      public synchronized boolean updateMetadata(MusicDirectory refreshedDirectory) {
          boolean metadataUpdated = false;
          for (Entry entry : children) {
              int index = refreshedDirectory.children.indexOf(entry);
              if (index != -1) {
                  final Entry refreshed = refreshedDirectory.children.get(index);
  
                  entry.setTitle(refreshed.getTitle());
                  entry.setAlbum(refreshed.getAlbum());
                  entry.setArtist(refreshed.getArtist());
                  entry.setTrack(refreshed.getTrack());
                  entry.setYear(refreshed.getYear());
                  entry.setGenre(refreshed.getGenre());
                  entry.setTranscodedContentType(refreshed.getTranscodedContentType());
                  entry.setTranscodedSuffix(refreshed.getTranscodedSuffix());
                  entry.setDiscNumber(refreshed.getDiscNumber());
                  entry.setType(refreshed.getType());
                  if (!Util.equals(entry.getCoverArt(), refreshed.getCoverArt())) {
                      metadataUpdated = true;
                      entry.setCoverArt(refreshed.getCoverArt());
                  }
  
                  new UpdateHelper.EntryInstanceUpdater(entry) {
                      @Override
                      public void update(Entry found) {
                          found.setTitle(refreshed.getTitle());
                          found.setAlbum(refreshed.getAlbum());
                          found.setArtist(refreshed.getArtist());
                          found.setTrack(refreshed.getTrack());
                          found.setYear(refreshed.getYear());
                          found.setGenre(refreshed.getGenre());
                          found.setTranscodedContentType(refreshed.getTranscodedContentType());
                          found.setTranscodedSuffix(refreshed.getTranscodedSuffix());
                          found.setDiscNumber(refreshed.getDiscNumber());
                          found.setType(refreshed.getType());
                          if (!Util.equals(found.getCoverArt(), refreshed.getCoverArt())) {
                              found.setCoverArt(refreshed.getCoverArt());
                              metadataUpdate = DownloadService.METADATA_UPDATED_COVER_ART;
                          }
                      }
                  }.execute();
              }
          }
  
          return metadataUpdated;
      }
  
      public synchronized boolean updateEntriesList(Context context, MusicDirectory refreshedDirectory) {
          boolean changed = false;
          Iterator<Entry> it = children.iterator();
          while (it.hasNext()) {
              Entry entry = it.next();
              // No longer exists in here
              if (refreshedDirectory.children.indexOf(entry) == -1) {
                  it.remove();
                  changed = true;
              }
          }
  
          // Make sure we contain all children from refreshed set
          boolean resort = false;
          for (Entry refreshed : refreshedDirectory.children) {
              if (!this.children.contains(refreshed)) {
                  this.children.add(refreshed);
                  resort = true;
                  changed = true;
              }
          }
  
          if (resort) {
              this.sortChildren(context);
          }
  
          return changed;
      }
  
      public static class Entry implements Serializable {
          public static final int TYPE_SONG = 0;
  
          private String id;
          private String parent;
          private String albumId;
          private String artistId;
          private boolean directory;
          private String title;
          private String album;
          private String artist;
          private Integer track;
          private Integer year;
          private String genre;
          private String contentType;
          private String suffix;
          private String transcodedContentType;
          private String transcodedSuffix;
          private String coverArt;
          private Integer duration;
          private Integer bitRate;
          private String path;
          private Integer discNumber;
          private int type = 0;
          private int closeness;
  
          public Entry() {
  
          }
  
          public Entry(String id) {
              this.id = id;
          }
  
          public Entry(Artist artist) {
              this.id = artist.getId();
              this.title = artist.getName();
              this.directory = true;
          }
  
          public void loadMetadata(File file) {
              try {
                  MediaMetadataRetriever metadata = new MediaMetadataRetriever();
                  metadata.setDataSource(file.getAbsolutePath());
                  String discNumber = metadata.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DISC_NUMBER);
                  if (discNumber == null) {
                      discNumber = "1/1";
                  }
                  int slashIndex = discNumber.indexOf("/");
                  if (slashIndex > 0) {
                      discNumber = discNumber.substring(0, slashIndex);
                  }
                  try {
                      setDiscNumber(Integer.parseInt(discNumber));
                  } catch (Exception e) {
                      Log.w(TAG, "Non numbers in disc field!");
                  }
                  String bitrate = metadata.extractMetadata(MediaMetadataRetriever.METADATA_KEY_BITRATE);
                  setBitRate(Integer.parseInt((bitrate != null) ? bitrate : "0") / 1000);
                  String length = metadata.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION);
                  setDuration(Integer.parseInt(length) / 1000);
                  String artist = metadata.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ARTIST);
                  if (artist != null) {
                      setArtist(artist);
                  }
                  String album = metadata.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ALBUM);
                  if (album != null) {
                      setAlbum(album);
                  }
                  metadata.release();
              } catch (Exception e) {
                  Log.i(TAG, "Device doesn't properly support MediaMetadataRetreiver", e);
              }
          }
  
          public void rebaseTitleOffPath() {
              try {
                  String filename = getPath();
                  if (filename == null) {
                      return;
                  }
  
                  int index = filename.lastIndexOf('/');
                  if (index != -1) {
                      filename = filename.substring(index + 1);
                      if (getTrack() != null) {
                          filename = filename.replace(String.format("%02d ", getTrack()), "");
                      }
  
                      index = filename.lastIndexOf('.');
                      if (index != -1) {
                          filename = filename.substring(0, index);
                      }
  
                      setTitle(filename);
                  }
              } catch (Exception e) {
                  Log.w(TAG, "Failed to update title based off of path", e);
              }
          }
  
          public String getId() {
              return id;
          }
  
          public void setId(String id) {
              this.id = id;
          }
  
          public String getParent() {
              return parent;
          }
  
          public void setParent(String parent) {
              this.parent = parent;
          }
  
          public String getAlbumId() {
              return albumId;
          }
  
          public void setAlbumId(String albumId) {
              this.albumId = albumId;
          }
  
          public String getArtistId() {
              return artistId;
          }
  
          public void setArtistId(String artistId) {
              this.artistId = artistId;
          }
  
          public boolean isDirectory() {
              return directory;
          }
  
          public void setDirectory(boolean directory) {
              this.directory = directory;
          }
  
          public String getTitle() {
              return title;
          }
  
          public void setTitle(String title) {
              this.title = title;
          }
  
          public String getAlbum() {
              return album;
          }
  
          public boolean isAlbum() {
              return getParent() != null || getArtist() != null;
          }
  
          public void setAlbum(String album) {
              this.album = album;
          }
  
          public String getAlbumDisplay() {
              if (album != null && title.startsWith("Disc ")) {
                  return album;
              } else {
                  return title;
              }
          }
  
          public String getArtist() {
              return artist;
          }
  
          public void setArtist(String artist) {
              this.artist = artist;
          }
  
          public Integer getTrack() {
              return track;
          }
  
          public void setTrack(Integer track) {
              this.track = track;
          }
  
          public Integer getYear() {
              return year;
          }
  
          public void setYear(Integer year) {
              this.year = year;
          }
  
          public String getGenre() {
              return genre;
          }
  
          public void setGenre(String genre) {
              this.genre = genre;
          }
  
          public String getContentType() {
              return contentType;
          }
  
          public void setContentType(String contentType) {
              this.contentType = contentType;
          }
  
          public String getSuffix() {
              return suffix;
          }
  
          public void setSuffix(String suffix) {
              this.suffix = suffix;
          }
  
          public String getTranscodedContentType() {
              return transcodedContentType;
          }
  
          public void setTranscodedContentType(String transcodedContentType) {
              this.transcodedContentType = transcodedContentType;
          }
  
          public String getTranscodedSuffix() {
              return transcodedSuffix;
          }
  
          public void setTranscodedSuffix(String transcodedSuffix) {
              this.transcodedSuffix = transcodedSuffix;
          }
  
          public Integer getDuration() {
              return duration;
          }
  
          public void setDuration(Integer duration) {
              this.duration = duration;
          }
  
          public Integer getBitRate() {
              return bitRate;
          }
  
          public void setBitRate(Integer bitRate) {
              this.bitRate = bitRate;
          }
  
          public String getCoverArt() {
              return coverArt;
          }
  
          public void setCoverArt(String coverArt) {
              this.coverArt = coverArt;
          }
  
          public String getPath() {
              return path;
          }
  
          public void setPath(String path) {
              this.path = path;
          }
  
          public Integer getDiscNumber() {
              return discNumber;
          }
  
          public void setDiscNumber(Integer discNumber) {
              this.discNumber = discNumber;
          }
  
          public int getType() {
              return type;
          }
  
          public void setType(int type) {
              this.type = type;
          }
  
          public boolean isSong() {
              return type == TYPE_SONG;
          }
  
          public int getCloseness() {
              return closeness;
          }
  
          public void setCloseness(int closeness) {
              this.closeness = closeness;
          }
  
          @Override
          public boolean equals(Object o) {
              if (this == o) {
                  return true;
              }
              if (o == null || getClass() != o.getClass()) {
                  return false;
              }
  
              Entry entry = (Entry) o;
              return id.equals(entry.id);
          }
  
          @Override
          public int hashCode() {
              return id.hashCode();
          }
  
          @Override
          public String toString() {
              return title;
          }
      }
  
      public static class EntryComparator implements Comparator<Entry> {
          private final boolean byYear;
          private final Collator collator;
  
          public EntryComparator(boolean byYear) {
              this.byYear = byYear;
              this.collator = Collator.getInstance(Locale.US);
              this.collator.setStrength(Collator.PRIMARY);
          }
  
          public static void sort(List<Entry> entries, boolean byYear) {
              try {
                  Collections.sort(entries, new EntryComparator(byYear));
              } catch (Exception e) {
                  Log.w(TAG, "Failed to sort MusicDirectory");
              }
          }
  
          public int compare(Entry lhs, Entry rhs) {
              if (lhs.isDirectory() && !rhs.isDirectory()) {
                  return -1;
              } else if (!lhs.isDirectory() && rhs.isDirectory()) {
                  return 1;
              } else if (lhs.isDirectory() && rhs.isDirectory()) {
                  if (byYear) {
                      Integer lhsYear = lhs.getYear();
                      Integer rhsYear = rhs.getYear();
                      if (lhsYear != null && rhsYear != null) {
                          return lhsYear.compareTo(rhsYear);
                      } else if (lhsYear != null) {
                          return -1;
                      } else if (rhsYear != null) {
                          return 1;
                      }
                  }
  
                  return collator.compare(lhs.getAlbumDisplay(), rhs.getAlbumDisplay());
              }
  
              Integer lhsDisc = lhs.getDiscNumber();
              Integer rhsDisc = rhs.getDiscNumber();
  
              if (lhsDisc != null && rhsDisc != null) {
                  if (lhsDisc < rhsDisc) {
                      return -1;
                  } else if (lhsDisc > rhsDisc) {
                      return 1;
                  }
              }
  
              Integer lhsTrack = lhs.getTrack();
              Integer rhsTrack = rhs.getTrack();
              if (lhsTrack != null && rhsTrack != null && !Objects.equals(lhsTrack, rhsTrack)) {
                  return lhsTrack.compareTo(rhsTrack);
              } else if (lhsTrack != null) {
                  return -1;
              } else if (rhsTrack != null) {
                  return 1;
              }
  
              return collator.compare(lhs.getTitle(), rhs.getTitle());
          }
      }
  }
  