//
//  CLMovieDownloader.h
//  mpl
//
//  Created by Iskandar Safarov on 8/04/2016.
//  Copyright © 2016 castLabs GmbH. All rights reserved.
//

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import "CLMovieDownloadSet.h"

/**
 * NSURLSession identifier used by shared downloader instance.
 */
FOUNDATION_EXPORT NSString* _Nonnull const CLMovieDownloaderSharedSessionIdentifier;

/**
 * This class is designed to facilitate downloading media streams to local storage. It currently
 * supports DASH and SmoothStreaming based content.
 */
@interface CLMovieDownloader : NSObject

#pragma mark Properties

/**
 * Indicates if the downloader is allows downloads over a cellular network.
 */
@property (readonly) BOOL allowsCellularAccess;

/**
 * Indicates if the downloader is operating in discretionary mode (i.e. allowing iOS to schedule
 * transfers at times more optimal for the device).
 */
@property (readonly) BOOL discretionary;

/**
 * A directory used to store any downloaded files (defaults to "<DocumentsDir>/CLMovieDownloader").
 *
 * @remark For manually initialized downloader instances it is prefered to set the custom download
 *         path as apart of the init call.
 *
 *         For a shared downloader the path update should be done very early, before other
 *         components may have accessed the downloader (e.g. immediately after the application
 *         finishes launching).
 */
@property (nonnull) NSString* downloadPath;

/**
 * A dictionary of extra headers that will be included in each URL request (unless overridden by
 * the CLMovieDownloadSet).
 */
@property (nullable) NSDictionary<NSString*, NSString*>* additionalHeaders;

#pragma mark Listing & activation APIs

/**
 * A list of properties for movies with at least one downloaded quality.
 *
 * @remark The properties can be used to activate/fetch a download set for a movie, which provides
 *         more details about what is downloaded, allows additional quality selection, etc.
 */
@property (nullable, readonly) NSArray<CLMovieDownloadProperties*>* downloadedMovies;

#pragma mark Download APIs

/**
 * A list of all active download sets. May be nil if no sets are active.
 *
 * @remark The active download sets are ALL sets currently registered in the downloader, not just
 *         the sets currently downloading (i.e. including sets that have already completely
 *         downloaded or is yet to be started). To differentiate the states of the sets each one
 *         should be queried separately (e.g. status, selected-/downloaded-qualities, and so on).
 *         This does NOT include downloads completed by a previous instance of the downloader,
 *         unless they have been manually re-activated (see APIs below).
 */
@property (nullable, readonly) NSArray<CLMovieDownloadSet*>* activeDownloadSets;

/**
 * Activate, or return if already active, a download set for the movie represented by the given
 * properties (as available from the `downloadedMovies` array property).
 *
 * @param[in] props The movie download properties.
 * @return Returns a download set for the given properties, or nil if not available.
 */
- (nullable CLMovieDownloadSet*)activateDownloadSet:(nonnull CLMovieDownloadProperties*)props;

/**
 * Activate, or return if already active, a download set for the movie represented by the given URL.
 *
 * @param[in] url An URL for a stream manifest.
 * @return Returns a download set for the given properties, or nil if not available.
 */
- (nullable CLMovieDownloadSet*)activateDownloadSetForURL:(nonnull NSString*)url;

/**
 * Return active download set for the movie represented by the given URL.
 *
 * @param[in] url An URL for a stream manifest.
 * @return Returns download set for the given URL, or nil if not available.
 */
- (nullable CLMovieDownloadSet*)fetchActiveDownloadSetForURL:(nonnull NSString*)url;

/// Generate download id
/// @param url for specified url
- (nonnull NSString*)downloadIdWithURL:(nonnull NSString*)url;

/**
 * Fetch download set for the movie represented by the given URL. Once the set is available, or a
 * failure occurs, the given completion block is called.
 *
 * @remark If the URL has an active download set in the downloader it is immediately returned,
 *         which is basically equivalent to what `-[fetchActiveDownloadSetForURL:]` does.
 * @remark If the URL has a locally downloaded manifest it is used to create a download set. And the
 *         set is populated with information about already downloaded qualites, etc.
 * @remark If none of the above holds true the manifest is downloaded and used to create a
 *         download set.
 *
 * @param[in] url An URL for a stream manifest.
 * @param[in] block A completion block to call once the download set is available, or on failure.
 */
- (void)fetchDownloadSetForURL:(nonnull NSString*)url
               completionBlock:(nonnull void (^)(CLMovieDownloadSet* _Nullable set, NSError* _Nullable error))block;

/**
 * Start downloading of the given set.
 *
 * @remark This method may throw an exception.
 * @param[in] set Download set to start downloading.
 * @return YES if successful, NO otherwise.
 */
- (BOOL)download:(nonnull CLMovieDownloadSet*)set;

