<commit_before>//
//  URLListViewController.swift
//  Hipstapaper
//
//  Created by <NAME> on 12/16/16.
//  Copyright © 2016 <NAME>. All rights reserved.
//

import RealmSwift
import SafariServices
import UIKit

class URLListViewController: UIViewController, RealmControllable {
    
    @IBOutlet fileprivate weak var tableView: UITableView? {
        didSet {
            let imageNib = UINib(nibName: URLTableViewCell.withImageNIBName, bundle: Bundle(for: URLTableViewCell.self))
            let noImageNib = UINib(nibName: URLTableViewCell.withOutImageNIBName, bundle: Bundle(for: URLTableViewCell.self))
            self.tableView?.register(imageNib, forCellReuseIdentifier: URLTableViewCell.withImageNIBName)
            self.tableView?.register(noImageNib, forCellReuseIdentifier: URLTableViewCell.withOutImageNIBName)
            self.tableView?.allowsMultipleSelectionDuringEditing = true
            self.tableView?.rowHeight = URLTableViewCell.cellHeight
            self.tableView?.estimatedRowHeight = URLTableViewCell.cellHeight
        }
    }
    
    fileprivate typealias UIBBI = UIBarButtonItem
    fileprivate lazy var editBBI: UIBBI = UIBBI(barButtonSystemItem: UIBarButtonSystemItem.edit, target: self, action: #selector(self.editBBITapped(_:)))
    fileprivate lazy var doneBBI: UIBBI = UIBBI(barButtonSystemItem: UIBarButtonSystemItem.done, target: self, action: #selector(self.doneBBITapped(_:)))
    fileprivate lazy var archiveBBI: UIBBI = UIBBI(title: "📥Archive", style: .plain, target: self, action: #selector(self.archiveBBITapped(_:)))
    fileprivate lazy var unarchiveBBI: UIBBI = UIBBI(title: "📤Unarchive", style: .plain, target: self, action: #selector(self.unarchiveBBITapped(_:)))
    fileprivate lazy var tagBBI: UIBBI = UIBBI(title: "🏷Tag", style: .plain, target: self, action: #selector(self.tagBBITapped(_:)))
    fileprivate lazy var sortBBI: UIBBI = UIBBI(title: "Sort", style: .plain, target: self, action: #selector(self.sortBBITapped(_:)))
    fileprivate lazy var filterBBI: UIBBI = UIBBI(title: "Filter", style: .plain, target: self, action: #selector(self.filterBBITapped(_:)))
    fileprivate let flexibleSpaceBBI: UIBBI = UIBBI(barButtonSystemItem: .flexibleSpace, target: .none, action: .none)
    fileprivate let verticalBarSpaceBBI: UIBBI = {
        let bbi = UIBBI(title: "|", style: .plain, target: .none, action: .none)
        bbi.isEnabled = false
        return bbi
    }()
    
    
    // MARK: Selection
    
    var itemsToLoad = URLItem.ItemsToLoad.all
    var filter: URLItem.ArchiveFilter = .unarchived
    var sortOrder: URLItem.SortOrder = .recentlyAddedOnTop
    
    fileprivate weak var selectionDelegate: URLItemsToLoadChangeDelegate?
    
    // MARK: Data
    
    fileprivate var data: Results<URLItem>?
    
    weak var realmController: RealmController? {
        didSet {
            // forward the message to any presented view controllers
            let addRemoveTagPopoverVC = (self.presentedViewController as? UINavigationController)?.viewControllers.first as? RealmControllable
            addRemoveTagPopoverVC?.realmController = self.realmController
            
            // reload the data
            self.hardReloadData()
        }
    }
    
    fileprivate var selectedURLItems: [URLItem]? {
        guard
            let data = self.data,
            let indexPaths = self.tableView?.indexPathsForSelectedRows,
            indexPaths.isEmpty == false
        else { return .none}
        let items = indexPaths.map({ data[$0.row] })
        return items
    }
    
    convenience init(selectionDelegate: URLItemsToLoadChangeDelegate, controller: RealmController?) {
        self.init()
        self.selectionDelegate = selectionDelegate
        self.realmController = controller
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        
        // register for 3d touch events
        if case .available = self.traitCollection.forceTouchCapability {
            self.registerForPreviewing(with: self, sourceView: view)
        }
        
        // because we are in a split view, we fully own our own Navigation Controller
        // therefore we don't need to micromanage this when switching views.
        // its always present
        self.navigationController?.setToolbarHidden(false, animated: false)
        
        // configure myself for splitview
        self.navigationItem.leftBarButtonItem = self.splitViewController?.displayModeButtonItem
        self.navigationItem.leftItemsSupplementBackButton = true
        
        // put us into non-edit mode
        self.doneBBITapped(.none)
        self.disableAllBBI()
        
        // load the data
        self.hardReloadData()
    }
    
    fileprivate func hardReloadData() {
        // get values in case things change
        let itemsToLoad = self.itemsToLoad
        let filter = self.filter
        let sortOrder = self.sortOrder
        
        // set title
        switch itemsToLoad {
        case .all:
            self.title = "Hipstapaper"
        case .tag(let tagID):
            self.title = "🏷 \(tagID.displayName)"
        }
        
        // clear things out
        self.notificationToken?.stop()
        self.notificationToken = .none
        self.data = .none
        self.tableView?.reloadData()
        self.doneBBITapped(.none)
        
        // configure data source
        self.data = self.realmController?.url_loadAll(for: itemsToLoad, sortedBy: sortOrder, filteredBy: filter)
        self.notificationToken = self.data?.addNotificationBlock(self.realmResultsChangeClosure)
    }
    
    private lazy var realmResultsChangeClosure: ((RealmCollectionChange<Results<URLItem>>) -> Void) = { [weak self] changes in
        switch changes {
        case .initial:
            self?.tableView?.reloadData()
        case .update(_, let deletions, let insertions, let modifications):
            self?.tableView?.beginUpdates()
            self?.tableView?.deleteRows(at: deletions.map({ IndexPath(row: $0, section: 0)}), with: .left)
            self?.tableView?.insertRows(at: insertions.map({ IndexPath(row: $0, section: 0) }), with: .right)
            self?.tableView?.reloadRows(at: modifications.map({ IndexPath(row: $0, section: 0) }), with: .automatic)
            self?.tableView?.endUpdates()
            let itemsSelectedAfterUpdate = self?.selectedURLItems ?? []
            self?.updateBBI(with: itemsSelectedAfterUpdate)
        case .error(let error):
            let alert = UIAlertController(title: "Error Loading Reading List", message: error.localizedDescription, preferredStyle: .alert)
            let action = UIAlertAction(title: "Dismiss", style: .cancel, handler: .none)
            alert.addAction(action)
            self?.present(alert, animated: true, completion: .none)
            self?.data = .none
            self?.tableView?.reloadData()
        }
    }
    
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        self.tableView?.flashScrollIndicators()
        self.tableView?.deselectAllRows(animated: true)
    }
    
    private var notificationToken: NotificationToken?
    
    deinit {
        self.notificationToken?.stop()
    }
}

extension URLListViewController: URLItemsToLoadChangeDelegate {
    func didChange(itemsToLoad: URLItem.ItemsToLoad?, sortOrder: URLItem.SortOrder?, filter: URLItem.ArchiveFilter?, sender: ViewControllerSender) {
        switch sender {
        case .contentVC:
            fatalError()
        case .tertiaryVC:
            // if the user changes the selection in the custom VC, we need to notify the source list
            self.selectionDelegate?.didChange(itemsToLoad: itemsToLoad ?? self.itemsToLoad,
                                              sortOrder: sortOrder ?? self.sortOrder,
                                              filter: filter ?? self.filter,
                                              sender: .contentVC)
            // then fall through to follow the logic of normal selection coming from the source list
            fallthrough
        case .sourceListVC:
            var changedSomething = false
            if let itemsToLoad = itemsToLoad {
                self.itemsToLoad = itemsToLoad
                changedSomething = true
            }
            if let sortOrder = sortOrder {<commit_msg>added shake to delete to ios app<commit_after>//
//  URLListViewController.swift
//  Hipstapaper
//
//  Created by <NAME> on 12/16/16.
//  Copyright © 2016 <NAME>. All rights reserved.
//

import RealmSwift
import SafariServices
import UIKit

class URLListViewController: UIViewController, RealmControllable {
    
    @IBOutlet fileprivate weak var tableView: UITableView? {
        didSet {
            let imageNib = UINib(nibName: URLTableViewCell.withImageNIBName, bundle: Bundle(for: URLTableViewCell.self))
            let noImageNib = UINib(nibName: URLTableViewCell.withOutImageNIBName, bundle: Bundle(for: URLTableViewCell.self))
            self.tableView?.register(imageNib, forCellReuseIdentifier: URLTableViewCell.withImageNIBName)
            self.tableView?.register(noImageNib, forCellReuseIdentifier: URLTableViewCell.withOutImageNIBName)
            self.tableView?.allowsMultipleSelectionDuringEditing = true
            self.tableView?.rowHeight = URLTableViewCell.cellHeight
            self.tableView?.estimatedRowHeight = URLTableViewCell.cellHeight
        }
    }
    
    fileprivate typealias UIBBI = UIBarButtonItem
    fileprivate lazy var editBBI: UIBBI = UIBBI(barButtonSystemItem: UIBarButtonSystemItem.edit, target: self, action: #selector(self.editBBITapped(_:)))
    fileprivate lazy var doneBBI: UIBBI = UIBBI(barButtonSystemItem: UIBarButtonSystemItem.done, target: self, action: #selector(self.doneBBITapped(_:)))
    fileprivate lazy var archiveBBI: UIBBI = UIBBI(title: "📥Archive", style: .plain, target: self, action: #selector(self.archiveBBITapped(_:)))
    fileprivate lazy var unarchiveBBI: UIBBI = UIBBI(title: "📤Unarchive", style: .plain, target: self, action: #selector(self.unarchiveBBITapped(_:)))
    fileprivate lazy var tagBBI: UIBBI = UIBBI(title: "🏷Tag", style: .plain, target: self, action: #selector(self.tagBBITapped(_:)))
    fileprivate lazy var sortBBI: UIBBI = UIBBI(title: "Sort", style: .plain, target: self, action: #selector(self.sortBBITapped(_:)))
    fileprivate lazy var filterBBI: UIBBI = UIBBI(title: "Filter", style: .plain, target: self, action: #selector(self.filterBBITapped(_:)))
    fileprivate let flexibleSpaceBBI: UIBBI = UIBBI(barButtonSystemItem: .flexibleSpace, target: .none, action: .none)
    fileprivate let verticalBarSpaceBBI: UIBBI = {
        let bbi = UIBBI(title: "|", style: .plain, target: .none, action: .none)
        bbi.isEnabled = false
        return bbi
    }()
    
    
    // MARK: Selection
    
    var itemsToLoad = URLItem.ItemsToLoad.all
    var filter: URLItem.ArchiveFilter = .unarchived
    var sortOrder: URLItem.SortOrder = .recentlyAddedOnTop
    
    fileprivate weak var selectionDelegate: URLItemsToLoadChangeDelegate?
    
    // MARK: Data
    
    fileprivate var data: Results<URLItem>?
    
    weak var realmController: RealmController? {
        didSet {
            // forward the message to any presented view controllers
            let addRemoveTagPopoverVC = (self.presentedViewController as? UINavigationController)?.viewControllers.first as? RealmControllable
            addRemoveTagPopoverVC?.realmController = self.realmController
            
            // reload the data
            self.hardReloadData()
        }
    }
    
    fileprivate var selectedURLItems: [URLItem]? {
        guard
            let data = self.data,
            let indexPaths = self.tableView?.indexPathsForSelectedRows,
            indexPaths.isEmpty == false
        else { return .none}
        let items = indexPaths.map({ data[$0.row] })
        return items
    }
    
    convenience init(selectionDelegate: URLItemsToLoadChangeDelegate, controller: RealmController?) {
        self.init()
        self.selectionDelegate = selectionDelegate
        self.realmController = controller
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        
        // register for 3d touch events
        if case .available = self.traitCollection.forceTouchCapability {
            self.registerForPreviewing(with: self, sourceView: view)
        }
        
        // because we are in a split view, we fully own our own Navigation Controller
        // therefore we don't need to micromanage this when switching views.
        // its always present
        self.navigationController?.setToolbarHidden(false, animated: false)
        
        // configure myself for splitview
        self.navigationItem.leftBarButtonItem = self.splitViewController?.displayModeButtonItem
        self.navigationItem.leftItemsSupplementBackButton = true
        
        // put us into non-edit mode
        self.doneBBITapped(.none)
        self.disableAllBBI()
        
        // load the data
        self.hardReloadData()
    }
    
    fileprivate func hardReloadData() {
        // get values in case things change
        let itemsToLoad = self.itemsToLoad
        let filter = self.filter
        let sortOrder = self.sortOrder
        
        // set title
        switch itemsToLoad {
        case .all:
            self.title = "Hipstapaper"
        case .tag(let tagID):
            self.title = "🏷 \(tagID.displayName)"
        }
        
        // clear things out
        self.notificationToken?.stop()
        self.notificationToken = .none
        self.data = .none
        self.tableView?.reloadData()
        self.doneBBITapped(.none)
        
        // configure data source
        self.data = self.realmController?.url_loadAll(for: itemsToLoad, sortedBy: sortOrder, filteredBy: filter)
        self.notificationToken = self.data?.addNotificationBlock(self.realmResultsChangeClosure)
    }
    
    private lazy var realmResultsChangeClosure: ((RealmCollectionChange<Results<URLItem>>) -> Void) = { [weak self] changes in
        switch changes {
        case .initial:
            self?.tableView?.reloadData()
        case .update(_, let deletions, let insertions, let modifications):
            self?.tableView?.beginUpdates()
            self?.tableView?.deleteRows(at: deletions.map({ IndexPath(row: $0, section: 0)}), with: .left)
            self?.tableView?.insertRows(at: insertions.map({ IndexPath(row: $0, section: 0) }), with: .right)
            self?.tableView?.reloadRows(at: modifications.map({ IndexPath(row: $0, section: 0) }), with: .automatic)
            self?.tableView?.endUpdates()
            let itemsSelectedAfterUpdate = self?.selectedURLItems ?? []
            self?.updateBBI(with: itemsSelectedAfterUpdate)
        case .error(let error):
            let alert = UIAlertController(title: "Error Loading Reading List", message: error.localizedDescription, preferredStyle: .alert)
            let action = UIAlertAction(title: "Dismiss", style: .cancel, handler: .none)
            alert.addAction(action)
            self?.present(alert, animated: true, completion: .none)
            self?.data = .none
            self?.tableView?.reloadData()
        }
    }
    
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        self.tableView?.flashScrollIndicators()
        self.tableView?.deselectAllRows(animated: true)
    }
    
    // MARK: Watch for shake gesture for deleting
    
    private func validateDelete() -> Bool {
        guard self.presentedViewController == .none, let items = self.selectedURLItems, items.isEmpty == false else { return false }
        return true
    }
    
    private func shakeToDelete() {
        guard let realmController = self.realmController, let items = self.selectedURLItems, items.isEmpty == false else { return }
//        let itemString = items.reduce("") { input, item -> String in
//            let commaSeparator = input == "" ? "" : ", "
//            let title = item.extras?.pageTitle ?? "Untitled"
//            let trimLength = title.characters.count > 20 ? 20 : title.characters.count
//            let trimmed = title.substring(to: title.index(title.startIndex, offsetBy: trimLength))
//            let ellipsed = trimmed.characters.count < title.characters.count ? trimmed + "…" : trimmed
//            return input + commaSeparator + ellipsed
//        }
        let alert = UIAlertController(title: "Delete \(items.count) Item(s)?", message: "This action cannot be undone.", preferredStyle: .actionSheet)
        let cancel = UIAlertAction(title: "Cancel", style: .cancel, handler: .none)
        let delete = UIAlertAction(title: "Delete", style: .destructive) { action in
            realmController.delete(items)
        }
        alert.addAction(cancel)
        alert.addAction(delete)
        self.emergencyDismiss(thenPresentViewController: alert)
    }
    
    override func motionEnded(_ motion: UIEventSubtype, with event: UIEvent?) {
        switch motion {
        case .motionShake:
            guard self.validateDelete() else { super.motionEnded(motion, with: event); return; }
            self.shakeToDelete()
        default:
            super.motionEnded(motion, with: event)
        }
    }
    
    private var notificationToken: NotificationToken?
    
    deinit {
        self.notificationToken?.stop()
    }
}

extension URLListViewController: URLItemsToLoadChangeDelegate {
    func didChange(itemsToLoad: URLItem.ItemsToLoad?, sortOrder: URLItem.SortOrder?, filter: URLItem.ArchiveFilter?, sender: ViewControllerSender) {
        switch sender {
        case .contentVC:
            fatalError()
        case .tertiaryVC:
            // if the user changes the selection in the custom VC, we need to notify the source list
            self.selectionDelegate?.didChange(itemsToLoad: itemsToLoad ?? self.itemsToLoad,
                                              sortOrder: sortOrder ?? self.sortOrder,
                                              filter: filter ?? self.filter,
                                              sender: .contentVC)
            // then fall through to follow the logic of normal selection coming from the source list
            fallthrough
        case .sourceListVC:
            var changedSomething = false
            if let itemsToLoad = itemsToLoad {
                self.itemsToLoad = itemsToLoad
                changedSomething = true
            }
            if let sortOrder = sortOrder {
{'fraction_non_alphanumeric': 0.09366761715177274, 'fraction_numerical': 0.0021520417496099426, 'mean_word_length': 2.517123935666982, 'pattern_counts': {'":': 0, '<': 14, '<?xml version=': 0, '>': 18, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 12, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2979180', 'n_tokens_mistral': 5121, 'n_tokens_neox': 5011, 'n_words': 1257}