#!/usr/bin/python
# Made with <3 by Fran González (@spaceisstrange)
# This program 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.
# This program 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 this program. If not, see <http://www.gnu.org/licenses/>

import sys
import requests
import init_proxy


# --------
# Lookups
# --------
def lookup(id=None, artist_amg_id=None, upc=None, country='US', media='all', entity=None, attribute=None, limit=None, proxy=None):
    if limit is None:
        limit = 50
    # If none of the basic lookup arguments are provided, raise a ValueError
    if id is None and artist_amg_id is None and upc is None:
        raise ValueError(lookup_no_ids)

    lookup_url = _url_lookup_builder(id, entity, limit)

    if proxy is None:
        # request without proxy server
        r = requests.get(lookup_url)
    else:
        # request with proxy server
        proxy = init_proxy.curl()
        try:
            r = requests.get(lookup_url, proxies=proxy['proxy'], headers=proxy['header'])
        except Exception:
            retry = 0
            while retry < 3:
                try:
                    r = requests.get(lookup_url, proxies=proxy['proxy'], headers=proxy['header'])
                    break
                except Exception:
                    retry += 1
            if retry > 2:
                detail = sys.exc_info()
                print(detail)
                return {'results': [], 'resultCount': 0}
    try:
        json = r.json()['results']
        result_count = r.json()['resultCount']
    except:
        raise ConnectionError(general_no_connection)

    if result_count == 0:
        raise LookupError(lookup_error)

    return _get_result_list(json)


# --------
# Parameters
# --------
base_lookup_url = 'https://itunes.apple.com/lookup?'
ampersand = '&'
entities = {
    'song': 'song',
}
parameters = {
    0: 'id=',
    1: 'entity=',
    2: 'limit=',
}
general_no_connection = 'Cannot fetch JSON data'
search_error = 'No results found with the keyword '
artist_search_error = 'No artists found with the keyword '
album_search_error = 'No albums found with the keyword '
track_search_error = 'No tracks found with the keyword '
lookup_error = 'No results with the given parameters'
lookup_no_ids = 'No id, amg id or upc arguments provided'


# --------
# Class for parsing JSON
# --------
class ResultItem(object):
    """
    Defines a general result item
    """
    def __init__(self, json):
        """
        Initializes the ResultItem class from the JSON provided
        :param json: String. Raw JSON data to fetch information from
        """
        self.artist_name = json['artistName']
        self.type = None

        if 'wrapperType' in json:
            self.type = json['wrapperType']

            if 'collectionType' in json:
                self.collection_type = json['collectionType']
            elif 'artistType' in json:
                self.artist_type = json['artistType']
            elif 'kind' in json:
                self.track_type = json['kind']
        elif 'kind' in json:
            self.type = json['kind']

        if 'primaryGenreName' in json:
            self.primary_genre_name = json['primaryGenreName']

        if 'artistId' in json:
            self.artist_id = json['artistId']

        if 'artistLinkUrl' in json:
            self.artist_link_url = json['artistLinkUrl']

        if 'radioStationUrl' in json:
            self.artist_radio_url = json['radioStationUrl']

        if 'primaryGenreId' in json:
            self.primary_genre_id = json['primaryGenreId']

        if 'amgArtistId' in json:
            self.artist_amg_id = json['amgArtistId']

        if 'artistViewUrl' in json:
            self.artist_view_url = json['artistViewUrl']

        if 'trackName' in json:
            self.track_name = json['trackName']

        if 'trackId' in json:
            self.track_id = json['trackId']

        if 'trackCensoredName' in json:
            self.track_censored_name = json['trackCensoredName']

        if 'trackViewUrl' in json:
            self.track_view_url = json['trackViewUrl']

        if 'trackCount' in json:
            self.track_count = json['trackCount']

        if 'trackNumber' in json:
            self.track_number = json['trackNumber']

        if 'previewUrl' in json:
            self.preview_url = json['previewUrl']

        if 'artworkUrl30' in json:
            self.artwork_url_30 = json['artworkUrl30']

        if 'artworkUrl60' in json:
            self.artwork_url_60 = json['artworkUrl60']

        if 'artworkUrl100' in json:
            self.artwork_url_100 = json['artworkUrl100']

        if 'artworkUrl512' in json:
            self.artwork_url_512 = json['artworkUrl512']

        if 'collectionName' in json:
            self.collection_name = json['collectionName']

        if 'collectionId' in json:
            self.collection_id = json['collectionId']

        if 'collectionCensoredName' in json:
            self.collection_censored_name = json['collectionCensoredName']

        if 'collectionViewUrl' in json:
            self.collection_view_url = json['collectionViewUrl']

        if 'collectionPrice' in json:
            self.collection_price = json['collectionPrice']

        if 'trackPrice' in json:
            self.track_price = json['trackPrice']

        if 'collectionHdPrice' in json:
            self.collection_hd_price = json['collectionHdPrice']

        if 'trackHdPrice' in json:
            self.track_hd_price = json['trackHdPrice']

        if 'releaseDate' in json:
            self.release_date = json['releaseDate']

        if 'collectionExplicitness' in json:
            self.collection_explicitness = json['collectionExplicitness']

        if 'trackExplicitness' in json:
            self.track_explicitness = json['trackExplicitness']

        if 'trackTimeMillis' in json:
            self.track_time = json['trackTimeMillis']

        if 'discCount' in json:
            self.disc_count = json['discCount']

        if 'discNumber' in json:
            self.disc_number = json['discNumber']

        if 'country' in json:
            self.country = json['country']

        if 'currency' in json:
            self.currency = json['currency']

        if 'copyright' in json:
            self.copyright = json['copyright']

        if 'contentAdvisoryRating' in json:
            self.content_advisory_rating = json['contentAdvisoryRating']

        if 'shortDescription' in json:
            self.short_description = json['shortDescription']

        if 'longDescription' in json:
            self.long_description = json['longDescription']

        if 'isStreamable' in json:
            self.is_streamable = json['isStreamable']

        if 'fileSizeBytes' in json:
            self.file_size = json['fileSizeBytes']

        if 'genres' in json:
            self.genres = json['genres']

        if 'price' in json:
            self.price = json['price']

        if 'description' in json:
            self.description = json['description']

        if 'genreIds' in json:
            self.genre_ids = json['genreIds']

        if 'artistIds' in json:
            self.artist_ids = json['artistIds']

        if 'formattedPrice' in json:
            self.formatted_price = json['formattedPrice']

        if 'averageUserRating' in json:
            self.average_user_rating = json['averageUserRating']

        if 'userRatingCount' in json:
            self.user_rating_count = json['userRatingCount']

        if 'screenshotUrls' in json:
            self.screenshot_urls = json['screenshotUrls']

        if 'ipadScreenshotUrls' in json:
            self.ipad_screenshot_urls = json['ipadScreenshotUrls']

        if 'features' in json:
            self.features = json['features']

        if 'supportedDevices' in json:
            self.supported_devices = json['supportedDevices']

        if 'advisories' in json:
            self.advisories = json['advisories']

        if 'isGameCenterEnabled' in json:
            self.is_game_center_enabled = json['isGameCenterEnabled']

        if 'userRatingCountForCurrentVersion' in json:
            self.user_rating_count_for_current_version = json['userRatingCountForCurrentVersion']

        if 'languageCodesISO2A' in json:
            self.language_codes = json['languageCodesISO2A']

        if 'averageUserRatingForCurrentVersion' in json:
            self.average_user_rating_for_current_version = json['averageUserRatingForCurrentVersion']

        if 'trackContentRating' in json:
            self.track_content_rating = json['trackContentRating']

        if 'version' in json:
            self.version = json['version']

        if 'sellerName' in json:
            self.seller_name = json['sellerName']

        if 'sellerUrl' in json:
            self.seller_url = json['sellerUrl']

        if 'bundleId' in json:
            self.bundle_id = json['bundleId']

        if 'releaseNotes' in json:
            self.release_notes = json['releaseNotes']

        if 'isVppDeviceBasedLicensingEnabled' in json:
            self.is_vpp_device_based_licensing_enabled = json['isVppDeviceBasedLicensingEnabled']

        if 'minimumOsVersion' in json:
            self.minimum_os_version = json['minimumOsVersion']

    def __repr__(self):
        """
        Retrieves all keys in the class as a String
        :return: String. All the keys available in the class
        """
        string = ''

        for key, value in self.__dict__.items():
            if not key.startswith('__'):
                string += '\n' + key + ':' + str(value)

        return string


