from jamaisvu.database import get_database, Database
from songdata import SongDataFinder
import jamaisvu.decoder as decoder
import fingerprint


class Jamaisvu(object):

    SONG_ID = "song_id"
    SONG_NAME = 'song_name'

    SONG_ARTIST = "song_artist"
    SONG_ALBUM = "song_album"
    SONG_GENRE = "song_genre"
    SONG_EXPLICIT = "song_explicit"
    SONG_LENGTH = "song_length"

    CONFIDENCE = 'confidence'
    MATCH_TIME = 'match_time'
    OFFSET = 'offset'
    OFFSET_SECS = 'offset_seconds'

    def __init__(self, config):
        super(Jamaisvu, self).__init__()

        self.config = config

        # initialize db
        db_cls = get_database(config.get("database_type", None))

        self.db = db_cls(**config.get("database", {}))
        self.db.setup()  # Change this with an if condition

        # if we should limit seconds fingerprinted,
        # None|-1 means use entire track
        self.limit = self.config.get("fingerprint_limit", None)
        if self.limit == -1:  # for JSON compatibility
            self.limit = None

        self.songdatafinder = SongDataFinder(self.config.get("acoustid_apikey"))

        self.get_fingerprinted_songs()

    def get_fingerprinted_songs(self):
        # get songs previously indexed
        self.songs = self.db.get_songs()
        self.songhashes_set = set()  # to know which ones we've computed before
        for song in self.songs:
            song_hash = song[Database.FIELD_FILE_SHA1]
            self.songhashes_set.add(song_hash)

    def fingerprint_file(self, filepath):
        song_hash = decoder.unique_hash(filepath)  # Generate song file hash

        # Don't refingerprint already fingerprinted files or try to find data
        if song_hash in self.songhashes_set:
            print "%s has already been fingerprinted." % song_hash

            return False
        else:
            songdata = self.songdatafinder.matchFile(filepath)
            if songdata == None:  # Exit like the previous condition, if we disabled userinput
                print("Could not ID song")
                return False

            song_name = songdata.getName()
            song_artist = songdata.getMainArtist()
            song_album = songdata.getAlbum()
            song_artist_genre = songdata.getMainArtistGenre()
            song_explicit = songdata.getExplicitRating()
            song_length = songdata.getLength()

            hashes, file_hash = _fingerprint_worker(filepath, self.limit)
            # Insert our song data into the songs table and return its location
            sid = self.db.insert_song(song_name,
                                      song_artist,
                                      song_album,
                                      song_artist_genre,
                                      song_explicit,
                                      song_length,
                                      file_hash)

            self.db.insert_hashes(sid, hashes)
            self.db.set_song_fingerprinted(sid)
            self.get_fingerprinted_songs() # Why is this being run after every call... with two nested for loops

            return True

    def fingerprint_directory(self, path, extensions):
        # TODO: Make this use the fingerprint_file, can't do multiple processes due to API limits
        # This shouldn't be an issue once gpu acceleration is implemented

        filenames_to_fingerprint = []
        for filename, _ in decoder.find_files(path, extensions):
            # don't refingerprint already fingerprinted files
            if decoder.unique_hash(filename) in self.songhashes_set:
                print "%s already fingerprinted, continuing..." % filename
            else:
                filenames_to_fingerprint.append(filename)

        print(filenames_to_fingerprint)

        for filename in filenames_to_fingerprint:
            self.fingerprint_file(filename)

        return True

    def find_matches(self, samples, Fs=fingerprint.DEFAULT_FS):
        hashes = fingerprint.fingerprint(samples, Fs=Fs)
        return self.db.return_matches(hashes)

    def align_matches(self, matches):
        """
            Finds hash matches that align in time with other matches and finds
            consensus about which hashes are "true" signal from the audio.

            Returns a dictionary with match information.
        """
        # align by diffs
        diff_counter = {}
        largest = 0
        largest_count = 0
        song_id = -1
        for tup in matches:
            sid, diff = tup
            if diff not in diff_counter:
                diff_counter[diff] = {}
            if sid not in diff_counter[diff]:
                diff_counter[diff][sid] = 0
            diff_counter[diff][sid] += 1

            if diff_counter[diff][sid] > largest_count:
                largest = diff
                largest_count = diff_counter[diff][sid]
                song_id = sid

        # extract idenfication
        song = self.db.get_song_by_id(song_id)
        if song:
            # TODO: Clarify what `get_song_by_id` should return.
            songname = song.get(Jamaisvu.SONG_NAME, None)
            songartist = song.get(Jamaisvu.SONG_ARTIST, None)
            songalbum = song.get(Jamaisvu.SONG_ALBUM, None)
            songgenre = song.get(Jamaisvu.SONG_GENRE, None)
            songexplicit = song.get(Jamaisvu.SONG_EXPLICIT, True)  # Default to yes to explicit if there is no data, we don't want explicit songs on air
            songlength = song.get(Jamaisvu.SONG_LENGTH, 0)
        else:
            return None

        # return match info
        nseconds = round(float(largest) / fingerprint.DEFAULT_FS *
                         fingerprint.DEFAULT_WINDOW_SIZE *
                         fingerprint.DEFAULT_OVERLAP_RATIO, 5)
        song = {  # TODO: Replace this variable...
            Jamaisvu.SONG_ID: song_id,
            Jamaisvu.SONG_NAME: songname,
            Jamaisvu.SONG_ARTIST: songartist,
            Jamaisvu.SONG_ALBUM: songalbum,
            Jamaisvu.SONG_GENRE: songgenre,
            Jamaisvu.SONG_EXPLICIT: songexplicit,
            Jamaisvu.SONG_LENGTH: songlength,

            Jamaisvu.CONFIDENCE: largest_count,
            Jamaisvu.OFFSET: int(largest),
            Jamaisvu.OFFSET_SECS: nseconds,
            Database.FIELD_FILE_SHA1: song.get(Database.FIELD_FILE_SHA1, None)
            }

        return song

    def recognize(self, recognizer, *options, **kwoptions):
        r = recognizer(self)
        return r.recognize(*options, **kwoptions)


def _fingerprint_worker(filename, limit=None):
    # I only want the hashes. This function should not has any songdata otherwise
    try:
        filename, limit = filename
    except ValueError:
        pass

    channels, Fs, file_hash = decoder.read(filename, limit)
    result = set()
    channel_amount = len(channels)

    for channeln, channel in enumerate(channels):
        # TODO: Remove prints or change them into optional logging.
        print("Fingerprinting channel %d/%d for %s" % (channeln + 1,
                                                       channel_amount,
                                                       filename))
        hashes = fingerprint.fingerprint(channel, Fs=Fs)
        print("Finished channel %d/%d for %s" % (channeln + 1, channel_amount,
                                                 filename))
        result |= set(hashes)

    return result, file_hash


def chunkify(lst, n):
    """
    Splits a list into roughly n equal parts.
    http://stackoverflow.com/questions/2130016/splitting-a-list-of-arbitrary-size-into-only-roughly-n-equal-parts
    """
    return [lst[i::n] for i in xrange(n)]
