<commit_before>//
//  NavigationModelController.swift
//  NetNewsWire-iOS
//
//  Created by <NAME> on 4/21/19.
//  Copyright © 2019 Ranchero Software. All rights reserved.
//

import Foundation
import Account
import Articles
import RSCore
import RSTree

public extension Notification.Name {
	static let MasterSelectionDidChange = Notification.Name(rawValue: "MasterSelectionDidChange")
	static let BackingStoresDidRebuild = Notification.Name(rawValue: "BackingStoresDidRebuild")
	static let ArticlesReinitialized = Notification.Name(rawValue: "ArticlesReinitialized")
	static let ArticleDataDidChange = Notification.Name(rawValue: "ArticleDataDidChange")
	static let ArticlesDidChange = Notification.Name(rawValue: "ArticlesDidChange")
	static let ArticleSelectionDidChange = Notification.Name(rawValue: "ArticleSelectionDidChange")
}

class NavigationStateController {

	static let fetchAndMergeArticlesQueue = CoalescingQueue(name: "Fetch and Merge Articles", interval: 0.5)
	
	private var articleRowMap = [String: Int]() // articleID: rowIndex
	
	private var animatingChanges = false
	private var expandedNodes = [Node]()
	private var shadowTable = [[Node]]()
	
	private var sortDirection = AppDefaults.timelineSortDirection {
		didSet {
			if sortDirection != oldValue {
				sortDirectionDidChange()
			}
		}
	}

	private let treeControllerDelegate = FeedTreeControllerDelegate()
	lazy var treeController: TreeController = {
		return TreeController(delegate: treeControllerDelegate)
	}()
	
	var rootNode: Node {
		return treeController.rootNode
	}
	
	var numberOfSections: Int {
		return shadowTable.count
	}

	var currentMasterIndexPath: IndexPath? {
		didSet {
			guard let ip = currentMasterIndexPath, let node = nodeFor(ip) else {
				assertionFailure()
				return
			}
			if let fetcher = node.representedObject as? ArticleFetcher {
				timelineFetcher = fetcher
			}
			NotificationCenter.default.post(name: .MasterSelectionDidChange, object: self, userInfo: nil)
		}
	}
	
	var timelineName: String? {
		return (timelineFetcher as? DisplayNameProvider)?.nameForDisplay
	}
	
	var timelineFetcher: ArticleFetcher? {
		didSet {
			currentArticleIndexPath = nil
			if timelineFetcher is Feed {
				showFeedNames = false
			} else {
				showFeedNames = true
			}
			fetchArticles()
			NotificationCenter.default.post(name: .ArticlesReinitialized, object: self, userInfo: nil)
		}
	}
	
	
	var showFeedNames = false
	var showAvatars = false

	var isPrevArticleAvailable: Bool {
		guard let indexPath = currentArticleIndexPath else {
			return false
		}
		return indexPath.row > 0
	}
	
	var isNextArticleAvailable: Bool {
		guard let indexPath = currentArticleIndexPath else {
			return false
		}
		return indexPath.row + 1 < articles.count
	}
	
	var prevArticleIndexPath: IndexPath? {
		guard let indexPath = currentArticleIndexPath else {
			return nil
		}
		return IndexPath(row: indexPath.row - 1, section: indexPath.section)
	}
	
	var nextArticleIndexPath: IndexPath? {
		guard let indexPath = currentArticleIndexPath else {
			return nil
		}
		return IndexPath(row: indexPath.row + 1, section: indexPath.section)
	}
	
	var firstUnreadArticleIndexPath: IndexPath? {
		for (row, article) in articles.enumerated() {
			if !article.status.read {
				return IndexPath(row: row, section: 0)
			}
		}
		return nil
	}
	
	var currentArticle: Article? {
		if let indexPath = currentArticleIndexPath {
			return articles[indexPath.row]
		}
		return nil
	}
	
	var currentArticleIndexPath: IndexPath? {
		didSet {
			if currentArticleIndexPath != oldValue {
				NotificationCenter.default.post(name: .ArticleSelectionDidChange, object: self, userInfo: nil)
			}
		}
	}
	
