//
//  iTunes.swift
//  Harmony
//
//  Created by Kim Wittenburg on 14.04.15.
//  Copyright (c) 2015 Das Code Kollektiv. All rights reserved.
//

import Foundation
import AppKitPlus

/// The Cocoa Scripting Bridge interface of iTunes.
public let iTunes = iTunesApplication(bundleIdentifier: "com.apple.iTunes")!

/// An ID as returned from the
/// [Search API](https://www.apple.com/itunes/affiliates/resources/documentation/itunes-store-web-service-search-api.html)
public typealias iTunesId = UInt


/// This struct contains static helper objects and methods to work with the
/// [Search API](https://www.apple.com/itunes/affiliates/resources/documentation/itunes-store-web-service-search-api.html).
public struct iTunesAPI {
    
    // MARK: Types
    
    /// Error types indicating error responses from the
    /// [Search API](https://www.apple.com/itunes/affiliates/resources/documentation/itunes-store-web-service-search-api.html).
    public enum Error: ErrorType {
        case InvalidCountryCode
        case InvalidLanguageCode
        case UnknownError
    }
    
    /// Contains constants identifying the fields in a response from the
    /// [Search API](https://www.apple.com/itunes/affiliates/resources/documentation/itunes-store-web-service-search-api.html).
    public enum Field: String {
        case WrapperType = "wrapperType"
        case Kind = "kind"
        
        case TrackId = "trackId"
        case TrackName = "trackName"
        case TrackCensoredName = "trackCensoredName"
        case ArtistName = "artistName"
        case ReleaseDate = "releaseDate"
        case TrackNumber = "trackNumber"
        case TrackCount = "trackCount"
        case DiscNumber = "discNumber"
        case DiscCount = "discCount"
        case PrimaryGenreName = "primaryGenreName"
        
        case CollectionId = "collectionId"
        case CollectionName = "collectionName"
        case CollectionCensoredName = "collectionCensoredName"
        case CollectionViewUrl = "collectionViewUrl"
        case CollectionArtistName = "collectionArtistName"
        
        case ArtworkUrl60 = "artworkUrl60"
        case ArtworkUrl100 = "artworkUrl100"
    }
    
    // MARK: Static Properties and Functions
    
    /// This formatter is configured to be used to parse dates returned from the
    /// [Search API](https://www.apple.com/itunes/affiliates/resources/documentation/itunes-store-web-service-search-api.html)
    internal static let sharedDateFormatter: NSDateFormatter = {
        let dateFormatter = NSDateFormatter()
        dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ"
        return dateFormatter
    }()
    
    /// Processes the data returned from a request to the
    /// [Search API](https://www.apple.com/itunes/affiliates/resources/documentation/itunes-store-web-service-search-api.html).
    /// The `data` has to be in a valid JSON format. See `NSJSONSerialization`
    /// for details.
    ///
    /// Currently only tracks and albums are supported. If there are any other
    /// entries in the specified data this function will raise an exception.
    ///
    /// - throws: Parsing errors and `iTUnesAPI.Error` constants.
    /// - returns: An array of albums populated with all associated tracks in the
    ///            specified data.
    public static func parseAPIData(data: NSData) throws -> [Album] {
        guard let parsedData = try NSJSONSerialization.JSONObjectWithData(data, options: []) as? [String: AnyObject] else {
            throw Error.UnknownError
        }
        // Handle API Errors
        if let errorMessage = parsedData["errorMessage"] as? String {
            switch errorMessage {
            case "Invalid value(s) for key(s): [country]":
                throw Error.InvalidCountryCode
            case "Invalid value(s) for key(s): [language]":
                throw Error.InvalidLanguageCode
            default:
                throw Error.UnknownError
            }
        }
        // Parse API Results
        var albums = [iTunesId: Album]()
        if let results = parsedData["results"] as? [[String: AnyObject]] {
            for result in results {
                let convertedResult = result.filter({ (key, value) -> Bool in Field(rawValue: key) != nil }).map({ (Field(rawValue: $0)!, $1)
                })
                let albumId = convertedResult[.CollectionId] as! iTunesId
                if albums[albumId] == nil {
                    albums[albumId] = Album(data: convertedResult)
                }
                if isTrack(convertedResult) {
                    albums[albumId]?.addTrack(Track(data: convertedResult))
                }
            }
        }
        return Array(albums.values)
    }
    
    private static func isTrack(data: [Field: AnyObject]) -> Bool {
        return data[.WrapperType] as! String == "track" && data[.Kind] as! String == "song"
    }
    
    /// Creates an URL that searches the
    /// [Search API](https://www.apple.com/itunes/affiliates/resources/documentation/itunes-store-web-service-search-api.html)
    /// for albums matching a specific `term`.
    ///
    /// This function respects the user's preferences (See `Preferences` class).
    ///
    /// - returns: The query URL or `nil` if `term` is invalid.
    public static func createAlbumSearchURLForTerm(term: String) -> NSURL? {
        var searchTerm = term.stringByReplacingOccurrencesOfString(" ", withString: "+")
        searchTerm = searchTerm.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet())!
        if searchTerm.isEmpty {
            return nil
        }
        return NSURL(string: "https://itunes.apple.com/search?term=\(searchTerm)&media=music&entity=album&limit=\(Preferences.sharedPreferences.numberOfSearchResults)&country=de&lang=de_DE")
    }
    
    /// Creates an URL that looks up all tracks that belong to the album with the
    /// specified `id` in the
    /// [Search API](https://www.apple.com/itunes/affiliates/resources/documentation/itunes-store-web-service-search-api.html).
    ///
    /// This function respects the user's preferences (See `Preferences` class).
    public static func createAlbumLookupURLForId(id: iTunesId) -> NSURL {
        return NSURL(string: "http://itunes.apple.com/lookup?id=\(id)&entity=song&country=de&lang=de_DE&limit=200")!
    }
}

/// Defines a type that can be parsed from a result of the
/// [Search API](https://www.apple.com/itunes/affiliates/resources/documentation/itunes-store-web-service-search-api.html).
public protocol iTunesType {
    
    /// Initializes the receiver with the specified data. The receiver may use
    /// all or only some of the values.
    ///
    /// This method requires `data` to contain the expected formats. If data
    /// contains no data or data in an invalid format for an expected key, this
    /// method raises an exception. To check wether a specified dictionary is
    /// valid use `canInitializeFromData`.
    init(data: [iTunesAPI.Field: AnyObject])
    
    /// Returns all fields that are required to initialize an instance of the
    /// receiving type.
    static var requiredFields: [iTunesAPI.Field] { get }
    
}

extension iTunesType {
    
    /// Returns wether the specified `data` can be used to initialize a instance
    /// of the receiving type.
    public static func canInitializeFromData(data: [iTunesAPI.Field: AnyObject]) -> Bool {
        for field in requiredFields {
            if data[field] == nil {
                return false
            }
        }
        return true
    }

}
