//
// © 2017-2020 Qualcomm Technologies, Inc. and/or its subsidiaries. All rights reserved.
//

import UIKit

typealias MusicListDataSourceUpdateRequestHandler = (MusicListDataSourceUpdateRequest) -> ()
typealias MusicListDataSourceOperationCompletionHandler = (_ success: Bool, _ error: Error?) -> ()

enum MusicListDisplayHelperItemState {
	case placeholder
	case available
	case fetching
	case failed
	case unplayable
}

// MARK: -

enum MusicListDisplayHelperItemAccessory {
	case current
	case playing
	case options
	case checked
	case unchecked
	case none
}

// MARK: -

enum MusicListDataSourceUpdateRequest {
	case reloadSection(section: Int)
	case reload(section: Int, row: Int)
	case remove(section: Int, row: Int)
	case insert(section: Int, row: Int)
}

// MARK: -

class MusicListDisplayHelperItem {
	var state: MusicListDisplayHelperItemState = .placeholder
	
	var imageURL: String? = nil
	var imageLoadFailureIsError: Bool = true
	var title: String = ""
	var subtitle: String = ""
	var accessory: MusicListDisplayHelperItemAccessory = .none
	var options: [String] = []
}

// MARK: -

protocol MusicListDataSourceDelegate {
	var onItemsInvalidated: (() -> ())? { get set }
	var title: String {get}
    func itemsInitialState(completion: @escaping ([MusicListDisplayHelperItem]) -> () , completionQueue: DispatchQueue) -> ()
	func optionsForItem(_ item: MusicListDisplayHelperItem, atIndex index: Int) -> [String]
	func accessoryForItem(_ item: MusicListDisplayHelperItem, atIndex index: Int) -> MusicListDisplayHelperItemAccessory
	func mediaItemForItem(_ item: MusicListDisplayHelperItem, atIndex index: Int) -> IoTMediaItem?
}

// MARK: -

protocol MusicListDataSourceQueueOperationsDelegate {
	var currentPlayingIndex: Int { get }
	func playItem(_ playlist: IoTPlaylist,
				  atIndex index: Int,
				  now: Bool,
				  completion: @escaping MusicListDataSourceOperationCompletionHandler,
				  queue: DispatchQueue)
	func removeItemFromQueue(_ playlist: IoTPlaylist,
							 atIndex index: Int,
							 completion: @escaping MusicListDataSourceOperationCompletionHandler,
							 queue: DispatchQueue)
	func moveItem(_ playlist: IoTPlaylist,
				  from: Int, to: Int,
				  completion: @escaping MusicListDataSourceOperationCompletionHandler,
				  queue: DispatchQueue)
}

// MARK: -

protocol MusicListDataSourceUserPlaylistOperationsDelegate {
	func removeItemFromUserPlaylist(atIndex index: Int,
									completion: @escaping MusicListDataSourceOperationCompletionHandler,
									queue: DispatchQueue)
}

// MARK: -

protocol MusicListDataSourceFavoritesOperationsDelegate {
	func removeItemFromFavorites(atIndex index: Int,
								 completion: @escaping MusicListDataSourceOperationCompletionHandler,
								 queue: DispatchQueue)
	func moveItem(from: Int,
				  to: Int,
				  completion: @escaping MusicListDataSourceOperationCompletionHandler,
				  queue: DispatchQueue)
}

// MARK: -

final class MusicListDataSource {
	typealias OnAddToPlaylistCompletion = (UserPlaylist?) -> ()
	// MARK: Public Properties
	let playerGroup: IoTPlayerGroup
	let playerController: PlayerControllerProtocol
	let delegate: MusicListDataSourceDelegate
	
