<file_sep>//
//  ZACoreDataStack.swift
//  CoreDataStack
//
//  Created by <NAME> on 10/23/15.
//  Copyright © 2015 <NAME>. All rights reserved.
//

import CoreData


private var _stacks:[String:NSPersistentStoreCoordinator] = [:]

public class ZACoreDataStack {
    
    // MARK: - Private Attributes
        
    private let coreDataUbiquitizedKey = "com.apple.coredata.ubiquity.ubiquitized"
    private let iCloudEnabledKey = "CoreDataStack.iCloudEnabledKey"
    
    private let cloudStoreSuffix = "_cloud.sqlite"
    private let localStoreSuffix = "_local.sqlite"
    
    private var modelName: String? = nil
    private var _persistentStoreCoordinator: NSPersistentStoreCoordinator?
    private var _managedObjectContext: NSManagedObjectContext
    
    private lazy var applicationDocumentsDirectory: NSURL = {
        let urls = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)
        return urls[urls.count-1]
    }()
    
    // MARK: - Initialization and Deinitialization

    public convenience init(model:String) {
        self.init(key:model, model: model, isCloudEnabledByDefault: false)
    }
    
    public convenience init(model:String, isCloudEnabledByDefault:Bool) {
        self.init(key:model, model: model, isCloudEnabledByDefault: isCloudEnabledByDefault)
    }
    
    public convenience init(key:String, model:String) {
        self.init(key:key, model: model, isCloudEnabledByDefault: false)
    }
    
    public init(key:String, model:String, isCloudEnabledByDefault:Bool) {
        modelName = model
        _managedObjectContext = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)

        if model.characters.count > 0 {
            if let modelURL = NSBundle.mainBundle().URLForResource(model, withExtension: "momd"), let managedObjectModel = NSManagedObjectModel(contentsOfURL: modelURL) {
                
                if _stacks[key] != nil {
                    self._persistentStoreCoordinator = _stacks[key]
                    registerForStoreNotifications()
                }
                else {
                    self._persistentStoreCoordinator = NSPersistentStoreCoordinator(managedObjectModel: managedObjectModel)

                    if let coordinator = self._persistentStoreCoordinator {
                        do {
                            try configurePersistentStoreCoordinator(coordinator, isCloudEnabledByDefault: isCloudEnabledByDefault)
                            _managedObjectContext.persistentStoreCoordinator = coordinator
                            
                            // store global reference for sharing between stack instances
                            _stacks[key] = coordinator
                            
                            registerForStoreNotifications()
                        }
                        catch {
                            print("error configuring persistent store container")
                        }
                    }
                }
            }
            else {
                print("ERROR: model file not found")
            }
        }
        else {
            print("ERROR: model name cannot be emptystring")
        }
    }
    
    deinit {
        deRegisterForStoreNotifications()
    }
    
    
    // MARK: - Configure Stack Objects

    private func configurePersistentStoreCoordinator(persistentStoreCoordinator: NSPersistentStoreCoordinator, isCloudEnabledByDefault:Bool) throws {
        if let modelName = self.modelName {
            var options:[String:AnyObject]? = nil
            
            let url:NSURL
            let isCloudEnabled:Bool
            if NSUserDefaults.standardUserDefaults().objectForKey(iCloudEnabledKey) != nil {
                isCloudEnabled = NSUserDefaults.standardUserDefaults().boolForKey(iCloudEnabledKey)
            }
            else {
                isCloudEnabled = isCloudEnabledByDefault
                NSUserDefaults.standardUserDefaults().setBool(isCloudEnabledByDefault, forKey: iCloudEnabledKey)
            }

            if isCloudEnabled {
                url = self.applicationDocumentsDirectory.URLByAppendingPathComponent(self.cloudStoreName())
                options = [NSPersistentStoreUbiquitousContentNameKey : modelName, NSMigratePersistentStoresAutomaticallyOption: true, NSInferMappingModelAutomaticallyOption: true]
            }
            else {
                url = self.applicationDocumentsDirectory.URLByAppendingPathComponent(self.localStoreName())
                options = [NSMigratePersistentStoresAutomaticallyOption: true, NSInferMappingModelAutomaticallyOption: true]
            }
            
            try persistentStoreCoordinator.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: url, options: options)
            
            if self.isCoreDataCloudEnabled() {
                print("\nConfigured for CLOUD\n")
            }
            else {
                print("\nConfigured for LOCAL\n")
            }
        }
    }
    
    private func cloudStoreName() -> String {
        let result:String
        if let modelName = self.modelName {
            result = "\(modelName)\(cloudStoreSuffix)"
        }
        else {
            result = ""
        }
        return result
    }
    
    private func localStoreName() -> String {
        let result:String
        if let modelName = self.modelName {
            result = "\(modelName)\(localStoreSuffix)"
        }
        else {
            result = ""
        }
        return result
    }
    
    
    // MARK: - Stack Object Accessors

    public func currentPersistentStore() -> NSPersistentStore? {
        var store:NSPersistentStore? = nil
        
        if let persistentStoreCoordinator = _managedObjectContext.persistentStoreCoordinator {
            store = persistentStoreCoordinator.persistentStores[0]
        }
        
        return store
    }
    
    public func currentManagedObjectContext() -> NSManagedObjectContext {
        return self._managedObjectContext
    }
    

    // MARK: - Core Data Saving support
    
    public func saveContext () {
        if _managedObjectContext.hasChanges {
            do {
                try _managedObjectContext.save()
            } catch {
                // Replace this implementation with code to handle the error appropriately.
                // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
                let nserror = error as NSError
                NSLog("Unresolved error \(nserror), \(nserror.userInfo)")
                abort()
            }
        }
    }

    
    // MARK: - CoreData Notification Handlers

    private func registerForStoreNotifications() {
        let notificationCenter : NSNotificationCenter = NSNotificationCenter.defaultCenter();
        
        notificationCenter.addObserverForName(NSPersistentStoreCoordinatorStoresWillChangeNotification, object: self._persistentStoreCoordinator, queue: NSOperationQueue.mainQueue()) { (note : NSNotification) -> Void in
            print("\nNSPersistentStoreCoordinatorStoresWillChangeNotification\n")
            
            self._managedObjectContext.performBlock({ () -> Void in
                if self._managedObjectContext.hasChanges {
                    self.saveContext()
                }
                else {
                    self._managedObjectContext.reset()
                }
            })
            
            // TODO: disable user interface
        }
        
        notificationCenter.addObserverForName(NSPersistentStoreCoordinatorStoresDidChangeNotification, object: self._persistentStoreCoordinator, queue: NSOperationQueue.mainQueue()) { (note : NSNotification) -> Void in
            print("\nNSPersistentStoreCoordinatorStoresDidChangeNotification\n")
            
            if self.isCoreDataCloudEnabled() {
                print("\nCLOUD\n")
            }
            else {
                print("\nLOCAL\n")
            }

            // TODO: enable user interface
        }
        
        notificationCenter.addObserverForName(NSPersistentStoreCoordinatorWillRemoveStoreNotification, object: self._persistentStoreCoordinator, queue: NSOperationQueue.mainQueue()) { (note : NSNotification) -> Void in
            print("\nNSPersistentStoreCoordinatorWillRemoveStoreNotification\n")
        }
        
        notificationCenter.addObserverForName(NSPersistentStoreDidImportUbiquitousContentChangesNotification, object: self._persistentStoreCoordinator, queue: NSOperationQueue.mainQueue()) { (note : NSNotification) -> Void in
            print("\nNSPersistentStoreDidImportUbiquitousContentChangesNotification\n")
            
            self._managedObjectContext.performBlock({ () -> Void in
                self._managedObjectContext.mergeChangesFromContextDidSaveNotification(note)
            })
        }
    }
    
    private func deRegisterForStoreNotifications() {
        let notificationCenter : NSNotificationCenter = NSNotificationCenter.defaultCenter();
        notificationCenter.removeObserver(NSPersistentStoreCoordinatorStoresWillChangeNotification)
        notificationCenter.removeObserver(NSPersistentStoreCoordinatorStoresDidChangeNotification)
        notificationCenter.removeObserver(NSPersistentStoreCoordinatorWillRemoveStoreNotification)
        notificationCenter.removeObserver(NSPersistentStoreDidImportUbiquitousContentChangesNotification)
    }
    
    
    // MARK: - Core Data Cloud/Local Migration Methods

    public func isCoreDataCloudEnabled() -> Bool {
        var result = false
        
        if NSUserDefaults.standardUserDefaults().objectForKey(iCloudEnabledKey) != nil {
            result = NSUserDefaults.standardUserDefaults().boolForKey(iCloudEnabledKey)
        }
        
        return result
    }

    private func buildNewStore(url:NSURL, options:[NSObject : AnyObject], type:String) -> Bool {
        var result = false
        
        // remove any existing store at target location
        if NSFileManager.defaultManager().fileExistsAtPath(url.absoluteString) {
            do {
                try NSFileManager.defaultManager().removeItemAtURL(url)
            }
            catch {
                print("removeItemAtURL error - do we care?")
            }
        }
        
        if let currentStore = self.currentPersistentStore(), let coordinator = self._persistentStoreCoordinator {
            do {
                try coordinator.migratePersistentStore(currentStore, toURL: url, options: options, withType: type)
                result = true
            }
            catch {
                print("Unresolved error")
            }
        }
        
        return result
    }
    
    public func resetCloudData() {
        self.removeStoreFromCloud()
        
        if let store = self.currentPersistentStore() {
            self.currentManagedObjectContext().reset()
            
            do {
                try self.currentManagedObjectContext().persistentStoreCoordinator?.removePersistentStore(store)
                try NSPersistentStoreCoordinator.removeUbiquitousContentAndPersistentStoreAtURL(store.URL!, options: store.options)
            }
            catch {
                
            }
        }
    }
    
    public func removeStoreFromCloud() {
        if self.isCoreDataCloudEnabled() {
            var success = true
            
            // update options for local data store
            let newOptions:[NSObject : AnyObject] = [NSPersistentStoreRemoveUbiquitousMetadataOption : true]
            
            // set cloud store location
            let cloudURL = self.applicationDocumentsDirectory.URLByAppendingPathComponent(self.cloudStoreName())
            if let currentStore = self.currentPersistentStore(), let coordinator = self._persistentStoreCoordinator {
                do {
                    try coordinator.migratePersistentStore(currentStore, toURL: cloudURL, options: newOptions, withType: NSSQLiteStoreType)
                }
                catch {
                    print("Unresolved error")
                }
            }
            else {
                success = false
            }
            
            if success {
                NSUserDefaults.standardUserDefaults().setBool(false, forKey: iCloudEnabledKey)
            }
        }
    }

    public func moveStoreToCloud() {
        if !self.isCoreDataCloudEnabled() {
            var success = true
            if let modelName = self.modelName {
                
                // update options for local data store
                let newOptions:[NSObject : AnyObject] = [NSPersistentStoreUbiquitousContentNameKey : modelName]
                
                // set cloud store location
                let cloudURL = self.applicationDocumentsDirectory.URLByAppendingPathComponent(self.cloudStoreName())
                if let currentStore = self.currentPersistentStore(), let coordinator = self._persistentStoreCoordinator {
                    do {
                        try coordinator.migratePersistentStore(currentStore, toURL: cloudURL, options: newOptions, withType: NSSQLiteStoreType)
                    }
                    catch {
                        print("Unresolved error")
                    }
                }
                else {
                    success = false
                }
            }
            else {
                success = false
            }
            
            if success {
                NSUserDefaults.standardUserDefaults().setBool(true, forKey: iCloudEnabledKey)
            }
        }
    }
}

