// At a high level, what we're trying to do is get the most similar track
        // to the seed tracks, excluding played/skipped ones[, and ones too similar to skipped ones].
        //
        // The RANDOM_CONDITION will exclude the played/skipped items for us, but we need to
        // add the Similarity-based selection[, exclusion], and ordering.

        public RandomBySimilar() : base("mirage_similar")
        {
            MiragePlugin.Init();

            Label       = AddinManager.CurrentLocalizer.GetString("Shuffle by Similar");
            Adverb      = AddinManager.CurrentLocalizer.GetString("by similar");
            Description = AddinManager.CurrentLocalizer.GetString("Play songs similar to those already played");

            Select    = ", HYENA_BINARY_FUNCTION ('MIRAGE_DISTANCE', ?, mirage.ScmsData) as Distance";
            From      = "LEFT OUTER JOIN MirageTrackAnalysis mirage ON (mirage.TrackID = CoreTracks.TrackID) ";
            Condition = "mirage.Status = 0 AND CoreTracks.ArtistID NOT IN (?) AND Distance > 0";
            OrderBy   = "Distance ASC, RANDOM ()";
        }
        public void Dispose()
        {
            ServiceManager.SourceManager.MusicLibrary.TracksAdded   -= OnLibraryTracksAdded;
            ServiceManager.SourceManager.MusicLibrary.TracksDeleted -= OnLibraryTracksDeleted;

            if (analysis_job != null)
            {
                ServiceManager.JobScheduler.Cancel(analysis_job);
                analysis_job = null;
            }

            DistanceCalculator.Dispose();

            try {
                Analyzer.CancelAnalyze();
            } catch (Exception) {
            }

            action_service.UIManager.RemoveUi(uiManagerId);
            action_service.UIManager.RemoveActionGroup(actions);

            instance = null;
        }
        public void DelayedInitialize()
        {
            if (instance != null)
            {
                throw new InvalidOperationException("A MiragePlugin instance is already in use");
            }

            Init();

            action_service = ServiceManager.Get <InterfaceActionService> ();

            TrackAnalysis.Init();
            MigrateLegacyDb();
            DistanceCalculator.Init();

            InstallInterfaceActions();

            if (!ServiceStartup())
            {
                ServiceManager.SourceManager.SourceAdded += OnSourceAdded;
            }

            instance = this;
        }
        public void Dispose()
        {
            ServiceManager.SourceManager.MusicLibrary.TracksAdded -= OnLibraryTracksAdded;
            ServiceManager.SourceManager.MusicLibrary.TracksDeleted -= OnLibraryTracksDeleted;

            if (analysis_job != null) {
                ServiceManager.JobScheduler.Cancel (analysis_job);
                analysis_job = null;
            }

            DistanceCalculator.Dispose ();

            try {
                Analyzer.CancelAnalyze ();
            } catch (Exception) {
            }

            action_service.UIManager.RemoveUi (uiManagerId);
            action_service.UIManager.RemoveActionGroup (actions);

            instance = null;
        }
        public void DelayedInitialize()
        {
            if (instance != null)
                throw new InvalidOperationException ("A MiragePlugin instance is already in use");

            Init ();

            action_service = ServiceManager.Get<InterfaceActionService> ();

            TrackAnalysis.Init ();
            MigrateLegacyDb ();
            DistanceCalculator.Init ();

            InstallInterfaceActions ();

            if (!ServiceStartup ()) {
                ServiceManager.SourceManager.SourceAdded += OnSourceAdded;
            }

            instance = this;
        }