class Track(ResultItem):
    """
    Defines a Track whether it's a music track or a movie or a show episode
    """
    def __init__(self, json):
        """
        Initializes the ResultItem class from the JSON provided
        :param json: String. Raw JSON data to fetch information from
        """
        ResultItem.__init__(self, json)

    def get_track_time_minutes(self, round_number=2):
        """
        Retrieves the track's length and converts it to minutes
        :param round_number: Int. Number of decimals to round the minutes
        :return: Int. Track length in minutes
        """
        return round(self.track_time / 60000, round_number)

    def get_track_time_hours(self, round_number=2):
        """
        Retrieves the track's length and converts it to hours
        :param round_number: Int. Number of decimals to round the hours
        :return: Int. Track length in hours
        """
        return round(self.track_time / 6000000, round_number)


class MusicArtist(ResultItem):
    """
    Defines an Music Artist
    """
    def __init__(self, json):
        """
        Initializes the ResultItem class from the JSON provided
        :param json: String. Raw JSON data to fetch information from
        """
        ResultItem.__init__(self, json)

    def get_albums(self):
        """
        Retrieves all the albums by the artist
        :return: List. Albums published by the artist
        """
        return lookup(id=self.artist_id, entity=entities['album'])[1:]


# --------
# Private methods
# --------
def _get_result_list(json):
    result_list = []

    for item in json:
        if 'wrapperType' in item:
            # Music
            if item['wrapperType'] == 'artist' and item['artistType'] == 'Artist':
                music_artist_result = MusicArtist(item)
                result_list.append(music_artist_result)
            elif item['wrapperType'] == 'track' and item['kind'] == 'song':
                music_track_result = Track(item)
                result_list.append(music_track_result)
        else:
            unknown_result = ResultItem(item)
            result_list.append(unknown_result)

    return result_list


def _url_lookup_builder(id=None, entity=None, limit=None):
    built_url = base_lookup_url
    has_one_argument = False

    if id is not None:
        built_url += parameters[0] + str(id)
        has_one_argument = True

    if entity is not None:
        built_url += ampersand + parameters[1] + entity

    built_url += ampersand + parameters[2] + str(limit)

    return built_url


def _parse_query(query):
    """
    Replaces every space in the provided query with a +
    :param query: term to delete spaces
    :return: query without spaces as a string
    """
    return str(query).replace(' ', '+')