<file_sep>Pod::Spec.new do |s|

  s.platform              = :ios
  s.ios.deployment_target = '8.0'
  
  s.name         = "ZACoreDataStack"
  s.version      = "0.0.8"
  s.summary      = "ZACoreDataStack provides an independent core data stack initialized with just the model file name"

  s.homepage     = "https://github.com/zullevig/ZACoreDataStack"
  s.license      = { :type => "MIT", :file => "LICENSE" }
  s.author       = "<NAME>"
  s.source       = { :git => "https://github.com/zullevig/ZACoreDataStack.git", :tag => s.version }
  s.source_files = "ZACoreDataStack/**/*.{swift}"
  
  s.framework  = "CoreData"

end
<file_sep># ZACoreDataStack

A CoreData stack framework supporting the creation of the stack with just the name of the data model and enabling iCloud syncing by setting a single flag.

### Creating the CoreData stack

```
let coreDataStack = ZACoreDataStack(model: "CoreDataStack", isCloudEnabledByDefault: true)
```

### Installation with CocoaPods

To integrate ZACoreDataStack into your Xcode project using CocoaPods, specify it in your Podfile:

```
platform :ios, '8.0'
pod 'ZACoreDataStack', '~> 0.0.0'
```

Then, run the following command:

```
$ pod install
```
