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

import UIKit
import Kingfisher
import CocoaLumberjack

class MusicListDisplayHelper: NSObject {
	// MARK: Private Properties
	private let cellIdentifier = "MusicCollectionViewCell"
	private let sectionHeaderIdentifier = "sectionHeaderIdentifier"
	private let dragAllowed: Bool
	private let collectionView: UICollectionView
	private let countUpdater: ( (Int) -> () )?
	private let onItemSelected: ( (IndexPath, MusicListDisplayHelperItem) -> () )?
	
	private weak var viewController: UIViewController?
	private var dataSources: [MusicListDataSource]
	private var movingCell: UICollectionViewCell?
	
	private var longPressGestureRecognizer: UILongPressGestureRecognizer?
	private var tapGestureRecognizer: UITapGestureRecognizer?
	
	// MARK: Init/Deinit
	init(dataSources: [MusicListDataSource],
		 viewController: UIViewController,
		 collectionView: UICollectionView,
		 dragAllowed: Bool,
		 countUpdater: ((Int) -> ())?,
		 onItemSelected: ((IndexPath, MusicListDisplayHelperItem) -> ())?,
		 useNarrowRows: Bool = false) {
		self.dataSources = dataSources
		self.collectionView = collectionView
		self.dragAllowed = dragAllowed
		self.countUpdater = countUpdater
		self.onItemSelected = onItemSelected
		super.init()
		
		self.viewController = viewController
		for dataSource in dataSources {
			dataSource.updateRequestHandler = { [weak self] request in
				guard let self = self else {
					return
				}
				self.handleUpdateRequest(request: request)
			}
		}
		
		let nib = UINib(nibName: cellIdentifier, bundle: nil)
		collectionView.register(nib, forCellWithReuseIdentifier: cellIdentifier)
		
		collectionView.delegate = self
		collectionView.dataSource = self
		let layout = ListFlowLayout()
		if dataSources.count > 1 {
			self.collectionView.register(UICollectionReusableView.self,
										  forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader,
										  withReuseIdentifier: self.sectionHeaderIdentifier)
			layout.headerReferenceSize = CGSize(width: 1, height: 36)
		}
		let cellHeight = useNarrowRows ? MusicCollectionViewCell.narrowRowCellHeight : MusicCollectionViewCell.cellHeight
		layout.itemSize = CGSize(width: layout.widthForRow(forBounds:collectionView.bounds, collectionView: collectionView),
								 height: cellHeight)
		layout.minimumLineSpacing = 0
		collectionView.collectionViewLayout = layout
		collectionView.alwaysBounceVertical = true // To match iOS
		
		if dragAllowed {
			longPressGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(handleLongGesture(_:)))
			longPressGestureRecognizer!.cancelsTouchesInView = false
			collectionView.addGestureRecognizer(longPressGestureRecognizer!)
		}
		
		tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTapGesture(_:)))
		tapGestureRecognizer!.cancelsTouchesInView = false
		collectionView.addGestureRecognizer(tapGestureRecognizer!)
	}
	
	deinit {
		collectionView.removeGestureRecognizer(tapGestureRecognizer!)
		if let longPress = longPressGestureRecognizer {
			collectionView.removeGestureRecognizer(longPress)
		}
	}
}
	
// MARK: - Gesture Handlers
extension MusicListDisplayHelper {
	// We don't have UIButtons in the views. It's all handled via gesture recognizers
	@objc private func handleTapGesture(_ gesture: UITapGestureRecognizer) {
		if gesture.state == .ended {
			guard let selectedIndexPath = collectionView.indexPathForItem(at: gesture.location(in: self.collectionView)) else {
				return
			}
		
			let item = dataSources[selectedIndexPath.section].item(at: selectedIndexPath.row)
			
			if !shouldAllowTaps(forAccessory: item.accessory){
				return
			}
			
			if let gestureView = gesture.view {
				let width = gestureView.bounds.size.width
				let leftmostTapX = dragAllowed ? width * 0.2 : 0.0
				let location = gesture.location(in: gestureView)
				if item.accessory == .options && location.x > width * 0.8 {
					DDLogVerbose("Did Tap Option in \(selectedIndexPath)")
					showOptions(item: item, atIndexPath: selectedIndexPath)
				} else if location.x > leftmostTapX {
					if let selected = onItemSelected {
						DDLogVerbose("Did Select in \(selectedIndexPath)")
						selected(selectedIndexPath, item)
					}
				}
			}
		}
	}
	
