#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Copyright (c) 2011-2015 Slack
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

import lxml.html
import re
import json
import itertools
from .base import Scraper, ExceptionMixin, RequestMixin, UtilityMixin, LoggerMixin, StandardFactory, GetReleaseResultMixin, GetListResultMixin
from .base import SearchScraper as SearchScraperBase
from ..result import NotFoundResult


READABLE_NAME = 'iTunes Store'
SCRAPER_URL = 'http://itunes.apple.com/'
NOTES = 'Multiple track artists or release artists are not split correctly.'


class ReleaseScraper(GetReleaseResultMixin, Scraper, RequestMixin, ExceptionMixin, UtilityMixin, LoggerMixin):

    _base_url = 'http://itunes.apple.com/%s/album/'
    string_regex = '^http(?:s)?://itunes\.apple\.com/(\w{2,4})/album/([^/]*)/([^\?]+)[^/]*$'

    exclude_genres = ['music']

    def __init__(self, store, release_name, id):
        super(ReleaseScraper, self).__init__()
        self.id = id
        self.store = store
        self._release_name = release_name
        self._index = None

    def get_instance_info(self):
        return u'id=%s, store=%s' % (self.id, self.store)

    def get_url(self):
        return self._base_url % self.store + self._release_name + '/' + self.id

    def get_params(self):
        return {'ign-mpt': 'uo%3D4'}

    def _build_store_index(self):
        if self._index is None:
            self._index = {}
            for obj in self.data['included']:
                self._index[(obj['type'], obj['id'])] = obj

    def _get_store_obj_by_id(self, type, store_id):
        if self._index is None:
            self._build_store_index()
        return self._index.get((type, store_id))

    def _get_store_data(self):
        return self.data.get('data', {})

    def _check_if_release_artist_equals_track_artist(self):
        data = self._get_store_data()
        release_artist = data.get('attributes', {}).get('artistName')
        discs = self.get_disc_containers()
        self._release_artist_equal_track_artists = True
        for song in itertools.chain(*discs.values()):
            track_artist = song.get('attributes', {}).get('artistName')
            self._release_artist_equal_track_artists = release_artist == track_artist
            if not self._release_artist_equal_track_artists:
                break

    def process_initial_data(self, initial_data):
        #get the raw response content and parse it
        return lxml.html.document_fromstring(initial_data)

    def add_release_event(self):
        data = self._get_store_data()
        release_date = data.get('attributes', {}).get('releaseDate')
        if release_date:
            release_event = self.result.create_release_event()
            release_event.set_date(release_date)
            self.result.append_release_event(release_event)
        else:
            self.log_warning('no release date in store data')

    def add_release_title(self):
        data = self._get_store_data()
        release_title = data.get('attributes', {}).get('name')
        if release_title:
            self.result.set_title(release_title)
        else:
            self.log_warning('no release title in store data')

    def add_release_artists(self):
        data = self._get_store_data()
        artist_name = data.get('attributes', {}).get('artistName')
        if artist_name is None:
            self.log_warning('no release artist in store data')
        elif artist_name == 'Various Artists':
            artist = self.result.create_artist()
            artist.set_various(True)
            artist.append_type(self.result.ArtistTypes.MAIN)
            self.result.append_release_artist(artist)
        else:
            artist = self.result.create_artist()
            artist.set_name(artist_name)
            artist.append_type(self.result.ArtistTypes.MAIN)
            self.result.append_release_artist(artist)

    def add_genres(self):
        data = self._get_store_data()
        genres = data.get('relationships', {}).get('genres', {}).get('data', [])
        for genre in genres:
            if genre['type'] == 'genre':
                genre_data = self._get_store_obj_by_id(genre['type'], genre['id'])
                if genre_data:
                    genre_name = genre_data.get('attributes', {}).get('name')
                    if not genre_name.lower() in self.exclude_genres:
                        self.result.append_genre(genre_name)

    def get_disc_containers(self):
        discs = {1: []}
        old_track_num = 0
        disc_num = 1
        data = self._get_store_data()
        songs = data.get('relationships', {}).get('songs', {}).get('data', [])
        if not songs:
            self.log_warning('no songs in store data')
        for song in songs:
            if song['type'] == 'product/album/song':
                song_data = self._get_store_obj_by_id(song['type'], song['id'])
                attributes = song_data.get('attributes', {})
                if attributes.get('kind') == 'song':
                    track_num = attributes.get('trackNumber')
                    if not track_num:
                        self.log_warning('no track number for song: {!r}'.format(song_data))
                        continue
                    if track_num < old_track_num + 1:
                        disc_num += 1
                        discs[disc_num] = []
                    discs[disc_num].append(song_data)
                    old_track_num = track_num
        return discs

    def get_track_number(self, track_container):
        track_number = track_container.get('attributes', {}).get('trackNumber')
        if track_number:
            track_number = str(track_number)
        return track_number

    def get_track_artists(self, track_container):
        track_artists = []
        if not self._release_artist_equal_track_artists:
            track_artist = track_container.get('attributes', {}).get('artistName')
            # TODO: There seems to be no way to reliably determine whether there are multiple artists represented
            if track_artist:
                artist = self.result.create_artist()
                artist.set_name(track_artist)
                artist.append_type(self.result.ArtistTypes.MAIN)
                track_artists.append(artist)
        return track_artists

    def get_track_title(self, track_container):
        return track_container.get('attributes', {}).get('name')

    def get_track_length(self, track_container):
        track_length = None
        offers = track_container.get('relationships', {}).get('offers', {}).get('data', [])
        if len(offers) > 0:
            offer = self._get_store_obj_by_id(offers[0]['type'], offers[0]['id'])
            if offer:
                assets = offer.get('attributes', {}).get('assets', [])
                if len(assets) > 0:
                    track_length = assets[0].get('duration')
        return track_length
            
    def initialize_data(self):
        super(ReleaseScraper, self).initialize_data()
        if self.data is not None:
            #if the release does not exist, the website wants to connect to iTunes
            warning_div = self.data.cssselect('div.loadingbox')
            if len(warning_div) == 1:
                self.result = self.instantiate_result(NotFoundResult)
                return
            store_data = self.data.cssselect('script#shoebox-ember-data-store')
            if len(store_data) != 1:
                self.raise_exception(u'could not retrieve store data')
            store_data = store_data[0].text_content()
            try:
                self.data = json.loads(store_data)
            except:
                self.raise_exception(u'could not parse store data')
            self._check_if_release_artist_equals_track_artist()