/**
 * Pause the download of a given set.
 *
 * @remark This method may throw an exception.
 * @param[in] set Download set to pause.
 * @return YES if successful (or if the set was already paused), NO otherwise.
 */
- (BOOL)pause:(nonnull CLMovieDownloadSet*)set;

/**
 * Resume the download of a given set.
 *
 * @remark This method may throw an exception.
 * @param[in] set Download set to resume.
 * @return YES if successful (or if the set was already running), NO otherwise.
 */
- (BOOL)resume:(nonnull CLMovieDownloadSet*)set;

/**
 * Cancel the download of a given set. Unless the set is already idle this will trigger a delegate
 * callback signaling that the set enters idle state. Once the call returns the set will be idle
 * and in a valid state for reuse.
 *
 * @remark This method may throw an exception.
 * @param[in] set Download set to cancel.
 * @return YES if successful (or if the set was already idle), NO otherwise.
 */
- (BOOL)cancel:(nonnull CLMovieDownloadSet*)set;

/**
 * Removes all local files related to a given set. If the set is running it will be cancelled before
 * removal. Once the call returns the set has been invalidated and can't be reused.
 *
 * @remark This will invalidate the download set.
 * @remark This method may throw an exception.
 * @param[in] set Download set to remove.
 * @return YES if successful, NO otherwise (e.g. if the set was not idle).
 */
- (BOOL)remove:(nonnull CLMovieDownloadSet*)set;

/**
 * Pause all active download sets.
 */
- (void)pauseAll;

/**
 * Resume all active download sets.
 */
- (void)resumeAll;

/**
 * Cancel all active download sets.
 */
- (void)cancelAll;

/**
 * Removes all locally downloaded files. Any running download set will be cancelled before removal.
 *
 * @remark This will invalidate all active download sets.
 */
- (void)removeAll;

#pragma mark Background Event Handling

/**
 * Calls the given completion handler once all queued events from a background session has been
 * processed. This API MUST be called from the application delegate
 * -[NSApplication application:handleEventsForBackgroundURLSession:completionHandler:] for
 * background downloads to work reliably.
 *
 * @param[in] completionHandler The completion handler to call.
 */
- (void)handleBackgroundEventsWithCompletionHandler:(void (^ _Nullable)(void))completionHandler;

#pragma mark Initialization

/**
 * Initialize a downloader instance with the given characteristics.
 *
 * @param[in] config Configuration for the background session
 * @param[in] path Specifies the path to keep the downloaded resources in (`nil` == default).
 */

- (nullable instancetype)initWithConfiguration:(nullable NSURLSessionConfiguration*)config
                            andDownloadPath:(nullable NSString*)path;

/**
 * Initialize a downloader instance with the given characteristics.
 *
 * @param[in] identifier The identifier of the background session.
 * @param[in] allowCellular Indicates if cellular access is allowed for the downloads.
 * @param[in] discretionary Indicates if downloader is in discretionary mode.
 * @param[in] path Specifies the path to keep the downloaded resources in (`nil` == default).
 *
 * @remark In discretionary mode the iOS schedules the downloads at appropriate times for optimal
 *         performance (see documentation for NSURLSessionConfiguration discretionary property).
 */

- (nullable instancetype)initWithIdentifier:(nullable NSString*)identifier
                             cellularAccess:(BOOL)allowCellular
                          discretionaryMode:(BOOL)discretionary
                            andDownloadPath:(nullable NSString*)path;

/**
 * Convenience method for initializing a downloader with default download path, equivalent to:
 *
 *   [[CLMovieDownloader alloc] initWithIdentifier:identifier
 *                                  cellularAccess:allowCellular
 *                               discretionaryMode:discretionary
 *                                 andDownloadPath:nil];
 */
- (nullable instancetype)initWithIdentifier:(nullable NSString*)identifier
                             cellularAccess:(BOOL)allowCellular
                       andDiscretionaryMode:(BOOL)discretionary;

/**
 * Convenience method for initializing a downloader with default characteristics, equivalent to:
 *
 *   [[CLMovieDownloader alloc] initWithIdentifier:identifier
 *                                  cellularAccess:NO
 *                            andDiscretionaryMode:NO];
 */
- (nullable instancetype)initWithIdentifier:(nullable NSString*)identifier;

/**
 * Convenience method for initialising a downloader with default characteristics, equivalent to:
 *
 *   [[CLMovieDownloader alloc] initWithIdentifier:nil];
 */
- (nonnull instancetype)init;

#pragma mark Singleton Access

/**
 * Retrieve the globally shared downloader instance. This downloader instance is configured to run
 * without using cellular access for transfers and not using discretionary mode, equivalent to a
 * downloader created using:
 *
 *   [[CLMovieDownloader alloc] initWithCellularAccess:NO andDiscretionaryMode:NO];
 *
 * If you need a downloader with different characteristics it will need to be manually created and
 * maintained using the regular alloc/init APIs.
 */
+ (nonnull CLMovieDownloader*)sharedDownloader;

@end