	@objc private func handleLongGesture(_ gesture: UILongPressGestureRecognizer) {
		if !dragAllowed {
			return
		}
		
		switch(gesture.state) {
		case UIGestureRecognizer.State.began:
			guard let selectedIndexPath = collectionView.indexPathForItem(at: gesture.location(in: self.collectionView)) else {
				break
			}
			
			guard let gestureView = gesture.view else {
				break
			}
			let location = gesture.location(in: gestureView)
			if location.x > gestureView.bounds.size.width * 0.2 {
				// Only start drag if near reorder button
				break
			}
			
			movingCell = collectionView.cellForItem(at: selectedIndexPath)
			if movingCell != nil {
				collectionView.beginInteractiveMovementForItem(at: selectedIndexPath)
				animateMoveStart(cell: movingCell!)
			}
		case UIGestureRecognizer.State.changed:
			if movingCell != nil {
				let gestureView = gesture.view
				var location = gesture.location(in: gestureView)
				// We center the x for the gesture to match tableview behaviour
				let x = gestureView!.bounds.width / 2
				location.x = x
				
				collectionView.updateInteractiveMovementTargetPosition(location)
			}
		case UIGestureRecognizer.State.ended:
			if movingCell != nil {
				collectionView.endInteractiveMovement()
				animateMoveEnd(cell: movingCell!)
				movingCell = nil
			}
		default:
			if movingCell != nil {
				collectionView.cancelInteractiveMovement()
				animateMoveEnd(cell: movingCell!)
				movingCell = nil
			}
		}
	}
}

// MARK: - Private Methods
extension MusicListDisplayHelper {
	private func shouldAllowTaps(forAccessory accessory: MusicListDisplayHelperItemAccessory) -> Bool {
		return accessory == .options ||
			accessory == . none ||
			accessory == .unchecked ||
			accessory == .checked
	}
	
	private func showOptions(item: MusicListDisplayHelperItem, atIndexPath path: IndexPath) {
		guard let cell = collectionView.cellForItem(at: path) else {
			return
		}
		let alertController = UIAlertController(title: nil,
												message: nil,
												preferredStyle: .actionSheet)
		
		alertController.popoverPresentationController?.sourceView = cell
		alertController.popoverPresentationController?.sourceRect = CGRect(x: cell.bounds.width * 0.9,
																		   y: 0,
																		   width: cell.bounds.width * 0.1,
																		   height: cell.bounds.height)
		
		let optionAction: (UIAlertAction) -> () = { action in
			guard let title = action.title else {
				return
			}
			
			self.dataSources[path.section].performOption(title: title, at: path.row)
		}
		for optionIndex in 0..<item.options.count {
			let option = item.options[optionIndex]
			alertController.addAction(UIAlertAction(title: option,
													style: .default,
													handler: optionAction))
		}
		
		alertController.addAction(UIAlertAction(title: NSLocalizedString("Cancel", comment: "Cancel"),
												style: .cancel,
												handler: nil))
		
		viewController?.present(alertController, animated: true)
	}
	


	
	private func animateMoveStart(cell: UICollectionViewCell) {
		collectionView.bringSubviewToFront(cell)
		cell.layer.masksToBounds = false
		UIView.animate(withDuration: 0.1,
					   delay: 0.0,
					   options: [.allowUserInteraction, .beginFromCurrentState],
					   animations: { () -> Void in
						cell.alpha = ListFlowLayout.alphaForMove
						cell.layer.shadowRadius = 4
						cell.layer.shadowOffset = CGSize.zero
						cell.layer.shadowColor = UIColor.black.cgColor
						cell.layer.shadowOpacity = 0.5
		}, completion: nil)
	}
	
	private func animateMoveEnd(cell: UICollectionViewCell) {
		cell.alpha = 1.0
		cell.layer.masksToBounds = true
		cell.layer.shadowRadius = 0
		UIView.animate(withDuration: 0.1,
					   delay: 0.0,
					   options: [.allowUserInteraction, .beginFromCurrentState],
					   animations: { () -> Void in
						cell.alpha = 1.0
						cell.layer.shadowRadius = 0
		}, completion: { finished in
			cell.layer.masksToBounds = true
		})
	}
	
	private func handleUpdateRequest(request: MusicListDataSourceUpdateRequest) {
		switch request {
		case .insert(let section, let row):
			let path = IndexPath(row: row, section: section)
			collectionView.insertItems(at: [path])
			break
		case .reload(let section, let row):
			if let datasource = collectionView.dataSource {
				if section >= datasource.numberOfSections?(in: collectionView) ?? 1 ||
					row >= datasource.collectionView(collectionView, numberOfItemsInSection: section) {
					DDLogWarn("Request to reload out of range cells - abandoning")
					return
				}
			}
			
			DDLogVerbose("rows \(collectionView.numberOfItems(inSection: section))")
			let path = IndexPath(row: row, section: section)
			collectionView.reloadItems(at: [path])
		case .reloadSection(let section):
			if let datasource = collectionView.dataSource,
				section >= datasource.numberOfSections?(in: collectionView) ?? 1 {
				DDLogWarn("Request to reload out of range section - abandoning")
				return
			}
			if dataSources.count > 1 {
				collectionView.reloadData()
			} else {
				self.collectionView.reloadSections([section])
			}
			break
		case .remove(let section, let row):
			let path = IndexPath(row: row, section: section)
			collectionView.deleteItems(at: [path])
			break
		}
	}
}