	var updateRequestHandler: MusicListDataSourceUpdateRequestHandler?
	var onAddToPlaylistHandler: ((@escaping OnAddToPlaylistCompletion) -> ())?
	var sectionIndex: Int = 0
	var title: String { return delegate.title }
	var queueDelegate: MusicListDataSourceQueueOperationsDelegate?
	var userPlaylistDelegate: MusicListDataSourceUserPlaylistOperationsDelegate?
	var favoritesDelegate: MusicListDataSourceFavoritesOperationsDelegate?
	// MARK: Private Properties
	private let syncQueue = DispatchQueue(label: "com.qualcomm.qti.MusicListDataSource.sync")
	private var items: [MusicListDisplayHelperItem] // Access should be on syncQueue
	// MARK: Initializer
	init(playerGroup: IoTPlayerGroup,
		 playerController: PlayerControllerProtocol,
		 delegate: MusicListDataSourceDelegate) {
		self.playerGroup = playerGroup
		self.playerController = playerController
		self.delegate = delegate
        var d = delegate
        self.items = []
        
        d.itemsInitialState(completion: { [weak self] newItems in
            guard let self = self else { return }
            self.items = newItems
            self.dispatchUpdateRequest(request: .reloadSection(section: self.sectionIndex))
        }, completionQueue: syncQueue)

		d.onItemsInvalidated = { [weak self] in
			guard let self = self else { return }
			self.reloadAllFromDelegate()
		}
	}
}

// MARK: Public Methods
extension MusicListDataSource {
	func numberOfItems() -> Int {
		let count = syncQueue.sync {
			return items.count
		}
		return count
	}

	func item(at: Int) -> MusicListDisplayHelperItem {
		let item: MusicListDisplayHelperItem = syncQueue.sync {
			let storedItem = items[at]
			updateOtherFields(storedItem, atIndex: at)
			return storedItem
		}
		return item
	}
	
	private func updateOtherFields(_ item: MusicListDisplayHelperItem, atIndex index: Int) {
		item.accessory = self.delegate.accessoryForItem(item, atIndex: index)
		item.options = self.delegate.optionsForItem(item, atIndex: index)
	}
	
	func updateItem(_ item: MusicListDisplayHelperItem,
					atIndex index: Int,
					state: MusicListDisplayHelperItemState) {
		syncQueue.async {
			item.state = state
			item.options = self.delegate.optionsForItem(item, atIndex: index)
		}
	}
	
	func moveItem(from: Int, to: Int) {
		let completionHandler: MusicListDataSourceOperationCompletionHandler = { [weak self] (success, error) in
			guard let self = self else {
				return
			}
			if success {
				let item = self.items[from]
				let myTo = (from > to) ? to : (to - 1)
				self.items.remove(at: from)
				self.items.insert(item, at: myTo)
			}
		}
		
		if let favHandler = favoritesDelegate {
			favHandler.moveItem(from: from,
								to: to,
								completion: completionHandler,
								queue: syncQueue)
		} else if let qHandler = queueDelegate,
            let playlist = playerGroup.playlist {
			qHandler.moveItem(playlist,
							  from: from,
							  to: to,
							  completion: completionHandler,
							  queue: syncQueue)
		}
	}
	
	func performOption(title: String, at: Int) {
		guard let option = MusicLibraryItemOptions.from(string: title) else {
			return
		}
		switch option {
		case .playNow:
			addItemToQueue(index: at, atEnd: true, playNow: true)
		case .playNext:
			addItemToQueue(index: at, atEnd: false, playNow: false)
		case .addToFavorites:
			addItemToFavorites(itemAt: at)
		case .addToPlaylist:
			if let handler = onAddToPlaylistHandler {
				handler({ [weak self] userPlaylist in
					guard let self = self else {
						return
					}
					
					if userPlaylist != nil {
						self.addItemToUserPlaylist(userPlaylist!, itemAt: at)
					}
				})
			}
			break
		case .addToQueue:
			addItemToQueue(index: at, atEnd: true, playNow: false)
		case .removeFromQueue:
			removeItemFromQueue(index: at)
		case .removeFromPlaylist:
			removeItemFromUserPlaylist(itemAt: at)
		case .removeFromFavorites:
			removeItemFromFavorites(itemAt: at)
		}
	}
	
	func dispatchUpdateRequest(request: MusicListDataSourceUpdateRequest) {
		DispatchQueue.main.async { [weak self] in
			guard let self = self,
				let handler = self.updateRequestHandler else {
					return
			}
			handler(request)
		}
	}
}

// MARK: - Private Methods
extension MusicListDataSource {
	private func addItemToFavorites(itemAt index: Int) {
		syncQueue.async { [weak self] in
			guard let self = self else { return }
			let item = self.items[index]
			if let mediaItem = self.delegate.mediaItemForItem(item, atIndex: index) {
				FavoritesController.sharedInstance.makeFavorite(item: mediaItem)
				item.options = self.delegate.optionsForItem(item, atIndex: index)
			}
		}
	}
	