class SearchScraper(GetListResultMixin, SearchScraperBase, RequestMixin, ExceptionMixin):

    url = 'http://itunes.apple.com/search'

    def get_url(self):
        return self.url

    def get_params(self):
        return {'media': 'music', 'entity': 'album', 'limit': '25', 'term': self.search_term}

    def process_initial_data(self, initial_data):
        try:
            response = json.loads(initial_data)
        except:
            self.raise_exception(u'invalid server response: %s' % initial_data)
        return response

    def get_release_containers(self):
        if 'results' in self.data:
            return self.data['results']
        return []

    def get_release_name(self, release_container):
        components = []
        for key in ['artistName', 'collectionName']:
            if key in release_container:
                components.append(release_container[key])
        name = u' \u2013 '.join(components)
        return name

    def get_release_info(self, release_container):
        components = []
        if 'releaseDate' in release_container:
            components.append(release_container['releaseDate'].split('T')[0])
        for key in ['country', 'primaryGenreName']:
            if key in release_container:
                components.append(release_container[key])
        info = u' | '.join(components)
        return info

    def get_release_url(self, releaseContainer):
        release_url = None
        if 'collectionViewUrl' in releaseContainer:
            release_url = releaseContainer['collectionViewUrl']
            m = re.match(ReleaseScraper.string_regex, release_url)
            if not m:
                release_url = None
        return release_url


class ScraperFactory(StandardFactory):

    scraper_classes = [ReleaseScraper]
    search_scraper = SearchScraper