// MARK: - UICollectionViewDelegate, UICollectionViewDataSource Conformance
extension MusicListDisplayHelper: UICollectionViewDelegate, UICollectionViewDataSource {
	func numberOfSections(in collectionView: UICollectionView) -> Int {
		return dataSources.count
	}
	
	func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
		let count = dataSources[section].numberOfItems()
		if let updater = countUpdater {
			updater(count)
		}
		return count
	}
	
	func collectionView(_ collectionView: UICollectionView, canFocusItemAt indexPath: IndexPath) -> Bool {
		return false
	}
	
	func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
		var cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellIdentifier, for: indexPath) as? MusicCollectionViewCell
		if cell == nil {
			cell = MusicCollectionViewCell()
		}

		let dataSource = dataSources[indexPath.section]
		let item = dataSource.item(at: indexPath.row)
		
		cell!.showReorder(dragAllowed)
		
		cell!.titleLabel?.text = item.title.uppercased()
		cell!.subtitleLabel?.text = item.subtitle

        if item.accessory == .checked {
            cell?.makeAccessible(item.title.capitalized, localizableSuffix: Accessibility.MediaLibrary.CellSuffix.selectedSuffix)
        } else {
        	cell?.makeAccessible(item.title.capitalized, doNotLocalizeForHuman: true)
        }

		if item.state == .fetching {
			cell?.artworkImageView?.loadAnimationImages(nameStem: "ind-progress-album-list", count: 20)
			cell?.artworkImageView?.startAnimating()
		} else {
			if cell!.artworkImageView?.isAnimating ?? false {
				cell?.artworkImageView?.stopAnimating()
			}
			
			cell!.showErrorState((item.state == .failed && item.imageLoadFailureIsError) || item.state == .unplayable)
			
			if let imageURL = item.imageURL, item.state != .failed {
				cell!.artworkImageView?.kf.setImage(with: URL(string: imageURL),
                                                    placeholder: UIImage(named: "album-art-default-med"),
                                                    completionHandler: { (result) in
                                                        switch result {
                                                        case .failure(let error):
                                                            DDLogVerbose("Image load failed: url: \(String(describing: imageURL)), error: \(String(describing: error))")
                                                            cell!.showErrorState(true)
                                                            self.dataSources[indexPath.section].updateItem(item,
                                                                                                           atIndex: indexPath.row,
                                                                                                           state: .failed)
                                                        default:
                                                            break
                                                        }
                })
			} else {
				if item.accessory == .checked || item.accessory == .unchecked {
					cell?.artworkImageView?.image = nil
				} else {
					cell!.artworkImageView?.image = UIImage(named: "album-art-default-med")
				}
			}
		}
		
		switch item.accessory {
		case .current:
			cell!.accessory = .current
		case .playing:
			cell!.accessory = .playing
		case .options:
			cell!.accessory = .options
		case .checked:
			cell!.accessory = .checked
		case .unchecked:
			cell!.accessory = .unchecked
		case .none:
			cell!.accessory = .none
		}
		
		return cell!
	}
	
	// headers
	
	func collectionView(_ collectionView: UICollectionView,
						viewForSupplementaryElementOfKind kind: String,
						at indexPath: IndexPath) -> UICollectionReusableView {
		var v : UICollectionReusableView! = nil
		if kind == UICollectionView.elementKindSectionHeader {
			v = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader,
																withReuseIdentifier: self.sectionHeaderIdentifier,
																for: indexPath)
            v.backgroundColor = ColorCompat.systemGroupedBackground
			if v.subviews.count == 0 {
				let lbl = UILabel(frame: CGRect(x: 22, y: 0, width: 200, height: 36))
				lbl.font = UIFont.systemFont(ofSize: 16, weight: .light)
                lbl.textColor = ColorCompat.secondaryLabel
				lbl.textAlignment = .left
				v.addSubview(lbl)
				
				let sep = UIView(frame: CGRect(x: 0, y: v.frame.size.height - 1, width: v.frame.size.width, height: 1))
				sep.autoresizingMask = .flexibleWidth
                sep.backgroundColor = ColorCompat.separator
				v.addSubview(sep)
			}
			let lab = v.subviews[0] as! UILabel
			lab.text = dataSources[indexPath.section].title.uppercased()
		}
		return v
	}
	
	func collectionView(_ collectionView: UICollectionView, moveItemAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
		dataSources[sourceIndexPath.section].moveItem(from: sourceIndexPath.row, to: destinationIndexPath.row)
	}
}
