
package org.airsonic.player.service.search;

import com.codahale.metrics.ConsoleReporter;
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.Timer;
import org.airsonic.player.dao.AlbumDao;
import org.airsonic.player.dao.MusicFolderDao;
import org.airsonic.player.domain.Album;
import org.airsonic.player.domain.Artist;
import org.airsonic.player.domain.MediaFile;
import org.airsonic.player.domain.MediaFile.MediaType;
import org.airsonic.player.domain.MusicFolder;
import org.airsonic.player.domain.ParamSearchResult;
import org.airsonic.player.domain.RandomSearchCriteria;
import org.airsonic.player.domain.SearchCriteria;
import org.airsonic.player.domain.SearchResult;
import org.airsonic.player.service.SearchService;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.subsonic.restapi.ArtistID3;

import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.concurrent.TimeUnit;

public class SearchServiceTestCase extends AbstractAirsonicHomeTest {

    @Autowired
    private AlbumDao albumDao;

    private final MetricRegistry metrics = new MetricRegistry();

    @Autowired
    private MusicFolderDao musicFolderDao;

    @Autowired
    private SearchService searchService;

    @Before
    public void setup() {
        populateDatabaseOnlyOnce();
    }

    @Test
    public void testSearchTypical() {

        /*
         * A simple test that is expected to easily detect API syntax differences when updating lucene.
         * Complete route coverage and data coverage in this case alone are not conscious.
         */

        List<MusicFolder> allMusicFolders = musicFolderDao.getAllMusicFolders();
        Assert.assertEquals(3, allMusicFolders.size());

        // *** testSearch() ***

        String query = "Sarah Walker";
        final SearchCriteria searchCriteria = new SearchCriteria();
        searchCriteria.setQuery(query);
        searchCriteria.setCount(Integer.MAX_VALUE);
        searchCriteria.setOffset(0);

        /*
         * _ID3_ALBUMARTIST_ Sarah Walker/Nash Ensemble
         * Should find any version of Lucene.
         */
        SearchResult result = searchService.search(searchCriteria, allMusicFolders,
                IndexType.ALBUM);
        Assert.assertEquals("(0) Specify '" + query + "' as query, total Hits is", 1,
                result.getTotalHits());
        Assert.assertEquals("(1) Specify artist '" + query + "' as query. Artist SIZE is", 0,
                result.getArtists().size());
        Assert.assertEquals("(2) Specify artist '" + query + "' as query. Album SIZE is", 0,
                result.getAlbums().size());
        Assert.assertEquals("(3) Specify artist '" + query + "' as query, MediaFile SIZE is", 1,
                result.getMediaFiles().size());
        Assert.assertEquals("(4) ", MediaType.ALBUM, result.getMediaFiles().get(0).getMediaType());
        Assert.assertEquals(
                "(5) Specify artist '" + query + "' as query, and get a album. Name is ",
                "_ID3_ALBUMARTIST_ Sarah Walker/Nash Ensemble",
                result.getMediaFiles().get(0).getArtist());
        Assert.assertEquals(
                "(6) Specify artist '" + query + "' as query, and get a album. Name is ",
                "_ID3_ALBUM_ Ravel - Chamber Music With Voice",
                result.getMediaFiles().get(0).getAlbumName());

        /*
         * _ID3_ALBUM_ Ravel - Chamber Music With Voice
         * Should find any version of Lucene.
         */
        query = "music";
        searchCriteria.setQuery(query);
        result = searchService.search(searchCriteria, allMusicFolders, IndexType.ALBUM_ID3);
        Assert.assertEquals("Specify '" + query + "' as query, total Hits is", 1,
                result.getTotalHits());
        Assert.assertEquals("(7) Specify '" + query + "' as query, and get a song. Artist SIZE is ",
                0, result.getArtists().size());
        Assert.assertEquals("(8) Specify '" + query + "' as query, and get a song. Album SIZE is ",
                1, result.getAlbums().size());
        Assert.assertEquals(
                "(9) Specify '" + query + "' as query, and get a song. MediaFile SIZE is ", 0,
                result.getMediaFiles().size());
        Assert.assertEquals("(9) Specify '" + query + "' as query, and get a album. Name is ",
                "_ID3_ALBUMARTIST_ Sarah Walker/Nash Ensemble",
                result.getAlbums().get(0).getArtist());
        Assert.assertEquals("(10) Specify '" + query + "' as query, and get a album. Name is ",
                "_ID3_ALBUM_ Ravel - Chamber Music With Voice",
                result.getAlbums().get(0).getName());

        /*
         * _ID3_ALBUM_ Ravel - Chamber Music With Voice
         * Should find any version of Lucene.
         */
        query = "Ravel - Chamber Music";
        searchCriteria.setQuery(query);
        result = searchService.search(searchCriteria, allMusicFolders, IndexType.SONG);
        Assert.assertEquals("(11) Specify album '" + query + "' as query, total Hits is", 2,
                result.getTotalHits());
        Assert.assertEquals("(12) Specify album '" + query + "', and get a song. Artist SIZE is", 0,
                result.getArtists().size());
        Assert.assertEquals("(13) Specify album '" + query + "', and get a song. Album SIZE is", 0,
                result.getAlbums().size());
        Assert.assertEquals("(14) Specify album '" + query + "', and get a song. MediaFile SIZE is",
                2, result.getMediaFiles().size());
        Assert.assertEquals("(15) Specify album '" + query + "', and get songs. The first song is ",
                "01 - Gaspard de la Nuit - i. Ondine", result.getMediaFiles().get(0).getTitle());
        Assert.assertEquals(
                "(16) Specify album '" + query + "', and get songs. The second song is ",
                "02 - Gaspard de la Nuit - ii. Le Gibet", result.getMediaFiles().get(1).getTitle());

        // *** testSearchByName() ***

        /*
         * _ID3_ALBUM_ Sackcloth 'n' Ashes
         * Should be 1 in Lucene 3.0(Because Single quate is not a delimiter).
         */
        query = "Sackcloth 'n' Ashes";
        ParamSearchResult<Album> albumResult = searchService.searchByName(query, 0,
                Integer.MAX_VALUE, allMusicFolders, Album.class);
        Assert.assertEquals(
                "(17) Specify album name '" + query + "' as the name, and get an album.", 1,
                albumResult.getItems().size());
        Assert.assertEquals("(18) Specify '" + query + "' as the name, The album name is ",
                "_ID3_ALBUM_ Sackcloth 'n' Ashes", albumResult.getItems().get(0).getName());
        Assert.assertEquals(
                "(19) Whether the acquired album contains data of the specified album name", 1L,
                albumResult.getItems().stream()
                        .filter(r -> "_ID3_ALBUM_ Sackcloth \'n\' Ashes".equals(r.getName()))
                        .count());

        /*
         * Should be 0 in Lucene 3.0(Since the slash is not a delimiter).
         */
        query = "lker/Nash";
        ParamSearchResult<ArtistID3> artistId3Result = searchService.searchByName(query, 0,
                Integer.MAX_VALUE, allMusicFolders, ArtistID3.class);
        Assert.assertEquals("(20) Specify '" + query + "' as the name, and get an artist.", 0,
                artistId3Result.getItems().size());
        ParamSearchResult<Artist> artistResult = searchService.searchByName(query, 0,
                Integer.MAX_VALUE, allMusicFolders, Artist.class);

        /*
         * // XXX 3.x -> 8.x :
         * Hit 'Nash*' as ​​the slash becomes a delimiter.
         */
        Assert.assertEquals("(21) Specify '" + query + "' as the name, and get an artist.", 1,
                artistResult.getItems().size());

        // *** testGetRandomSongs() ***

        /*
         * Regardless of the Lucene version,
         * RandomSearchCriteria can specify null and means the maximum range.
         * 11 should be obtainable.
         */
        RandomSearchCriteria randomSearchCriteria = new RandomSearchCriteria(Integer.MAX_VALUE, // count
                null, // genre,
                null, // fromYear
                null, // toYear
                allMusicFolders // musicFolders
        );
        List<MediaFile> allRandomSongs = searchService.getRandomSongs(randomSearchCriteria);
        Assert.assertEquals(
                "(22) Specify MAX_VALUE as the upper limit, and randomly acquire songs.", 11,
                allRandomSongs.size());

        /*
         * Regardless of the Lucene version,
         * 7 should be obtainable.
         */
        randomSearchCriteria = new RandomSearchCriteria(Integer.MAX_VALUE, // count
                null, // genre,
                1900, // fromYear
                null, // toYear
                allMusicFolders // musicFolders
        );
        allRandomSongs = searchService.getRandomSongs(randomSearchCriteria);
        Assert.assertEquals("(23) Specify 1900 as 'fromYear', and randomly acquire songs.", 7,
                allRandomSongs.size());

        /*
         * Regardless of the Lucene version,
         * It should be 0 because it is a non-existent genre.
         */
        randomSearchCriteria = new RandomSearchCriteria(Integer.MAX_VALUE, // count
                "Chamber Music", // genre,
                null, // fromYear
                null, // toYear
                allMusicFolders // musicFolders
        );
        allRandomSongs = searchService.getRandomSongs(randomSearchCriteria);
        Assert.assertEquals("(24) Specify music as 'genre', and randomly acquire songs.", 0,
                allRandomSongs.size());

        /*
         * Genre including blank.
         * Regardless of the Lucene version, It should be 2.
         */
        randomSearchCriteria = new RandomSearchCriteria(Integer.MAX_VALUE, // count
                "Baroque Instrumental", // genre,
                null, // fromYear
                null, // toYear
                allMusicFolders // musicFolders
        );
        allRandomSongs = searchService.getRandomSongs(randomSearchCriteria);
        Assert.assertEquals("(25) Search by specifying genres including spaces and hyphens.", 2,
                allRandomSongs.size());

        // *** testGetRandomAlbums() ***

        /*
         * Acquisition of maximum number(5).
         */
        List<Album> allAlbums = albumDao.getAlphabeticalAlbums(0, Integer.MAX_VALUE, true, true, allMusicFolders);
        Assert.assertEquals("(26) Get all albums with Dao.", 5, allAlbums.size());
        List<MediaFile> allRandomAlbums = searchService.getRandomAlbums(Integer.MAX_VALUE,
                allMusicFolders);
        Assert.assertEquals("(27) Specify Integer.MAX_VALUE as the upper limit,"
                + "and randomly acquire albums(file struct).", 5, allRandomAlbums.size());

        /*
         * Acquisition of maximum number(5).
         */
        List<Album> allRandomAlbumsId3 = searchService.getRandomAlbumsId3(Integer.MAX_VALUE,
                allMusicFolders);
        Assert.assertEquals(
                "(28) Specify Integer.MAX_VALUE as the upper limit, and randomly acquire albums(ID3).",
                5, allRandomAlbumsId3.size());

        /*
         * Total is 4.
         */
        query = "ID 3 ARTIST";
        searchCriteria.setQuery(query);
        result = searchService.search(searchCriteria, allMusicFolders, IndexType.ARTIST_ID3);
        Assert.assertEquals("(29) Specify '" + query + "', total Hits is", 4,
                result.getTotalHits());
        Assert.assertEquals("(30) Specify '" + query + "', and get an artists. Artist SIZE is ", 4,
                result.getArtists().size());
        Assert.assertEquals("(31) Specify '" + query + "', and get a artists. Album SIZE is ", 0,
                result.getAlbums().size());
        Assert.assertEquals("(32) Specify '" + query + "', and get a artists. MediaFile SIZE is ",
                0, result.getMediaFiles().size());

        /*
         * Three hits to the artist.
         * ALBUMARTIST is not registered with these.
         * Therefore, the registered value of ARTIST is substituted in ALBUMARTIST.
         */
        long count = result.getArtists().stream()
                .filter(a -> a.getName().startsWith("_ID3_ARTIST_")).count();
        Assert.assertEquals("(33) Artist whose name contains \\\"_ID3_ARTIST_\\\" is 3 records.",
                3L, count);

        /*
         * The structure of "01 - Sonata Violin & Cello I. Allegro.ogg"
         * ARTIST -> _ID3_ARTIST_ Sarah Walker/Nash Ensemble
         * ALBUMARTIST -> _ID3_ALBUMARTIST_ Sarah Walker/Nash Ensemble
         * (The result must not contain duplicates. And ALBUMARTIST must be returned correctly.)
         */
        count = result.getArtists().stream()
                .filter(a -> a.getName().startsWith("_ID3_ALBUMARTIST_")).count();
        Assert.assertEquals("(34) Artist whose name is \"_ID3_ARTIST_\" is 1 records.", 1L, count);

        /*
         * Below is a simple loop test.
         * How long is the total time?
         */
        int countForEachMethod = 500;
        String[] randomWords4Search = createRandomWords(countForEachMethod);
        String[] randomWords4SearchByName = createRandomWords(countForEachMethod);

        Timer globalTimer = metrics
                .timer(MetricRegistry.name(SearchServiceTestCase.class, "Timer.global"));
        final Timer.Context globalTimerContext = globalTimer.time();

        System.out.println("--- Random search (" + countForEachMethod * 5 + " times) ---");

        // testSearch()
        Arrays.stream(randomWords4Search).forEach(w -> {
            searchCriteria.setQuery(w);
            searchService.search(searchCriteria, allMusicFolders, IndexType.ALBUM);
        });

        // testSearchByName()
        Arrays.stream(randomWords4SearchByName).forEach(w -> {
            searchService.searchByName(w, 0, Integer.MAX_VALUE, allMusicFolders, Artist.class);
        });

        // testGetRandomSongs()
        RandomSearchCriteria criteria = new RandomSearchCriteria(Integer.MAX_VALUE, // count
                null, // genre,
                null, // fromYear
                null, // toYear
                allMusicFolders // musicFolders
        );
        for (int i = 0; i < countForEachMethod; i++) {
            searchService.getRandomSongs(criteria);
        }

        // testGetRandomAlbums()
        for (int i = 0; i < countForEachMethod; i++) {
            searchService.getRandomAlbums(Integer.MAX_VALUE, allMusicFolders);
        }

        // testGetRandomAlbumsId3()
        for (int i = 0; i < countForEachMethod; i++) {
            searchService.getRandomAlbumsId3(Integer.MAX_VALUE, allMusicFolders);
        }

        globalTimerContext.stop();

        /*
         * Whether or not IndexReader is exhausted.
         */
        query = "Sarah Walker";
        searchCriteria.setQuery(query);
        result = searchService.search(searchCriteria, allMusicFolders, IndexType.ALBUM);
        Assert.assertEquals("(35) Can the normal case be implemented.", 0,
                result.getArtists().size());
        Assert.assertEquals("(36) Can the normal case be implemented.", 0,
                result.getAlbums().size());
        Assert.assertEquals("(37) Can the normal case be implemented.", 1,
                result.getMediaFiles().size());
        Assert.assertEquals("(38) Can the normal case be implemented.", MediaType.ALBUM,
                result.getMediaFiles().get(0).getMediaType());
        Assert.assertEquals("(39) Can the normal case be implemented.",
                "_ID3_ALBUMARTIST_ Sarah Walker/Nash Ensemble",
                result.getMediaFiles().get(0).getArtist());

        System.out.println("--- SUCCESS ---");

        ConsoleReporter reporter = ConsoleReporter.forRegistry(metrics)
                .convertRatesTo(TimeUnit.SECONDS).convertDurationsTo(TimeUnit.MILLISECONDS).build();
        reporter.report();

        System.out.println("End. ");
    }

    private static String[] createRandomWords(int count) {
        String[] randomStrings = new String[count];
        Random random = new Random();
        for (int i = 0; i < count; i++) {
            char[] word = new char[random.nextInt(8) + 3];
            for (int j = 0; j < word.length; j++) {
                word[j] = (char) ('a' + random.nextInt(26));
            }
            randomStrings[i] = new String(word);
        }
        return randomStrings;
    }

}