	private func addItemToUserPlaylist(_ userPlaylist: UserPlaylist, itemAt index: Int) {
		syncQueue.async { [weak self] in
			guard let self = self else {
				return
			}
			let item = self.items[index]
			if let mediaItem = self.delegate.mediaItemForItem(item, atIndex: index) {
				userPlaylist.items.append(mediaItem)
				UserPlaylistsController.sharedInstance.updateUserPlaylist(userPlaylist)
			}
		}
	}
	
	// The remove methods rely on the delegate actually do the removal.
	
	private func removeItemFromFavorites(itemAt index: Int) {
		let completionHandler: MusicListDataSourceOperationCompletionHandler = { [weak self] (success, error) in
			guard let self = self else {
				return
			}
			if success {
				self.items.remove(at: index)
				self.dispatchUpdateRequest(request: .remove(section: self.sectionIndex, row: index))
			}
		}
		
		guard let handler = favoritesDelegate else {
			return
		}
		
		handler.removeItemFromFavorites(atIndex: index,
										completion: completionHandler,
										queue: syncQueue)
		
	}
	
	private func removeItemFromUserPlaylist(itemAt index: Int) {
		let completionHandler: MusicListDataSourceOperationCompletionHandler = { [weak self] (success, error) in
			guard let self = self else {
				return
			}
			if success {
				self.items.remove(at: index)
				self.dispatchUpdateRequest(request: .remove(section: self.sectionIndex, row: index))
			}
		}
		
		guard let handler = userPlaylistDelegate else {
			return
		}
		
		handler.removeItemFromUserPlaylist(atIndex: index,
										   completion: completionHandler,
										   queue: syncQueue)
	}
	
	private func removeItemFromQueue(index: Int) {
		let completionHandler: MusicListDataSourceOperationCompletionHandler = { [weak self] (success, error) in
			guard let self = self else {
				return
			}
			if success {
				self.items.remove(at: index)
				self.dispatchUpdateRequest(request: .remove(section: self.sectionIndex, row: index))
			}
		}
		
		guard let handler = queueDelegate,
        	let playlist = playerGroup.playlist else {
            return
		}
		
		handler.removeItemFromQueue(playlist,
									atIndex: index,
									completion: completionHandler,
									queue: syncQueue)
	}
	
	private func addItemToQueue(index: Int, atEnd: Bool, playNow: Bool) {
		if let qHandler = queueDelegate,
            let playlist = playerGroup.playlist {
			let currentIndex = qHandler.currentPlayingIndex
			let completionHandler: MusicListDataSourceOperationCompletionHandler = { [weak self] (success, error) in
				guard let self = self else {
					return
				}

                if success && !playNow {
                    // Play next has a move to after the current index
                    let to = currentIndex + 1
                    if index != to {
                        let item = self.items[index]
                        let myTo = (index > to) ? to : (to - 1)
                        self.items.remove(at: index)
                        self.items.insert(item, at: myTo)
                    }
                }
				self.dispatchUpdateRequest(request: .reloadSection(section: self.sectionIndex))
			}
			
			qHandler.playItem(playlist,
							  atIndex: index,
							  now: playNow,
							  completion: completionHandler,
							  queue: syncQueue)
		} else {
			syncQueue.async { [weak self] in
				guard let self = self else {
						return
				}
				let item = self.items[index]
				if let mediaItem = self.delegate.mediaItemForItem(item, atIndex: index),
                    let playlist = self.playerGroup.playlist {
					QueueOperationsHelper.sharedInstance.add(toPlaylist: playlist,
															 item: mediaItem,
															 atEnd: atEnd,
															 playNow: playNow,
															 completion: nil,
															 completionQueue: nil)
				}
			}
		}
	}
	
	private func reloadAllFromDelegate() {
        delegate.itemsInitialState(completion: { [weak self] newItems in
            guard let self = self else { return }
            self.items = newItems
            self.dispatchUpdateRequest(request: .reloadSection(section: self.sectionIndex))
        }, completionQueue: syncQueue)
	}
}