	var articles = ArticleArray() {
		didSet {
			if articles == oldValue {
				return
			}
			if articles.representSameArticlesInSameOrder(as: oldValue) {
				articleRowMap = [String: Int]()
				NotificationCenter.default.post(name: .ArticleDataDidChange, object: self, userInfo: nil)
				return
			}
			updateShowAvatars()
			articleRowMap = [String: Int]()
			NotificationCenter.default.post(name: .ArticlesDidChange, object: self, userInfo: nil)
		}
	}
	
	var isTimelineUnreadAvailable: Bool {
		if let unreadProvider = timelineFetcher as? UnreadCountProvider {
			return unreadProvider.unreadCount > 0
		}
		return false
	}
	
	var isAnyUnreadAvailable: Bool {
		return appDelegate.unreadCount > 0
	}
	
	init() {

		for section in treeController.rootNode.childNodes {
			expandedNodes.append(section)
			shadowTable.append([Node]())
		}
		
		rebuildShadowTable()
		
		NotificationCenter.default.addObserver(self, selector: #selector(containerChildrenDidChange(_:)), name: .ChildrenDidChange, object: nil)
		NotificationCenter.default.addObserver(self, selector: #selector(batchUpdateDidPerform(_:)), name: .BatchUpdateDidPerform, object: nil)
		NotificationCenter.default.addObserver(self, selector: #selector(displayNameDidChange(_:)), name: .DisplayNameDidChange, object: nil)
		NotificationCenter.default.addObserver(self, selector: #selector(accountStateDidChange(_:)), name: .AccountStateDidChange, object: nil)
		NotificationCenter.default.addObserver(self, selector: #selector(accountsDidChange(_:)), name: .AccountsDidChangeNotification, object: nil)
		
		NotificationCenter.default.addObserver(self, selector: #selector(userDefaultsDidChange(_:)), name: UserDefaults.didChangeNotification, object: nil)<commit_msg>Rename AccountsDidChangeNotification to AccountsDidChange to match other notification names<commit_after>//
//  NavigationModelController.swift
//  NetNewsWire-iOS
//
//  Created by <NAME> on 4/21/19.
//  Copyright © 2019 Ranchero Software. All rights reserved.
//

import Foundation
import Account
import Articles
import RSCore
import RSTree

public extension Notification.Name {
	static let MasterSelectionDidChange = Notification.Name(rawValue: "MasterSelectionDidChange")
	static let BackingStoresDidRebuild = Notification.Name(rawValue: "BackingStoresDidRebuild")
	static let ArticlesReinitialized = Notification.Name(rawValue: "ArticlesReinitialized")
	static let ArticleDataDidChange = Notification.Name(rawValue: "ArticleDataDidChange")
	static let ArticlesDidChange = Notification.Name(rawValue: "ArticlesDidChange")
	static let ArticleSelectionDidChange = Notification.Name(rawValue: "ArticleSelectionDidChange")
}

class NavigationStateController {

	static let fetchAndMergeArticlesQueue = CoalescingQueue(name: "Fetch and Merge Articles", interval: 0.5)
	
	private var articleRowMap = [String: Int]() // articleID: rowIndex
	
	private var animatingChanges = false
	private var expandedNodes = [Node]()
	private var shadowTable = [[Node]]()
	
	private var sortDirection = AppDefaults.timelineSortDirection {
		didSet {
			if sortDirection != oldValue {
				sortDirectionDidChange()
			}
		}
	}

	private let treeControllerDelegate = FeedTreeControllerDelegate()
	lazy var treeController: TreeController = {
		return TreeController(delegate: treeControllerDelegate)
	}()
	
	var rootNode: Node {
		return treeController.rootNode
	}
	
	var numberOfSections: Int {
		return shadowTable.count
	}

	var currentMasterIndexPath: IndexPath? {
		didSet {
			guard let ip = currentMasterIndexPath, let node = nodeFor(ip) else {
				assertionFailure()
				return
			}
			if let fetcher = node.representedObject as? ArticleFetcher {
				timelineFetcher = fetcher
			}
			NotificationCenter.default.post(name: .MasterSelectionDidChange, object: self, userInfo: nil)
		}
	}
	
	var timelineName: String? {
		return (timelineFetcher as? DisplayNameProvider)?.nameForDisplay
	}
	
	var timelineFetcher: ArticleFetcher? {
		didSet {
			currentArticleIndexPath = nil
			if timelineFetcher is Feed {
				showFeedNames = false
			} else {
				showFeedNames = true
			}
			fetchArticles()
			NotificationCenter.default.post(name: .ArticlesReinitialized, object: self, userInfo: nil)
		}
	}
	
	
	var showFeedNames = false
	var showAvatars = false

	var isPrevArticleAvailable: Bool {
		guard let indexPath = currentArticleIndexPath else {
			return false
		}
		return indexPath.row > 0
	}
	
	var isNextArticleAvailable: Bool {
		guard let indexPath = currentArticleIndexPath else {
			return false
		}
		return indexPath.row + 1 < articles.count
	}
	
	var prevArticleIndexPath: IndexPath? {
		guard let indexPath = currentArticleIndexPath else {
			return nil
		}
		return IndexPath(row: indexPath.row - 1, section: indexPath.section)
	}
	
	var nextArticleIndexPath: IndexPath? {
		guard let indexPath = currentArticleIndexPath else {
			return nil
		}
		return IndexPath(row: indexPath.row + 1, section: indexPath.section)
	}
	
	var firstUnreadArticleIndexPath: IndexPath? {
		for (row, article) in articles.enumerated() {
			if !article.status.read {
				return IndexPath(row: row, section: 0)
			}
		}
		return nil
	}
	
	var currentArticle: Article? {
		if let indexPath = currentArticleIndexPath {
			return articles[indexPath.row]
		}
		return nil
	}
	
	var currentArticleIndexPath: IndexPath? {
		didSet {
			if currentArticleIndexPath != oldValue {
				NotificationCenter.default.post(name: .ArticleSelectionDidChange, object: self, userInfo: nil)
			}
		}
	}
	
	var articles = ArticleArray() {
		didSet {
			if articles == oldValue {
				return
			}
			if articles.representSameArticlesInSameOrder(as: oldValue) {
				articleRowMap = [String: Int]()
				NotificationCenter.default.post(name: .ArticleDataDidChange, object: self, userInfo: nil)
				return
			}
			updateShowAvatars()
			articleRowMap = [String: Int]()
			NotificationCenter.default.post(name: .ArticlesDidChange, object: self, userInfo: nil)
		}
	}
	
	var isTimelineUnreadAvailable: Bool {
		if let unreadProvider = timelineFetcher as? UnreadCountProvider {
			return unreadProvider.unreadCount > 0
		}
		return false
	}
	
	var isAnyUnreadAvailable: Bool {
		return appDelegate.unreadCount > 0
	}
	
	init() {

		for section in treeController.rootNode.childNodes {
			expandedNodes.append(section)
			shadowTable.append([Node]())
		}
		
		rebuildShadowTable()
		
		NotificationCenter.default.addObserver(self, selector: #selector(containerChildrenDidChange(_:)), name: .ChildrenDidChange, object: nil)
		NotificationCenter.default.addObserver(self, selector: #selector(batchUpdateDidPerform(_:)), name: .BatchUpdateDidPerform, object: nil)
		NotificationCenter.default.addObserver(self, selector: #selector(displayNameDidChange(_:)), name: .DisplayNameDidChange, object: nil)
		NotificationCenter.default.addObserver(self, selector: #selector(accountStateDidChange(_:)), name: .AccountStateDidChange, object: nil)
		NotificationCenter.default.addObserver(self, selector: #selector(accountsDidChange(_:)), name: .AccountsDidChange, object: nil)
		
		NotificationCenter.default.addObserver(self, selector: #selector(userDefaultsDidChange(_:)), name: UserDefaults.didChangeNotification, object: nil)
{'fraction_non_alphanumeric': 0.09017149179420984, 'fraction_numerical': 0.00331919601696478, 'mean_word_length': 4.717975751186083, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 11, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6701058', 'n_tokens_mistral': 3648, 'n_tokens_neox': 3142, 'n_words': 775}